Preise

Architektur im Überblick

Quelldateien

Diese Seite wurde aus den folgenden Quelldateien erstellt:

Das Projekt pi-mono ist eine modulare Monorepo-Architektur für KI-gestützte Coding-Assistenten. Es bietet eine flexible Agenten-Abstraktionsschicht, die verschiedene LLM-Provider integriert und über multiple Benutzerschnittstellen (Terminal, Web, Slack) zugänglich gemacht wird. Die Architektur folgt einem Plugin-basierten Ansatz mit strikter Trennung zwischen Kernlogik, Erweiterungen und Präsentationsschichten.

Systemarchitektur

Die Gesamtarchitektur von pi-mono basiert auf einer dreischichtigen Struktur: Core Layer (Agenten-Logik, Session-Management), Extension Layer (Plugins, Skills, Themes) und Presentation Layer (TUI, Web-UI, Slack-Bot). Diese Trennung ermöglicht maximale Wiederverwendbarkeit und unabhängige Entwicklung der einzelnen Komponenten.

正在加载图表渲染器...

Architektur-Highlights:

Core Agent Architecture

Das Agent Package stellt die fundamentale Abstraktionsschicht für alle LLM-Interaktionen dar. Die Agent-Klasse kapselt Zustandsverwaltung, Message-Konvertierung und Tool-Execution in einer einheitlichen API.

Zustandsverwaltung und Konfiguration

Der Agent verwaltet einen internen AgentState mit folgenden Kernkomponenten:

typescript
1// Aus packages/agent/src/agent.ts:117-127
2private _state: AgentState = {
3    systemPrompt: "",
4    model: getModel("google", "gemini-2.5-flash-lite-preview-06-17"),
5    thinkingLevel: "off",
6    tools: [],
7    messages: [],
8    isStreaming: false,
9    streamMessage: null,
10    pendingToolCalls: new Set<string>(),
11    error: undefined,
12};

Die AgentOptions-Schnittstelle (packages/agent/src/agent.ts:41-114) bietet umfangreiche Konfigurationsmöglichkeiten:

OptionTypBeschreibung
convertToLlm(messages: AgentMessage[]) => Message[]Transformiert interne Messages vor LLM-Aufruf
transformContext(messages, signal) => Promise<AgentMessage[]>Context-Pruning oder externe Injektion
steeringMode"all" | "one-at-a-time"Steuerung der Steering-Message-Zustellung
streamFnStreamFnCustom Stream-Funktion für Proxy-Backends
getApiKey(provider: string) => Promise&lt;string&gt;Dynamische API-Key-Auflösung
toolExecution"parallel" | "sequential"Tool-Ausführungsmodus

Message-Konvertierung und Context-Transformation

Die Standard-Implementierung defaultConvertToLlm (packages/agent/src/agent.ts:37-39) filtert Messages auf LLM-kompatible Rollen:

typescript
1function defaultConvertToLlm(messages: AgentMessage[]): Message[] {
2    return messages.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult");
3}

Die optionale transformContext-Funktion ermöglicht erweiterte Szenarien wie Context-Window-Management oder Injection externer Kontexte vor jedem LLM-Aufruf.

Tool-Execution-Lifecycle

Der Agent unterstützt zwei Tool-Execution-Modi:

  • Parallel (Standard): Mehrere Tool-Calls werden gleichzeitig ausgeführt
  • Sequential: Tool-Calls werden nacheinander verarbeitet

Zusätzlich bieten beforeToolCall und afterToolCall Hooks (packages/agent/src/agent.ts:109-113) Eingriffspunkte für Validierung, Logging oder Modifikation von Tool-Ergebnissen.

Session Management

Das Session-Management-System verantwortet Persistierung, Wiederherstellung und Metadaten-Verwaltung von Konversationen. Es integriert nahtlos mit dem Agent-Core und unterstützt File-basierte Speicherung.

Session-Info-Erstellung

Die Funktion buildSessionInfo (packages/coding-agent/src/core/session-manager.ts:542) extrahiert Session-Metadaten aus gespeicherten Einträgen:

正在加载图表渲染器...

Die Aktivitätszeit-Berechnung (packages/coding-agent/src/core/session-manager.ts:504-530) priorisiert Message-Timestamps über Header-Timestamps:

typescript
1function getLastActivityTime(entries: FileEntry[]): number | undefined {
2    let lastActivityTime: number | undefined;
3    for (const entry of entries) {
4        if (entry.type !== "message") continue;
5        const message = (entry as SessionMessageEntry).message;
6        if (!isMessageWithContent(message)) continue;
7        if (message.role !== "user" && message.role !== "assistant") continue;
8        
9        const msgTimestamp = (message as { timestamp?: number }).timestamp;
10        if (typeof msgTimestamp === "number") {
11            lastActivityTime = Math.max(lastActivityTime ?? 0, msgTimestamp);
12        }
13    }
14    return lastActivityTime;
15}

CLI-Integration

Der Haupteinstiegspunkt (packages/coding-agent/src/main.ts:1-54) integriert Session-Management mit der CLI-Argumentverarbeitung:

typescript
1// Aus packages/coding-agent/src/main.ts:25-31
2import { SessionManager } from "./core/session-manager.js";
3import { SettingsManager } from "./core/settings-manager.js";
4import { printTimings, time } from "./core/timings.js";
5import { allTools } from "./core/tools/index.js";
6import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";

Die SessionManager-Klasse wird für Operationen wie Session-Auflistung, -Laden und -Speicherung verwendet, während selectSession (packages/coding-agent/src/main.ts:15) einen interaktiven Session-Picker bereitstellt.

Model Resolution Registry

Das Model-Resolution-System ermöglicht flexible Auswahl von LLM-Providern und Modellen über CLI-Argumente mit Fuzzy-Matching-Unterstützung.

CLI-Modellauflösung

Die Funktion resolveCliModel (packages/coding-agent/src/core/model-resolver.ts:328-367) unterstützt multiple Eingabeformate:

typescript
1export function resolveCliModel(options: {
2    cliProvider?: string;
3    cliModel?: string;
4    modelRegistry: ModelRegistry;
5}): ResolveCliModelResult {
6    const { cliProvider, cliModel, modelRegistry } = options;
7    
8    if (!cliModel) {
9        return { model: undefined, warning: undefined, error: undefined };
10    }
11    
12    // Wichtig: Verwende ALLE Modelle, nicht nur mit vorkonfiguriertem Auth
13    const availableModels = modelRegistry.getAll();
14    if (availableModels.length === 0) {
15        return {
16            model: undefined,
17            warning: undefined,
18            error: "No models available. Check installation or add models to models.json.",
19        };
20    }
21    
22    // Case-insensitive Provider-Lookup
23    const providerMap = new Map<string, string>();
24    for (const m of availableModels) {
25        providerMap.set(m.provider.toLowerCase(), m.provider);
26    }
27    // ... Fuzzy-Matching Logik
28}

Unterstützte CLI-Formate:

FormatBeispielBeschreibung
--provider X --model Y--provider openai --model gpt-4Explizite Provider-Angabe
--model provider/model--model openai/gpt-4Kombiniertes Format
--model pattern:thinking--model claude:highMit Thinking-Level

Dynamische API-Key-Verwaltung

Der Agent unterstützt dynamische API-Key-Auflösung über den getApiKey-Callback (packages/agent/src/agent.ts:79-81):

typescript
1/**
2 * Resolves an API key dynamically for each LLM call.
3 * Useful for expiring tokens (e.g., GitHub Copilot OAuth).
4 */
5getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;

Dies ermöglicht die Verwendung von OAuth-Tokens mit begrenzter Gültigkeitsdauer, die vor jedem LLM-Aufruf erneuert werden müssen.

Transport-Konfiguration

Die AgentOptions erlauben die Auswahl des bevorzugten Transports (packages/agent/src/agent.ts:94-96):

typescript
1/**
2 * Preferred transport for providers that support multiple transports.
3 */
4transport?: Transport;

Der Standard-Transport ist "sse" (Server-Sent Events), kann aber für spezifische Provider-Anforderungen überschrieben werden.

Extension Package System

Das Extension-System ermöglicht die Entdeckung und Verwaltung von Plugins, Skills, Prompts und Themes über eine Manifest-basierte Architektur.

Ressourcentypen und Manifest-Struktur

Der Package Manager definiert vier Ressourcentypen (packages/coding-agent/src/core/package-manager.ts:109-118):

typescript
1type ResourceType = "extensions" | "skills" | "prompts" | "themes";
2
3const FILE_PATTERNS: Record<ResourceType, RegExp> = {
4    extensions: /\.(ts|js)$/,
5    skills: /\.md$/,
6    prompts: /\.md$/,
7    themes: /\.json$/,
8};

Das PiManifest-Interface (packages/coding-agent/src/core/package-manager.ts:88-93) definiert die Struktur in package.json:

typescript
1interface PiManifest {
2    extensions?: string[];
3    skills?: string[];
4    prompts?: string[];
5    themes?: string[];
6}

Extension-Discovery

Die Funktion resolveExtensionEntries (packages/coding-agent/src/core/package-manager.ts:420-448) implementiert einen mehrstufigen Discovery-Prozess:

正在加载图表渲染器...

Discovery-Logik:

  1. Manifest-basiert: Wenn package.json ein pi.extensions-Array enthält, werden diese Pfade aufgelöst
  2. Index-basiert: Fallback auf index.ts oder index.js im Verzeichnis
  3. Validierung: Nur existierende Dateien werden zurückgegeben

Resource Accumulator

Der ResourceAccumulator (packages/coding-agent/src/core/package-manager.ts:95-100) sammelt alle Ressourcen mit Metadaten:

typescript
1interface ResourceAccumulator {
2    extensions: Map<string, { metadata: PathMetadata; enabled: boolean }>;
3    skills: Map<string, { metadata: PathMetadata; enabled: boolean }>;
4    prompts: Map<string, { metadata: PathMetadata; enabled: boolean }>;
5    themes: Map<string, { metadata: PathMetadata; enabled: boolean }>;
6}

Die enabled-Flag ermöglicht Deaktivierung ohne physisches Entfernen der Ressource.

User Interface Layers

pi-mono bietet drei unterschiedliche Präsentationsschichten für verschiedene Einsatzszenarien: Terminal (TUI), Browser (Web-UI) und Chat-Plattformen (Slack via Mom).

Terminal User Interface (TUI)

Das TUI-Package (packages/tui/src/tui.ts:57-212) implementiert differenzielles Rendering für Terminal-basierte Interaktion:

typescript
1export class TUI extends Container {
2    public terminal: Terminal;
3    private previousLines: string[] = [];
4    private previousWidth = 0;
5    // ...
6}

Kernkonzepte:

typescript
1export const CURSOR_MARKER = "\x1b_pi:c\x07";

Die OverlayOptions-Schnittstelle (packages/tui/src/tui.ts:114-138) unterstützt flexible Positionierung mit Prozentwerten:

typescript
1export interface OverlayOptions {
2    width?: SizeValue;        // Number or "50%"
3    minWidth?: number;
4    maxHeight?: SizeValue;
5    anchor?: OverlayAnchor;   // "center", "top-left", etc.
6    offsetX?: number;
7    offsetY?: number;
8    // ...
9}

Web User Interface

Das Web-UI-Package (packages/web-ui/src/index.ts:1-119) exportiert eine umfassende React-Komponentenbibliothek:

Hauptkomponenten:

KomponenteBeschreibung
ChatPanelHaupt-Chat-Interface
MessageListNachrichtenliste mit Virtualisierung
AgentInterfaceContainer für Agent-Interaktion
StreamingMessageContainerStreaming-Nachrichten-Anzeige

Storage-Backends:

typescript
1export { AppStorage, getAppStorage, setAppStorage } from "./storage/app-storage.js";
2export { IndexedDBStorageBackend } from "./storage/backends/indexeddb-storage-backend.js";
3export { Store } from "./storage/store.js";

Tool-Renderer:

Das Web-UI bietet ein erweiterbares Tool-Renderer-System (packages/web-ui/src/index.ts:104-112):

typescript
1export { getToolRenderer, registerToolRenderer, renderTool, setShowJsonMode } from "./tools/index.js";
2export { BashRenderer } from "./tools/renderers/BashRenderer.js";
3export { CalculateRenderer } from "./tools/renderers/CalculateRenderer.js";
4export { DefaultRenderer } from "./tools/renderers/DefaultRenderer.js";

Slack Integration (Mom Package)

Das Mom-Package (packages/mom/src/main.ts:1-293) integriert den Coding-Agent mit Slack:

typescript
1const MOM_SLACK_APP_TOKEN = process.env.MOM_SLACK_APP_TOKEN;
2const MOM_SLACK_BOT_TOKEN = process.env.MOM_SLACK_BOT_TOKEN;

Handler-Implementierung:

Der MomHandler (packages/mom/src/main.ts:281-293) verwaltet Channel-spezifische Agent-Instanzen:

typescript
1const handler: MomHandler = {
2    isRunning(channelId: string): boolean {
3        const state = channelStates.get(channelId);
4        return state?.running ?? false;
5    },
6    
7    async handleStop(channelId: string, slack: SlackBot): Promise&lt;void&gt; {
8        const state = channelStates.get(channelId);
9        if (state?.running) {
10            state.stopRequested = true;
11            state.runner.abort();
12            // ...
13        }
14    }
15};

Message-Management:

Die createMomContext-Funktion (packages/mom/src/main.ts:135-274) implementiert Truncation für Slack-Limits:

typescript
1respond: async (text: string, shouldLog = true) => {
2    const MAX_MAIN_LENGTH = 35000;
3    const truncationNote = "\n\n_(message truncated, ask me to elaborate on specific parts)_";
4    if (accumulatedText.length > MAX_MAIN_LENGTH) {
5        accumulatedText = accumulatedText.substring(0, MAX_MAIN_LENGTH - truncationNote.length) + truncationNote;
6    }
7    // ...
8}

Datenfluss und Aufrufketten

Der folgende Sequenzdiagramm zeigt den vollständigen Datenfluss von einer Benutzereingabe bis zur Tool-Ausführung:

正在加载图表渲染器...

Datenfluss-Erklärung:

  1. Eingabe-Verarbeitung: Benutzer-Eingabe wird vom UI-Layer an AgentSession weitergeleitet
  2. Context-Transformation: Optionale Pruning oder Anreicherung des Contexts (packages/agent/src/agent.ts:52-54)
  3. Message-Konvertierung: Filterung auf LLM-kompatible Formate
  4. Streaming: LLM-Provider streamt Tokens zurück
  5. Tool-Execution: Bei Tool-Calls werden beforeToolCall, Execution und afterToolCall durchlaufen
  6. Event-Propagation: Ergebnisse werden als AgentEvent an UI zurückgesendet

Modulabhängigkeiten

Die Modulabhängigkeiten zeigen die hierarchische Struktur des Monorepos:

正在加载图表渲染器...

Abhängigkeitsregeln:

  • UI-Pakete (TUI, Web-UI, Mom) hängen vom Core ab, nie umgekehrt
  • Agent ist die einzige Abstraktion für LLM-Zugriff
  • AI Package kapselt alle Provider-spezifischen Implementierungen
  • Pods enthält nur Typ-Definitionen (packages/pods/src/index.ts:1-2)

Technologie-Stack und Design-Entscheidungen

TechnologieVerwendungszweckBegründungAlternativen
TypeScriptGesamter CodebaseTypsicherheit, bessere IDE-UnterstützungJavaScript, PureScript
Monorepo (pnpm workspaces)PaketorganisationCode-Sharing, einheitliche VersionierungPolyrepo, Lerna
ReactWeb-UI KomponentenGroße Ökosystem, Component-ReuseVue, Svelte, Solid
IndexedDBBrowser-SpeicherungPersistente Storage, große DatenmengenlocalStorage, SQLite WASM
Server-Sent EventsLLM-StreamingEinfache Implementierung, weite UnterstützungWebSockets, Long-Polling
APC-Escape-SequenzenTerminal-CursorZero-Width Marker, Terminal-unabhängigANSI-Cursor, NCurses
JSONLSession-DateienZeilenweises Parsing, Append-freundlichJSON, SQLite
Fuzzy MatchingModellauswahlBenutzerfreundlichkeit, Toleranz bei TippfehlernExact Match, Regex

Design-Entscheidungen

1. Agent als zentrale Abstraktion

Die Agent-Klasse wurde als einzige Schnittstelle zu LLM-Providern konzipiert. Dies ermöglicht:

  • Einheitliche Tool-Execution über alle Provider
  • Konsistente Error-Behandlung und Retry-Logik
  • Einfache Tests durch Mocking einer einzigen Schnittstelle

2. Session-Persistierung im JSONL-Format

Sessions werden als JSONL (JSON Lines) gespeichert (packages/coding-agent/src/core/session-manager.ts:503-542):

  • Vorteil: Zeilenweises Lesen/Schreiben ohne vollständiges Parsen
  • Vorteil: Append-Operationen für neue Messages ohne Datei-Rewrite
  • Nachteil: Keine direkte Indexierung (gelöst durch separate Metadaten)

3. Plugin-basiertes Extension-System

Extensions werden über Manifest-Einträge oder Index-Dateien entdeckt (packages/coding-agent/src/core/package-manager.ts:409-448):

  • Vorteil: Keine Registrierung zur Laufzeit erforderlich
  • Vorteil: Statische Analyse der Abhängigkeiten möglich
  • Nachteil: Erfordert Neustart für neue Extensions

4. Multi-Transport-Unterstützung

Der Agent unterstützt verschiedene Transport-Protokolle (packages/agent/src/agent.ts:94-96):

  • Ermöglicht Provider-spezifische Optimierungen
  • Unterstützt Proxy-Szenarien mit angepasstem Transport

5. Differenzielles Terminal-Rendering

Die TUI-Klasse (packages/tui/src/tui.ts:209-212) speichert vorherige Zeilen:

  • Minimiert Terminal-Updates
  • Reduziert Flackern bei schnellen Updates
  • Ermöglicht effiziente Cursor-Positionierung

Startup-Timings und Performance

Das timings-Modul (packages/coding-agent/src/core/timings.ts:9-25) bietet Performance-Monitoring:

typescript
1export function time(label: string): void {
2    if (!ENABLED) return;
3    const now = Date.now();
4    timings.push({ label, ms: now - lastTime });
5    lastTime = now;
6}
7
8export function printTimings(): void {
9    if (!ENABLED || timings.length === 0) return;
10    console.error("\n--- Startup Timings ---");
11    for (const t of timings) {
12        console.error(`  ${t.label}: ${t.ms}ms`);
13    }
14    console.error(`  TOTAL: ${timings.reduce((a, b) => a + b.ms, 0)}ms`);
15}

Verwendung im Main-Entry:

typescript
1// Aus packages/coding-agent/src/main.ts:28
2import { printTimings, time } from "./core/timings.js";

Die Timing-Funktion ist standardmäßig deaktiviert (ENABLED-Flag) und kann für Performance-Analyse aktiviert werden.