open-sse Architecture
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. Des interactions d’interface riches et pratiques rendent la planification, l’exécution des agents et le travail sur les projets accessibles et faciles à suivre, pour une expérience de développement engageante.
Une expérience de développement engageante grâce à des interactions d’interface riches et pratiques.
- IntelligentLes workflows OpenSpec transforment une intention en parcours exécutable, de la proposition à la livraison en passant par la conception et les tâches.
- EfficaceL’exécution multi-agent fait avancer recherche, réalisation et revue en parallèle, sur plusieurs dépôts et avec les Agent CLI que vous utilisez déjà.
- IntéressantDes interactions d’interface riches et pratiques rendent la planification, l’exécution des agents et le travail sur les projets accessibles et faciles à suivre.
Télécharger pour Windows
Partager HagiCode
Transformez vos idées en logiciels livrés.
HagiCode est un espace de développement agentique : les workflows OpenSpec planifient le changement, l’exécution multi-agent le réalise et Hero Scenario rend le long parcours visible, pour transformer vos idées en logiciels livrés.
- Intelligent
- Les workflows OpenSpec transforment une intention en parcours, de la proposition à la livraison.
- Efficace
- L’exécution multi-agent mène recherche, réalisation et revue en parallèle.
- Ludique
- Les interactions d’interface facilitent la planification, l’exécution des agents et les projets.
© 2026 HagiCode | Powered By hagilight-starlight@0.21.0 | Branch: main | Commit: de60bcd | Built:

