Architecture
Lemniscate se lit en deux moitiés qui ne se ressemblent pas. Sur le poste de développement, une même logique métier est enveloppée de trois façons — deux extensions d’IDE et un programme de terminal. Sur l’infrastructure du client, trois services indépendants tiennent les clés des fournisseurs de modèles et les plafonds de dépense.
La séparation n’est pas décorative : elle décide de ce qui traverse le pare-feu, et donc de ce qu’un administrateur a à ouvrir et à surveiller.
Le tableau d’ensemble
Section intitulée « Le tableau d’ensemble »flowchart TD subgraph poste["Poste de développement"] vscode["Extension VS Code"] jetbrains["Plugin JetBrains"] cli["CLI lemni"] gui["gui — interface React"] core["core — logique métier"] end
subgraph site["Hébergé par le client"] passerelle["llm-gateway"] base[("gateway-db")] console["gateway-admin"] end
fournisseurs["Fournisseurs de modèles"]
vscode -->|webview| gui jetbrains -->|navigateur embarqué JCEF| gui vscode -->|même processus| core jetbrains -->|sous-processus, messages JSON| core cli -->|lié à la construction| core core -->|requêtes de modèles| passerelle passerelle -->|configuration lue, appels journalisés| base console -->|lecture et écriture| base passerelle -->|clé du fournisseur injectée| fournisseurscore — la logique métier
Section intitulée « core — la logique métier »Tout ce qui décide vit dans core/ : dialogue avec les modèles (core/llm),
outils que l’agent peut appeler (core/tools), fournisseurs de contexte
(core/context), indexation (core/indexing), chargement de la configuration
(core/config), et le protocole de messages qui relie le tout
(core/protocol).
Aucune de ces décisions n’est réimplémentée par les enveloppes. Ce qui change
d’une enveloppe à l’autre, c’est la façon dont core est hébergé :
| Enveloppe | Hébergement de core |
|---|---|
| VS Code | dans le processus de l’extension, par un messager en mémoire |
| JetBrains | dans un sous-processus séparé, dialogue en JSON sur l’entrée et la sortie standard |
CLI lemni |
lié au programme à la construction |
Côté VS Code, VsCodeExtension.ts instancie core derrière un
InProcessMessenger : le même appel de fonction, sans franchir de frontière de
processus.
Côté JetBrains, la contrainte est différente — le plugin est en Kotlin et ne peut
pas charger du TypeScript. core est donc empaqueté séparément dans binary/,
que CoreMessenger.kt lance comme sous-processus et à qui il envoie des messages
JSON portant un messageId, un messageType et une charge utile. Les réponses
reviennent par le même canal, et celles qui concernent l’interface sont réémises
vers le navigateur embarqué. Une variable d’environnement USE_TCP bascule ce
canal sur une connexion TCP — c’est un chemin de débogage, pas un mode de
fonctionnement.
Côté CLI, la construction d’extensions/cli résout core/ et packages/* par
alias : le programme publié contient la logique, il ne la charge pas depuis
ailleurs.
gui — une interface, deux IDE
Section intitulée « gui — une interface, deux IDE »gui/ est une application React. Ce n’est pas une bibliothèque de composants :
c’est l’écran complet du produit dans l’IDE — le chat, l’agent, l’historique, la
page de réglages.
Les deux extensions l’embarquent, chacune à sa manière. L’empaquetage de
l’extension VS Code construit gui/ et vérifie la présence du bundle avant de
produire le VSIX ; le plugin JetBrains sert le même bundle depuis ses ressources,
dans un navigateur embarqué JCEF.
L’interface sait dans quel IDE elle tourne parce que la page qui la charge le lui
écrit : la page servie côté JetBrains pose ide à jetbrains dans le stockage
local, et gui lit cette valeur pour adapter les raccourcis clavier et quelques
écrans. C’est le seul endroit où les deux intégrations divergent visiblement.
Le CLI n’utilise pas gui : son interface est celle du terminal.
Le trio serveur
Section intitulée « Le trio serveur »Les trois composants serveur sont livrés au client et tournent chez lui. Ils sont distincts, et cette séparation porte une garantie.
llm-gateway — le relais
Section intitulée « llm-gateway — le relais »Un serveur Hono qui déclare une seule route : ALL /:endpoint/*. Le premier
segment du chemin nomme un endpoint configuré en base ; tout le reste — chemin
et chaîne de requête — est réémis vers l’URL de base du fournisseur associé.
Avant de relayer, la passerelle enchaîne des contrôles dans un ordre qui est un contrat : authentification de l’appelant, résolution de l’endpoint, présence d’un plan, quota mensuel, clé du fournisseur, puis relais. Un endpoint inconnu est refusé sans que le quota soit consulté, et aucun chemin qui atteint un fournisseur n’échappe au contrôle de quota.
Elle n’impose aucun schéma au corps de la requête. Un corps qui se parse en JSON
voit son champ model remplacé par celui de l’endpoint ; un corps qu’elle ne
sait pas parser part inchangé. Les flux en text/event-stream sont relayés au
fil de l’eau.
gateway-db — l’état
Section intitulée « gateway-db — l’état »Une base PostgreSQL. Elle porte les comptes et le condensat de leur clé d’API,
les plans et leur plafond mensuel, les fournisseurs, les modèles et leur tarif,
les endpoints publiés, et request_logs — une ligne par requête relayée.
Ce qu’elle ne porte pas : les clés des fournisseurs. Pour chaque fournisseur,
elle enregistre le nom de la variable d’environnement qui porte la clé et
l’en-tête HTTP dans lequel la poser. La clé elle-même n’existe que dans
l’environnement du processus llm-gateway.
gateway-admin — la console
Section intitulée « gateway-admin — la console »Une application React servie par son propre serveur, avec quatre onglets — les
comptes (Users), les plans (Plans), les endpoints (Endpoints) et la
consommation (Usage). C’est par elle que passent la création
d’un compte, la rotation d’une clé, la déclaration d’un fournisseur ou d’un
modèle, et la publication d’un endpoint.
La console écrit directement dans la base, avec sa propre chaîne de connexion. Elle ne passe jamais par la passerelle, et la passerelle ne lui parle pas. Le couplage entre les deux services est la base, rien d’autre.
Conséquence pratique : comme la passerelle relit la base à chaque requête, sans cache, un changement fait dans la console prend effet sur la requête suivante. Rien à redémarrer.
Ce que le diagramme ne montre pas
Section intitulée « Ce que le diagramme ne montre pas »Une partie du code du poste sait parler à un service de plan de contrôle hérité du projet dont Lemniscate est issu. Ce service n’est pas opéré, et les chemins qui en dépendent n’aboutissent pas — voir Ce qui n’existe pas.
Le raisonnement qui justifie le passage par la passerelle plutôt qu’un appel direct du poste au fournisseur est développé dans Le rôle de la passerelle. Ce qui sort du poste, et comment le vérifier, est dans Où passent les données.