AgentInterface: Session-Host und Event-Verteilung
AgentInterface ist ein Lit-customElement in pi-web-ui, das zwischen dem Agent aus pi-agent-core und den konkreten Nachrichten-Render-Komponenten vermittelt. Es hält eine Agent-Instanz als session-Property, kümmert sich um die Verdrahtung von streamFn und getApiKey, abonniert die Agent-Ereignisse, wandelt Text aus dem MessageEditor in prompt()-Aufrufe um und verteilt die zurückkommenden Ereignisse an MessageList und StreamingMessageContainer. Die Klasse selbst spricht nicht mit dem LLM und speichert keine Historie — sie ist nur ein zustandsbehafteter Event-Router.
Zuständigkeiten
- Session halten:
@property sessionzeigt auf eineAgent-Instanz; bei Session-Wechsel werden die Events neu abonniert. Siehepackages/web-ui/src/components/AgentInterface.ts:20-47. - streamFn und getApiKey verdrahten: In
connectedCallbackwirdsession.streamFn, falls noch der DefaultstreamSimple, durchcreateStreamFnmit Proxy-Unterstützung ersetzt; gleichzeitig wird ein Default-getApiKeyinjiziert, das den Schlüssel ausAppStorageliest. Siehepackages/web-ui/src/components/AgentInterface.ts:130-152. - Events abonnieren:
session.subscriberegistriert Callbacks, diemessage_start/message_update/message_end/agent_endaufStreamingMessageContainer.setMessageundrequestUpdatemappen. Siehepackages/web-ui/src/components/AgentInterface.ts:153-187. - Nachrichten senden:
sendMessage(input, attachments)prüft model und API key; fehlt der key, wirdonApiKeyRequiredaufgerufen, danachsession.prompt(input)bzw.prompt(message)mit Anhängen. Siehepackages/web-ui/src/components/AgentInterface.ts:215-262. - Auto-Scroll: Ein
ResizeObserverbeobachtet die Höhe des Inhalts und kombiniert mit demscroll-Ereignis, ob der Nutzer manuell nach oben gescrollt hat; darüber wird entschieden, ob automatisch unten angeheftet wird. Siehepackages/web-ui/src/components/AgentInterface.ts:89-105.
Designmotivation
Warum nicht direkt MessageEditor Agent.prompt aufrufen lassen? Weil auf der Browser-Seite noch ein paar Dinge fehlen: Die Proxy-URL muss dynamisch nach provider/key gewählt werden, der API key muss asynchron aus IndexedDB geholt werden, bei fehlendem key muss ein Dialog aufpoppen, und während des Streamings müssen Streaming-Container und stabile Liste entdoppelt werden. Wenn man diese Logik in jede Host-App steckt, wiederholt sie sich; packt man sie in die Nachrichten-Komponente, koppelt die Rendering-Schicht an die Storage-Schicht. AgentInterface wird als einzige Einstiegspunkt herausgezogen: ChatPanel macht nur Layout, MessageEditor nur Eingabe, MessageList nur Rendering — alle drei sprechen über die Klasse mit Agent.
Ein weiteres Motiv: session ist austauschbar. Beim Wechsel der Konversation wird nicht die ganze Komponente neu aufgebaut, sondern nur das session-Property getauscht; willUpdate erkennt die Änderung und ruft setupSessionSubscription() für ein neues Abonnement auf. Siehe packages/web-ui/src/components/AgentInterface.ts:68-75.
Wichtige Dateien
packages/web-ui/src/components/AgentInterface.ts:20-47—class AgentInterface-Deklaration und alle@property/@query-Felder.packages/web-ui/src/components/AgentInterface.ts:77-109—connectedCallback: holt nach dem ersten Frame den scroll container, hängtResizeObserveran, ruftsetupSessionSubscription.packages/web-ui/src/components/AgentInterface.ts:130-152— ersetzt den Default-streamFn, injiziertgetApiKey.packages/web-ui/src/components/AgentInterface.ts:153-187—session.subscribe-Callbacks, verteilt nach Event-Typ.packages/web-ui/src/components/AgentInterface.ts:215-262—sendMessage: key-Prüfung,onBeforeSend-Hook, Editor leeren,session.promptaufrufen.packages/web-ui/src/components/AgentInterface.ts:264-297—renderMessages: bautMessageListundStreamingMessageContainerzusammen und reichtpendingToolCallsundtoolResultsByIdweiter.packages/web-ui/src/components/AgentInterface.ts:111-128—disconnectedCallback: räumt observer, listener und unsubscribe weg.
setupSessionSubscription ist die zentrale Verdrahtungsstelle; hier werden streamFn und getApiKey auf die Standard-Implementierung ersetzt:
// 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 verkettet key-Prüfung und onApiKeyRequired-Callback; fehlt der key, entscheidet der Host, wie der Dialog aufgeht:
// 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;
}
}Datenfluss
Der Nutzer drückt im MessageEditor Enter; nach key-Prüfung und Proxy-Verdrahtung wird der Agent-Loop ausgelöst und läuft zurück ins Rendering:
Randbedingungen und Fehler
- Session nicht gesetzt:
renderzeigt direkt einen „No session set"-Platzhalter,sendMessagewirftNo session set on AgentInterface. Siehepackages/web-ui/src/components/AgentInterface.ts:216-219. - model nicht gesetzt: Ebenfalls in
sendMessagewirft esNo model set, als Hinweis, dass der Host das Modell nicht initialisiert hat. - Senden während des Streamings: Wenn
isStreamingtrue ist, kehrtsendMessagedirekt zurück — keine Queue, kein Hineinfunken. Siehepackages/web-ui/src/components/AgentInterface.ts:216. - Doppeltes Abonnieren:
setupSessionSubscriptionmacht zuerst_unsubscribeSession()für das alte Abonnement und hängt dann das neue an; ein Session-Wecksel leakt keine Listener. - Nachrichten-Entdopplung: Bei
message_endwirdStreamingMessageContainer.setMessage(null, true)geleert, damit der Streaming-Container die Nachricht nicht doppelt rendert, wenn die stabile Liste sie schon enthält. Siehepackages/web-ui/src/components/AgentInterface.ts:161-168.
Zusammenfassung
AgentInterface ist der „Session-Host" auf der Browser-Seite: verdrahtet streamFn und getApiKey, abonniert Events und verteilt sie an die Render-Komponenten. Nach oben wird es von ChatPanel in das Layout eingebaut, nach unten treibt es den Agent aus pi-agent-core an. Die Proxy-Entscheidungen im Detail stehen in CORS-Proxy und createStreamFn, die Render-Komponenten in Nachrichten-Render-Komponenten.