open-sse Architecture (Français)
Pourquoi un package d’espace de travail distinct ?
Section intitulée « Pourquoi un package d’espace de travail distinct ? »open-sse/ est un espace de travail autonome dans le monorepo OmniRoute pour plusieurs raisons :
- Réutilisabilité —
open-sseest publié sous le nom@omniroute/open-ssesur npm, afin que d’autres projets puissent l’utiliser indépendamment - Séparation claire — le moteur de streaming est découplé de la couche UI/BDD propre à OmniRoute
- Performances — le moteur ne dépend pas de Next.js, ce qui permet des démarrages à froid plus rapides dans les contextes CLI/serverless
- Gestion des versions —
open-ssepeut publier ses versions selon son propre calendrier
"workspaces": ["open-sse"]Structure de premier niveau
Section intitulée « Structure de premier niveau »open-sse/├── index.ts # Point d’entrée public├── types.d.ts # Exports de types publics├── package.json # @omniroute/open-sse├── config/ # Configurations des fournisseurs, constantes, registres├── executors/ # Exécuteurs HTTP par fournisseur (67 + base.ts/index.ts)├── handlers/ # Gestionnaires de requêtes (chatCore, responses, etc.)├── lib/ # Utilitaires internes├── mcp-server/ # Serveur Model Context Protocol├── services/ # Environ 298 modules de service├── transformer/ # Transformateur de format de l’API Responses├── translator/ # Traduction de formats (OpenAI ↔ Claude ↔ Gemini)└── utils/ # Utilitaires partagés (journalisation, erreurs, flux, etc.)Nombre de modules
Section intitulée « Nombre de modules »| Répertoire | Fichiers | Rôle |
| executors/ | 167 | Exécuteurs HTTP par fournisseur (unifiés via la fabrique DefaultExecutor) |
| handlers/ | 157 | Points d’entrée des requêtes (chatCore, responses, embeddings) |
| services/ | ~536 | Routage, mise en cache, limitation de débit, actualisation, etc. |
| translator/ | 56 | Conversion de formats (OpenAI ↔ Claude ↔ Gemini) |
| mcp-server/ | 44 | Outils et transports MCP |
| utils/ | ~108 | Utilitaires transversaux (journalisation, erreurs, flux) |
| config/ | ~339 | Configurations des fournisseurs, constantes, registres |
Le pipeline de requêtes
Section intitulée « Le pipeline de requêtes »Chaque requête LLM traverse un pipeline en 5 étapes :
┌──────────────┐ Requête HTTP │ 1. ROUTAGE │ résolution du combo, sélection du modèle (route Next.js) └──────┬───────┘ │ ▼ ┌──────────────┐ │ 2. TRADUIRE │ conversion de format (OpenAI ↔ Claude ↔ Gemini) └──────┬───────┘ │ ▼ ┌──────────────┐ │ 3. EXÉCUTER │ exécuteur du fournisseur, HTTP, nouvelle tentative, disjoncteur └──────┬───────┘ │ ▼ ┌──────────────┐ │ 4. FLUX │ transformation SSE, contre-pression └──────┬───────┘ │ ▼ ┌──────────────┐ │ 5. CONSIGNER│ suivi de l’utilisation, journal des appels, classification des erreurs └──────┬───────┘ │ ▼ Réponse HTTP (SSE ou JSON)Étape 1 : Routage (services/combo.ts)
Section intitulée « Étape 1 : Routage (services/combo.ts) »Point d’entrée : handleComboChat() dans services/combo.ts
Résout la requête en un tuple concret (provider, model, account, credentials) :
- Rechercher le combo par ID (ou créer un combo virtuel pour les modèles
auto/*) - Appliquer la stratégie de routage (priorité, pondération, round-robin, etc.)
- Exclure les fournisseurs défaillants (disjoncteur)
- Choisir la prochaine cible viable
Pour les modèles auto/*, cette étape :
- Exécute également l’algorithme de notation à 16 facteurs (
services/autoCombo/) - Sélectionne une paire
provider+modelen fonction de l’état de santé, du coût, de la latence, etc.
Étape 2 : Traduction (translator/)
Section intitulée « Étape 2 : Traduction (translator/) »Si le format source (par exemple, OpenAI) diffère du format cible (par exemple, Claude), la requête est traduite :
- Prompt système → message système
- Définitions des outils → format d’outil propre au fournisseur
- Paramètres de raisonnement/réflexion → équivalents propres au fournisseur
- Normalisation des rôles de message (
developer→systempour les fournisseurs autres qu’OpenAI)
Le fichier translator/index.ts expose :
translateRequest(body, sourceFormat, targetFormat): TranslatedRequestneedsTranslation(source, target): booleanÉtape 3 : Exécution (executors/)
Section intitulée « Étape 3 : Exécution (executors/) »Point d’entrée : getExecutor(providerId).execute(request, options)
Tous les fournisseurs utilisent DefaultExecutor (executors/default.ts) comme solution de repli via la fabrique getExecutor(). L’exécuteur :
- Construit l’URL en amont (
buildUrl()) - Ajoute les en-têtes propres au fournisseur (
buildHeaders()) - Transforme le corps de la requête (
transformRequest()) - Envoie la requête HTTP avec de nouvelles tentatives et un délai exponentiel
- Gère l’actualisation de l’authentification si nécessaire (fournisseurs OAuth)
Tous les exécuteurs étendent BaseExecutor (executors/base.ts, 1170 lignes de code), qui fournit :
- Une logique commune de nouvelle tentative
- L’intégration du proxy
- L’intégration du disjoncteur
- Des hooks de consignation de l’utilisation
Étape 4 : Streaming (utils/stream.ts)
Section intitulée « Étape 4 : Streaming (utils/stream.ts) »Pour les réponses en streaming, l’exécuteur renvoie un ReadableStream. Le gestionnaire :
- Achemine le flux à travers une transformation SSE (
createSSETransformStreamWithLogger) - Applique des signaux périodiques pour détecter les connexions interrompues
- Gère proprement la déconnexion du client (
pipeWithDisconnect) - Transforme SSE → JSON pour les clients sans streaming
Pour les réponses sans streaming, l’exécuteur renvoie un objet JSON analysé qui est transmis sans modification.
Étape 5 : Consignation (services/usage.ts)
Section intitulée « Étape 5 : Consignation (services/usage.ts) »Après la réponse (réussie ou échouée), les données d’utilisation sont enregistrées :
prompt_tokens,completion_tokens,cached_tokensprovenant de la réponsecost_usdcalculé à partir des données tarifaireslatency_ms,status,error_classen cas d’échec- Conservées dans la table
usage_history
Les artefacts des journaux d’appels (s’ils sont activés) sont écrits dans ${DATA_DIR}/call_logs/.
Analyse approfondie des fichiers clés
Section intitulée « Analyse approfondie des fichiers clés »chatCore.ts (5977 lignes)
Section intitulée « chatCore.ts (5977 lignes) »Le gestionnaire principal des requêtes. Malgré sa taille, sa structure est claire :
// Pseudo-structure de chatCore.tsexport async function handleChat(request: NextRequest) { // 1. Authentification + CORS await authenticateRequest(request); applyCorsHeaders(response);
// 2. Validation du corps const body = await parseRequestBody(request);
// 3. Détection du format + traduction const sourceFormat = detectFormat(request); const targetFormat = getTargetFormat(providerId); if (needsTranslation(sourceFormat, targetFormat)) { body = translateRequest(body, sourceFormat, targetFormat); }
// 4. Routage du combo 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) { // Continuer avec la cible suivante } }
// 5. Solution de repli d'urgence return await emergencyFallback(body);}Bien qu’il s’agisse d’une seule fonction gigantesque, elle est organisée en sections commentées qui correspondent au pipeline en 5 étapes.
combo.ts (4456 lignes de code)
Section intitulée « combo.ts (4456 lignes de code) »Le moteur de routage qui convertit un combo en une liste ordonnée de cibles.
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("échec de la cible, tentative avec la suivante", { target, err, }); } } throw new ComboExhaustedError("Toutes les cibles ont échoué");}Prend en charge 19 stratégies de routage (voir src/shared/constants/routingStrategies.ts) :
| Stratégie | Comportement |
|---|---|
priority |
Liste ordonnée donnant la priorité à la première cible |
weighted |
Sélection probabiliste selon le poids de chaque cible |
round-robin |
Parcours cyclique des cibles dans l’ordre |
context-relay |
Transfert du contexte entre les cibles |
fill-first |
Épuise le quota avant de passer à la cible suivante |
p2c |
Puissance de deux choix |
random |
Sélection aléatoire uniforme |
least-used |
Sélectionne celle ayant été la moins utilisée récemment |
cost-optimized |
Cible opérationnelle la moins chère en premier |
reset-aware |
Tient compte des fenêtres de réinitialisation du fournisseur |
reset-window |
Routage fondé sur la fenêtre de réinitialisation |
headroom |
Plus grande marge de quota restante en premier |
strict-random |
Répartition véritablement uniforme (sans pondération par la qualité) |
auto |
Utilise une notation à 16 facteurs (autoCombo/) |
lkgp |
Dernier fournisseur connu comme opérationnel en premier |
context-optimized |
Idéal pour les requêtes à contexte long |
fusion |
Diffuse en parallèle vers un panel, puis effectue une synthèse via un juge (fusion.ts) |
base.ts (1170 lignes de code)
Section intitulée « base.ts (1170 lignes de code) »L’exécuteur abstrait dont héritent les 107 exécuteurs. Il contient :
buildUrl()— construction de l’URL par défaut (les sous-classes la redéfinissent pour les cas personnalisés)buildHeaders()— en-têtes par défaut (authentification, type de contenu)transformRequest()— transmission directe par défautexecute()— la boucle HTTP principale avec nouvelles tentatives, temporisation exponentielle et disjoncteur
export class DefaultExecutor extends BaseExecutor { // Gère tous les fournisseurs compatibles avec OpenAI/Anthropic // Les fournisseurs enregistrent leurs configurations (URL, authentification, en-têtes), mais partagent la logique de l'exécuteur}Le comportement propre à chaque fournisseur (en-têtes d’authentification, URL de base, en-têtes de version) est configuré au moyen du registre des fournisseurs, et non de classes d’exécuteur distinctes.
---
## Services (117 modules)
Les services sont des **modules ciblés et dédiés à une seule tâche** que les gestionnaires composent. Les grandes catégories sont les suivantes :
### Routage et combinaison
- `combo.ts` — point d’entrée des requêtes utilisant le routage combiné- `services/autoCombo/` — notation à 16 facteurs, 8 stratégies de routage automatique- `wildcardRouter.ts` — fait correspondre les routes génériques (`gpt-*`)- `modelFamilyFallback.ts` — repli T5 au sein d’une même famille
### Limitation du débit et quota
- `rateLimitManager.ts` — compartiment à jetons par clé+fournisseur- `usage.ts` — enregistrement de l’utilisation- `quotaCache.ts` — instantanés des quotas en mémoire
### Compte et jeton
- `tokenRefresh.ts` — actualisation OAuth en cas de réponse 401- `accountFallback.ts` — basculement vers un autre compte- `sessionManager.ts` — état des sessions multitours
### Intelligence
- `intentClassifier.ts` — classe l’intention de la requête- `taskAwareRouter.ts` — effectue le routage selon le type de tâche- `thinkingBudget.ts` — alloue les jetons de raisonnement- `contextManager.ts` — injecte le contexte de routage
### Résilience
- `resilience.ts` — orchestration des nouvelles tentatives, du délai exponentiel et du disjoncteur- `emergencyFallback.ts` — repli de dernier recours- `modelDeprecation.ts` — routage automatique vers les modèles successeurs
### État
- `signatureCache.ts` — déduplication selon la signature de la requête- `volumeDetector.ts` — délestage de charge- `contextHandoff.ts` — sérialisation des sessions
### Compression
- `compression/` (sous-répertoire) — pipeline de compression complet- 39 fichiers couvrant les moteurs, les ensembles de règles et les adaptateurs
### Compétences
- (décrites dans [SKILLS.md](./SKILLS.md))
### Mémoire
- (décrite dans [MEMORY.md](./MEMORY.md))
---
## Exécuteurs (plus de 75 fichiers)
Un fichier par fournisseur. Ils étendent tous `BaseExecutor` et redéfinissent les éléments qui diffèrent.
### Modèles courants
Les fournisseurs sont résolus via `getExecutor(providerId)`, qui renvoie l’exécuteur configuré. Les fournisseurs compatibles avec OpenAI/Anthropic utilisent `DefaultExecutor` (`executors/default.ts`). Le comportement propre à chaque fournisseur (URL de base, en-têtes d’authentification, version de l’API) est configuré dans `open-sse/config/providers/`, tandis que les transformations du corps des requêtes sont gérées dans `open-sse/translator/`.
L’**URL personnalisée** est définie via la configuration du fournisseur :
```ts// Configuration du fournisseur dans open-sse/config/providers/export default { id: "together", baseURL: "https://api.together.xyz/v1/chat/completions",}L’authentification personnalisée est gérée par l’intermédiaire de la configuration d’authentification du registre des fournisseurs (clé d’API, OAuth, profils d’en-têtes).
Les transformations personnalisées du corps de la requête (par exemple, Anthropic séparant system de messages) sont enregistrées par fournisseur dans open-sse/translator/.
### La fabrique d’exécuteurs
`executors/index.ts` exporte `getExecutor(providerId)` :
```tsimport { getExecutor } from "@omniroute/open-sse/executors";
const executor = getExecutor("anthropic");const result = await executor.execute({ model: "claude-sonnet-4-5", messages: [...],});La résolution passe par ExecutorRegistry (executors/registry.ts) : chaque exécuteur spécialisé est déclaré dans la table intégrée de executors/index.ts et enregistré via registerExecutor(alias, instance) lors du chargement du module ; getExecutor() consulte le registre et se rabat sur un DefaultExecutor mémoïsé pour tout fournisseur ne disposant pas d’une entrée spécialisée. La correspondance complète alias → exécuteur est définie par le test de référence tests/unit/executor-map-golden.test.ts.
Traducteurs
Section intitulée « Traducteurs »Effectuez des traductions entre 3 formats : OpenAI, Anthropic, Gemini, ainsi que la nouvelle Responses API.
Quand la traduction a lieu
Section intitulée « Quand la traduction a lieu »import { needsTranslation, translateRequest } from "@omniroute/open-sse/translator";
if (needsTranslation(sourceFormat, targetFormat)) { body = translateRequest(body, sourceFormat, targetFormat);}Traductions courantes :
OpenAI → Anthropic: champsystemdistinct, en-têtex-api-keyOpenAI → Gemini:contentsau lieu demessages,systemInstructionOpenAI → Responses API: tableauinput, étatprevious_response_id
Cas particuliers pris en charge
Section intitulée « Cas particuliers pris en charge »- Rôle
developer→systempour les fournisseurs autres qu’OpenAI - Rôle
system→ fusionné dans le premier message utilisateur pour GLM/ERNIE json_schema→responseMimeType+responseSchemade Geminitools→ format d’outil propre au fournisseur- Paramètres de raisonnement (o1, Claude) → équivalents propres au fournisseur
Serveur MCP
Section intitulée « Serveur MCP »open-sse/mcp-server/ implémente le serveur Model Context Protocol :
- 110 outils (gestion des fournisseurs, combinaisons, mémoire, cache, compression, proxy, compétences, ludification, extensions, Notion, Obsidian, corpus local)
- 3 transports : stdio, SSE, HTTP diffusé en continu
- 33 portées pour une autorisation précise
Enregistrement des outils
Section intitulée « Enregistrement des outils »Les outils sont enregistrés sous forme de fichiers autonomes dans open-sse/mcp-server/tools/, chacun exportant un nom, un schéma, un gestionnaire et une portée :
import { z } from "zod";export default { name: "omniroute_get_health", description: "Get system health snapshot", scope: "read:health", inputSchema: z.object({}), handler: async (_args, ctx) => { return await getSystemHealth(); },};Transports
Section intitulée « Transports »// stdio (utilisation en CLI)startMcpStdio(server);
// SSE (diffusion en continu basée sur HTTP)startMcpSse(server, port);
// HTTP diffusable en continu (MCP moderne)startMcpStreamable(server, port);Autorisation
Section intitulée « Autorisation »Chaque appel d’outil passe par des vérifications de portée (open-sse/mcp-server/auth/) :
if (!hasScope(apiKey, "providers:read")) { throw new Error("Insufficient scope");}Transformateurs
Section intitulée « Transformateurs »open-sse/transformer/ effectue la conversion entre les formats Chat Completions et Responses API.
Pourquoi un transformateur distinct ?
Section intitulée « Pourquoi un transformateur distinct ? »Responses API est le nouveau format d’OpenAI avec des conversations avec état (previous_response_id). Lorsqu’un client envoie une requête Responses, OmniRoute :
- Convertit en interne Responses → Chat Completions
- Envoie la requête au fournisseur (tout fournisseur prenant en charge Chat Completions)
- Reconvertit la réponse au format Responses
- Diffuse en continu la réponse convertie au client
Le transformateur (transformer/responsesTransformer.ts) fournit :
createResponsesApiTransformStream(): TransformStreamCelui-ci gère :
- Les événements
response.output_item.added - Les événements
response.output_text.delta - L’événement
response.completed - La mise en correspondance des appels d’outils (
function_call↔tool_calls)
Configuration
Section intitulée « Configuration »open-sse/config/ contient la couche de configuration :
| Fichier | Objectif |
|---|---|
providerRegistry.ts |
Registre des modèles de chat du catalogue de 352 fournisseurs |
providerModels.ts |
Alias de modèles, mise en correspondance des formats |
constants.ts |
Délais d’expiration, limites, codes d’état |
defaultThinkingSignature.ts |
Signature de raisonnement Claude par défaut |
modelStrip.ts (dans services) |
Suppression des champs propre à chaque fournisseur |
Schéma du registre des fournisseurs
Section intitulée « Schéma du registre des fournisseurs »interface ProviderConfig { id: string; name: string; baseUrl: string; authType: "bearer" | "api-key" | "oauth" | "cookie"; executorClass: string; defaultModel: string; capabilities: ProviderCapabilities; models: ModelDefinition[];}La validation Zod au chargement du module garantit que toutes les configurations des fournisseurs sont valides.
Contraintes de performance
Section intitulée « Contraintes de performance »Le moteur de routage est soumis à des budgets de performance stricts :
| Opération | Objectif | Mesure |
|---|---|---|
| Résolution des combinaisons | <10ms | Pour 50 cibles |
| Vérification de la limitation de débit | <1ms | Seau à jetons en mémoire |
| Repli par famille de modèles | <5ms | Définitions de familles en cache |
| Distribution des requêtes de routage | <2ms | Chemin critique |
| Aucune E/S bloquante dans le chemin critique du routage | — | Entièrement asynchrone |
Antipatterns
Section intitulée « Antipatterns »❌ Appels synchrones à la base de données dans combo.ts — précalculer et mettre en cache
❌ Logique de nouvelle tentative dans les gestionnaires — utiliser retry() du service de résilience
❌ Accès direct à la configuration des fournisseurs — utiliser les accesseurs de providerRegistry
❌ Chaînes de repli codées en dur — les définir dans modelFamilyFallback.ts
❌ Mutations d’état entre des requêtes concurrentes — utiliser uniquement un contexte propre à chaque requête
Ajout d’un nouveau composant
Section intitulée « Ajout d’un nouveau composant »Ajout d’un nouveau service
Section intitulée « Ajout d’un nouveau service »- Créer
open-sse/services/[serviceName].tsavec une responsabilité bien définie - Exporter la fonction principale du gestionnaire ainsi que les éventuelles constantes
- Ajouter des tests unitaires dans
tests/unit/services/[serviceName].test.mjs - L’intégrer au pipeline de traitement des requêtes dans
handlers/chatCore.ts(s’il concerne le routage) - Mettre à jour la logique de routage dans
combo.tssi le service affecte la sélection des cibles - Le documenter dans ce fichier
Ajout d’un nouvel exécuteur
Section intitulée « Ajout d’un nouvel exécuteur »- Créer
open-sse/executors/[provider].tsétendantBaseExecutor - L’enregistrer dans
config/providerRegistry.ts - L’ajouter à la fabrique dans
executors/index.ts - Ajouter des tests unitaires pour l’exécuteur
- Le documenter dans
docs/architecture/ARCHITECTURE.md
Ajout d’un nouvel outil MCP
Section intitulée « Ajout d’un nouvel outil MCP »- Créer ou mettre à jour
open-sse/mcp-server/tools/[category]Tools.ts - Définir le schéma Zod des entrées
- Enregistrer l’outil dans
mcp-server/index.ts - L’ajouter à la matrice des portées dans
mcp-server/auth/ - Ajouter des tests unitaires
Voir aussi
Section intitulée « Voir aussi »- ARCHITECTURE.md — architecture générale
- CODEBASE_DOCUMENTATION.md — référence d’ingénierie
- REPOSITORY_MAP.md — description répertoire par répertoire
- AUTO-COMBO.md — notation à 16 facteurs
- MCP-SERVER.md — serveur MCP
- A2A-SERVER.md — serveur A2A
- Source :
open-sse/(plus de 400 fichiers, environ 143 000 lignes de code)
HagiCode
HagiCode est un espace de développement agentique qui associe workflows structurés, exécution multi-agent et vues Hero Dungeon.
Transformez vos idées en logiciels utiles grâce à un workflow agentique plus intelligent, rapide et agréable.

- SmartDes workflows structurés transforment une intention en parcours exécutable, de l’idée à la livraison.
- EfficientLes workflows multi-agents font avancer recherche, réalisation et revue en parallèle.
- FunHero Dungeon rend les longues sessions de code plus visuelles et collaboratives.