Add getApiKey hook to AgentLoopConfig that resolves API keys dynamically before each LLM call. This allows short-lived OAuth tokens (e.g. GitHub Copilot, Anthropic OAuth) to be refreshed between turns when tool execution takes a long time. Previously, the API key was resolved once when ProviderTransport.run() was called and passed as a static string to the agent loop. If the loop ran for longer than the token lifetime (e.g. 30 minutes for Copilot), subsequent LLM calls would fail with expired token errors. Changes: - Add getApiKey hook to AgentLoopConfig (packages/ai) - Call getApiKey before each LLM call in streamAssistantResponse - Update ProviderTransport to pass getApiKey instead of static apiKey - Update web-ui ProviderTransport with same pattern |
||
|---|---|---|
| .. | ||
| example | ||
| scripts | ||
| src | ||
| package.json | ||
| README.md | ||
| tsconfig.build.json | ||
| tsconfig.json | ||
@mariozechner/pi-web-ui
Reusable web UI components for building AI chat interfaces powered by @mariozechner/pi-ai.
Built with mini-lit web components and Tailwind CSS v4.
Features
- Modern Chat Interface - Complete chat UI with message history, streaming responses, and tool execution
- Tool Support - Built-in renderers for calculator, bash, time, and custom tools
- Attachments - PDF, Office documents, images with preview and text extraction
- Artifacts - HTML, SVG, Markdown, and text artifact rendering with sandboxed execution
- Pluggable Transports - Direct API calls or proxy server support
- Platform Agnostic - Works in browser extensions, web apps, VS Code extensions, Electron apps
- TypeScript - Full type safety with TypeScript
Installation
npm install @mariozechner/pi-web-ui
Quick Start
See the example directory for a complete working application.
import { Agent, ChatPanel, ProviderTransport, AppStorage,
SessionIndexedDBBackend, setAppStorage } from '@mariozechner/pi-web-ui';
import { getModel } from '@mariozechner/pi-ai';
import '@mariozechner/pi-web-ui/app.css';
// Set up storage
const storage = new AppStorage({
sessions: new SessionIndexedDBBackend('my-app-sessions'),
});
setAppStorage(storage);
// Create transport
const transport = new ProviderTransport();
// Create agent
const agent = new Agent({
initialState: {
systemPrompt: 'You are a helpful assistant.',
model: getModel('anthropic', 'claude-sonnet-4-5-20250929'),
thinkingLevel: 'off',
messages: [],
tools: [],
},
transport,
});
// Create chat panel and attach agent
const chatPanel = new ChatPanel();
await chatPanel.setAgent(agent);
document.body.appendChild(chatPanel);
Run the example:
cd example
npm install
npm run dev
Core Components
ChatPanel
The main chat interface component. Displays messages, handles input, and coordinates with the Agent.
import { ChatPanel, ApiKeyPromptDialog } from '@mariozechner/pi-web-ui';
const chatPanel = new ChatPanel();
// Optional: Handle API key prompts
chatPanel.onApiKeyRequired = async (provider: string) => {
return await ApiKeyPromptDialog.prompt(provider);
};
// Attach an agent
await chatPanel.setAgent(agent);
Agent
Core state manager that handles conversation state, tool execution, and streaming.
import { Agent, ProviderTransport } from '@mariozechner/pi-web-ui';
import { getModel } from '@mariozechner/pi-ai';
const agent = new Agent({
initialState: {
model: getModel('anthropic', 'claude-sonnet-4-5-20250929'),
systemPrompt: 'You are a helpful assistant.',
thinkingLevel: 'off',
messages: [],
tools: [],
},
transport: new ProviderTransport(),
});
// Subscribe to events
agent.subscribe((event) => {
if (event.type === 'state-update') {
console.log('Messages:', event.state.messages);
}
});
// Send a message
await agent.send('Hello!');
AgentInterface
Lower-level chat interface for custom implementations. Used internally by ChatPanel.
import { AgentInterface } from '@mariozechner/pi-web-ui';
const chat = new AgentInterface();
await chat.setAgent(agent);
Transports
Transport layers handle communication with AI providers.
ProviderTransport
The main transport that calls AI provider APIs using stored API keys.
import { ProviderTransport } from '@mariozechner/pi-web-ui';
const transport = new ProviderTransport();
const agent = new Agent({
initialState: { /* ... */ },
transport,
});
AppTransport
Alternative transport for proxying requests through a custom server.
import { AppTransport } from '@mariozechner/pi-web-ui';
const transport = new AppTransport();
const agent = new Agent({
initialState: { /* ... */ },
transport,
});
Tool Renderers
Customize how tool calls and results are displayed.
import { registerToolRenderer, type ToolRenderer } from '@mariozechner/pi-web-ui';
import { html } from '@mariozechner/mini-lit';
const myRenderer: ToolRenderer = {
renderParams(params, isStreaming) {
return html`<div>Calling tool with: ${JSON.stringify(params)}</div>`;
},
renderResult(params, result) {
return html`<div>Result: ${result.output}</div>`;
}
};
registerToolRenderer('my_tool', myRenderer);
Storage
The package provides flexible storage backends for API keys, settings, and session persistence.
AppStorage
Central storage configuration for the application.
import { AppStorage, setAppStorage, SessionIndexedDBBackend } from '@mariozechner/pi-web-ui';
const storage = new AppStorage({
sessions: new SessionIndexedDBBackend('my-app-sessions'),
});
setAppStorage(storage);
Available Backends
LocalStorageBackend- Uses browser localStorageIndexedDBBackend- Uses IndexedDB for larger dataSessionIndexedDBBackend- Specialized for session storageWebExtensionStorageBackend- For browser extensions using chrome.storage API
Session Management
import { getAppStorage } from '@mariozechner/pi-web-ui';
const storage = getAppStorage();
// Save session
await storage.sessions?.saveSession(sessionId, agentState, undefined, title);
// Load session
const sessionData = await storage.sessions?.loadSession(sessionId);
// List sessions
const sessions = await storage.sessions?.listSessions();
Styling
The package includes pre-built Tailwind CSS with the Claude theme:
import '@mariozechner/pi-web-ui/app.css';
Or customize with your own Tailwind config:
@import '@mariozechner/mini-lit/themes/claude.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
Dialogs
The package includes several dialog components for common interactions.
SettingsDialog
Settings dialog with tabbed interface for API keys, proxy configuration, etc.
import { SettingsDialog, ApiKeysTab, ProxyTab } from '@mariozechner/pi-web-ui';
// Open settings with tabs
SettingsDialog.open([new ApiKeysTab(), new ProxyTab()]);
SessionListDialog
Display and load saved sessions.
import { SessionListDialog } from '@mariozechner/pi-web-ui';
SessionListDialog.open(async (sessionId) => {
await loadSession(sessionId);
});
ApiKeyPromptDialog
Prompt user for API key when needed.
import { ApiKeyPromptDialog } from '@mariozechner/pi-web-ui';
const apiKey = await ApiKeyPromptDialog.prompt('anthropic');
PersistentStorageDialog
Request persistent storage permission.
import { PersistentStorageDialog } from '@mariozechner/pi-web-ui';
await PersistentStorageDialog.request();
Platform Integration
Browser Extension
import { AppStorage, WebExtensionStorageBackend, Agent, ProviderTransport } from '@mariozechner/pi-web-ui';
const storage = new AppStorage({
providerKeys: new WebExtensionStorageBackend(),
settings: new WebExtensionStorageBackend(),
});
setAppStorage(storage);
Web Application
import { AppStorage, SessionIndexedDBBackend, setAppStorage } from '@mariozechner/pi-web-ui';
const storage = new AppStorage({
sessions: new SessionIndexedDBBackend('my-app-sessions'),
});
setAppStorage(storage);
Examples
- example/ - Complete web application with session management
- sitegeist - Browser extension for AI-powered web navigation
API Reference
See src/index.ts for the full public API.
Known Bugs
- PersistentStorageDialog: Currently broken and commented out in examples. The dialog for requesting persistent storage does not work correctly and needs to be fixed.
License
MIT