AgentInterface : hôte de session et dispatch d'événements
AgentInterface est une couche Lit customElement dans pi-web-ui, insérée entre l'Agent de pi-agent-core et les composants de rendu de messages concrets. Elle détient une instance d'Agent via la propriété session, se charge d'installer streamFn et getApiKey, s'abonne aux événements de l'Agent, transforme le texte saisi dans MessageEditor en appel prompt(), puis redispatche les événements qui reviennent vers MessageList et StreamingMessageContainer. Elle ne touche pas au LLM et ne stocke pas d'historique : c'est un routeur d'événements avec état.
Responsabilités
- Détient la session :
@property sessionpointe vers une instance d'Agent; à chaque changement de session, on se réabonne aux événements, voirpackages/web-ui/src/components/AgentInterface.ts:20-47. - Installe streamFn et getApiKey : dans
connectedCallback, sisession.streamFnvaut encore le défautstreamSimple, on le remplace parcreateStreamFnavec support proxy, et on injecte ungetApiKeypar défaut qui lit la clé depuisAppStorage, voirpackages/web-ui/src/components/AgentInterface.ts:130-152. - Abonnement aux événements :
session.subscribeenregistre des callbacks et mappemessage_start/message_update/message_end/agent_endversStreamingMessageContainer.setMessageetrequestUpdate, voirpackages/web-ui/src/components/AgentInterface.ts:153-187. - Envoi de message :
sendMessage(input, attachments)valide le modèle et la clé API ; si la clé manque, on appelleonApiKeyRequired, puis on invoquesession.prompt(input)ouprompt(message)avec pièces jointes, voirpackages/web-ui/src/components/AgentInterface.ts:215-262. - Auto-scroll : un
ResizeObserverobserve la hauteur du contenu, couplé à l'événementscrollpour détecter si l'utilisateur remonte volontairement, et décider si on colle en bas, voirpackages/web-ui/src/components/AgentInterface.ts:89-105.
Motivations de design
Pourquoi ne pas laisser MessageEditor appeler directement Agent.prompt ? Parce que côté navigateur il manque encore quelques bricoles : l'URL du proxy doit être décidée dynamiquement selon provider/clé, la clé API doit être lue de façon asynchrone depuis IndexedDB, une boîte de dialogue doit s'ouvrir en cas de clé manquante, et pendant le streaming il faut gérer la déduplication entre le conteneur de streaming et la liste stable. Si on fourrait cette logique dans chaque application hôte, elle serait dupliquée ; si on la mettait dans les composants de message, le rendu serait couplé à la couche de stockage. AgentInterface est extraite comme point d'entrée unique : ChatPanel ne gère que la mise en page, MessageEditor que la saisie, MessageList que le rendu — les trois branches se branchent à l'Agent via elle.
L'autre motivation est que session est remplaçable : quand l'utilisateur change de session, on ne reconstruit pas tout le composant, on ne change que la propriété session ; willUpdate détecte le changement et relance setupSessionSubscription() pour se réabonner, voir packages/web-ui/src/components/AgentInterface.ts:68-75.
Fichiers clés
packages/web-ui/src/components/AgentInterface.ts:20-47— déclaration declass AgentInterfaceet de tous les champs@property/@query.packages/web-ui/src/components/AgentInterface.ts:77-109—connectedCallback: attend la première frame pour récupérer le scroll container, branche leResizeObserver, appellesetupSessionSubscription.packages/web-ui/src/components/AgentInterface.ts:130-152— remplace lestreamFnpar défaut, injectegetApiKey.packages/web-ui/src/components/AgentInterface.ts:153-187— callbackssession.subscribe, dispatch selon le type d'événement.packages/web-ui/src/components/AgentInterface.ts:215-262—sendMessage: validation de clé, hookonBeforeSend, vidage de l'éditeur, appel àsession.prompt.packages/web-ui/src/components/AgentInterface.ts:264-297—renderMessages: assembleMessageListetStreamingMessageContainer, passependingToolCallsettoolResultsById.packages/web-ui/src/components/AgentInterface.ts:111-128—disconnectedCallback: nettoie observer, listener, unsubscribe.
setupSessionSubscription est le point d'assemblage central : streamFn et getApiKey y sont remplacés par leurs implémentations par défaut :
// packages/web-ui/src/components/AgentInterface.ts:138-151
if (this.session.streamFn === streamSimple) {
this.session.streamFn = createStreamFn(async () => {
const enabled = await getAppStorage().settings.get<boolean>("proxy.enabled");
return enabled ? (await getAppStorage().settings.get<string>("proxy.url")) || undefined : undefined;
});
}
if (!this.session.getApiKey) {
this.session.getApiKey = async (provider: string) => {
const key = await getAppStorage().providerKeys.get(provider);
return key ?? undefined;
};
}sendMessage chaîne la validation de clé et le callback onApiKeyRequired : si la clé manque, c'est l'hôte qui décide comment réagir :
// packages/web-ui/src/components/AgentInterface.ts:222-238
const provider = session.state.model.provider;
const apiKey = await getAppStorage().providerKeys.get(provider);
if (!apiKey) {
if (!this.onApiKeyRequired) {
console.error("No API key configured and no onApiKeyRequired handler set");
return;
}
const success = await this.onApiKeyRequired(provider);
if (!success) {
return;
}
}Flux de données
L'utilisateur appuie sur Entrée dans MessageEditor ; après validation de clé et assemblage du proxy, on déclenche la boucle Agent et on reçoit le flux pour le rendu :
Limites et cas d'échec
- Session non définie :
renderaffiche un placeholder « No session set » ;sendMessagelèveNo session set on AgentInterface, voirpackages/web-ui/src/components/AgentInterface.ts:216-219. - Modèle non défini :
sendMessagelèveNo model setpour signaler que l'hôte n'a pas initialisé le modèle. - Envoi pendant un streaming : quand
isStreamingest true,sendMessageretourne immédiatement, sans file d'attente ni interruption, voirpackages/web-ui/src/components/AgentInterface.ts:216. - Abonnement dupliqué :
setupSessionSubscriptionappelle d'abord_unsubscribeSession()sur l'ancien abonnement avant de brancher le nouveau ; un changement de session ne fuit pas de listeners. - Déduplication des messages : sur
message_end,StreamingMessageContainer.setMessage(null, true)vide le conteneur, pour éviter que la liste stable (qui contient déjà ce message) ne soit re-rendue par le conteneur de streaming, voirpackages/web-ui/src/components/AgentInterface.ts:161-168.
Pour résumer
AgentInterface est l'« hôte de session » côté navigateur : elle installe streamFn et getApiKey, s'abonne aux événements, et les dispatche vers les composants de rendu. En amont, ChatPanel l'insère dans la mise en page ; en aval, elle pilote l'Agent de pi-agent-core. Pour les détails du décision proxy, voir Proxy CORS et createStreamFn ; pour les composants de rendu, voir Composants de rendu des messages.