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
- Add agentLoopContinue() to pi-ai for resuming from existing context
- Add Agent.continue() method and transport.continue() interface
- Simplify AgentSession compaction to two cases: overflow (auto-retry) and threshold (no retry)
- Remove proactive mid-turn compaction abort
- Merge turn prefix summary into main summary
- Add isCompacting property to AgentSession and RPC state
- Block input during compaction in interactive mode
- Show compaction count on session resume
- Rename RPC.md to rpc.md for consistency
Related to #128
- Added totalTokens field to Usage interface in pi-ai
- Anthropic: computed as input + output + cacheRead + cacheWrite
- OpenAI/Google: uses native total_tokens/totalTokenCount
- Fixed openai-completions to compute totalTokens when reasoning tokens present
- Updated calculateContextTokens() to use totalTokens field
- Added comprehensive test covering 13 providers
fixes#130
- Move lit from dependencies to peerDependencies in web-ui to prevent multiple Lit instances being loaded
- Update mini-lit from 0.1.8/0.1.10 to 0.2.0 in root package.json and web-ui/package.json
- Install @tailwindcss/vite in web-ui/example
- Run biome formatting fixes across codebase
This resolves HMR custom element re-registration errors caused by duplicate Lit registries.
Tool results now use content blocks and can include both text and images.
All providers (Anthropic, Google, OpenAI Completions, OpenAI Responses)
correctly pass images from tool results to LLMs.
- Update ToolResultMessage type to use content blocks
- Add placeholder text for image-only tool results in Google/Anthropic
- OpenAI providers send tool result + follow-up user message with images
- Fix Anthropic JSON parsing for empty tool arguments
- Add comprehensive tests for image-only and text+image tool results
- Update README with tool result content blocks API
- Add collapsible thinking blocks with shimmer animation during streaming
- Update user messages to use orange gradient pill styling (matching sitegeist)
- Fix cost display to only show for completed messages, not while streaming
- Update tool renderers to use ChevronsUpDown/ChevronUp icons instead of rotating ChevronRight
- Export ThinkingBlock component from public API
- Add inputChanged state to track when user modifies input after save
- Remove Clear button and removeKey() method entirely
- Save button now disables after successful save until input changes
- Save button stays enabled on test failure (allows immediate retry)
- Keep input field showing stars (••••) after successful save instead of clearing
This provides clearer feedback and prevents accidental key deletion.
Adds a reload button with RefreshCw icon to HTML artifact header buttons.
Clicking the button clears logs and re-executes the HTML content, useful
for manually refreshing when developing HTML artifacts or testing changes.
Changes:
- Import Button, icon from mini-lit and RefreshCw from lucide
- Add reload button to getHeaderButtons() that clears logs and calls executeContent()
- Add "Reload HTML" i18n key in English and German translations
HTML artifacts can read other artifacts via getArtifact() and attachments.
When any artifact is created, updated, rewritten, or deleted, all HTML
artifacts now automatically reload to reflect the latest data.
Changes:
- Add reloadAllHtmlArtifacts() method to ArtifactsPanel
- Call reload after all artifact mutations (create/update/rewrite/delete)
- Make HtmlArtifact.sandboxIframeRef and executeContent() public
- Update runtime providers before re-executing HTML content
This ensures HTML artifacts always display current data from their dependencies.
Issue:
Each browser_javascript execution wrapped console methods, but captured
the current (already wrapped) console as "original". This created a chain
of wrappers that accumulated across executions:
- Execution 1: 1x console.log (wrapper1 → real console)
- Execution 2: 2x console.log (wrapper2 → wrapper1 → real console)
- Execution 3: 3x console.log (wrapper3 → wrapper2 → wrapper1 → real console)
- Execution 4: 4x console.log (and so on...)
Fix:
Store the truly original console methods in window.__originalConsole on
first wrap only. All subsequent executions use these stored original methods
instead of capturing the current console. This prevents wrapper accumulation.
Changes:
- Check if window.__originalConsole exists before wrapping
- Store original console methods with .bind() to preserve context
- Always use window.__originalConsole for local logging
- Now each execution logs exactly 1x regardless of execution count
- Add handlePaste method to detect and process pasted images
- Automatically attach pasted images as attachments
- Support multiple images in single paste
- Prevent default paste behavior for images (text paste still works normally)
- Use same loadAttachment utility as drag-and-drop for consistency
- Export UserMessageWithAttachments type from web-ui index
- Remove unused i18n import from extract-document tool
- Update models.generated.ts (model order changes from API)
This fixes type errors when sitegeist uses custom user message renderer
- Add extract_document tool for extracting text from PDF/DOCX/XLSX/PPTX from URLs
- CORS proxy support from settings for fetching documents
- Proper error messages guiding users on CORS issues and manual file attachment
- Add scroll-into-view for active artifact tabs
- Export extract_document tool from web-ui package
When downloading HTML artifacts, the generated HTML now works standalone
without requiring the extension runtime.
Changes:
- Add PrepareHtmlOptions config object to SandboxedIframe.prepareHtmlDocument()
- Add isStandalone flag to skip runtime bridge and navigation interceptor
- When isStandalone=true:
- window.sendRuntimeMessage is NOT defined
- Navigation interceptor is NOT injected
- Artifact runtime providers fall back to embedded data
- HtmlArtifact download button now uses isStandalone: true
This fixes the issue where downloaded HTML artifacts would:
- Try to call window.sendRuntimeMessage (which would fail silently)
- Try to postMessage to non-existent parent window
- Not work when opened via file:// protocol
Now downloaded artifacts work completely standalone with embedded data.
The negated approach broke browser_script which runs in page context.
browser_script injects into web pages (http://, https://) where
chrome.runtime.sendMessage doesn't work. The original whitelist
approach correctly identifies extension contexts only.
The real issue with artifacts in sandbox iframes needs a different fix.
1. Skip JavaScript validation for <script type="module"> tags
- new Function() cannot validate ES module syntax (import/export)
- Module scripts now execute without validation errors
- Fixes issue where HTML artifacts with modules would fail to load
2. Improve __isExtensionContext detection logic
- Changed from whitelist (chrome-extension://, moz-extension://)
- To blacklist approach: negate http://, https://, file://
- More robust - handles all extension protocols (about:srcdoc, etc.)
- Fixes "offline mode" errors when using browser_script artifacts
Resolves artifacts not working in extension context after document.write()
- Add navigation interceptor script that prevents all iframe navigation
- Intercept link clicks, form submissions, and window.location changes
- Send open-external-url messages to parent window
- Use chrome.tabs.create() in extension context, window.open() fallback
- Fix font-size workaround to use 'initial' instead of hardcoded 16px
- Document Chrome extension font-size bug with Stack Overflow reference
This prevents HTML artifacts from navigating away when users click links,
which would break the sandbox message system. All links now open in new
Chrome tabs instead.
Set explicit font-size: 16px on the <html> element in sandboxed iframes
to prevent browser default inheritance quirks that can cause unexpected
font sizes (e.g., 75% shown in devtools).
This establishes a consistent base font size while still allowing user
HTML content to override it on body or specific elements as needed.
Fixes the issue where HTML artifacts displayed with inconsistent font
sizes due to iframe inheritance behavior.
Replace window.sendRuntimeMessage existence check with URL-based detection.
The sendRuntimeMessage function exists in both extension and non-extension
contexts (e.g., downloaded HTML artifacts), causing incorrect behavior.
Changes:
- Add window.__isExtensionContext() helper to RuntimeMessageBridge that checks:
- chrome-extension:// URLs (Chrome)
- moz-extension:// URLs (Firefox)
- about:srcdoc (sandbox iframes)
- Update all runtime providers to use __isExtensionContext() instead:
- FileDownloadRuntimeProvider: Correctly falls back to browser download
- ConsoleRuntimeProvider: Only sends messages in extension context
- ArtifactsRuntimeProvider: Properly detects offline/read-only mode
This fixes the issue where downloaded HTML artifacts incorrectly try to
communicate with the extension when opened from disk.
- Add getAllFromIndex to StorageBackend interface for index-based queries
- Implement getAllFromIndex in IndexedDBStorageBackend using cursor API
- Update SessionsStore.getAllMetadata to use lastModified index
- Enables database-native sorting instead of fetching all keys and sorting in JS
Benefits:
- Much faster for large datasets (uses IndexedDB cursor with direction)
- Reduces memory usage (no need to load all keys first)
- Leverages existing indices for optimal performance
- Changed ToolRenderer return type from TemplateResult to ToolRenderResult
- ToolRenderResult = { content: TemplateResult, isCustom: boolean }
- isCustom: true = no card wrapper, false = wrap in card
- Updated all existing tool renderers to return new format
- Updated Messages.ts to handle custom rendering
This enables tools to render without default card chrome when needed.
Make these properties public so they can be accessed externally for
test automation and other programmatic control.
Changes:
- Change agent, agentInterface, artifactsPanel from private to public
Clean up and restructure JavaScript REPL description following consistent pattern:
- Purpose section
- When to Use section
- Environment section
- Common Libraries section
- Important Notes section
- Example section
Removed buildJavaScriptReplDescription() function - no longer dynamically
injecting runtime provider docs into tool description (will move to system prompt).
Changes:
- Replace JAVASCRIPT_REPL_BASE_DESCRIPTION with JAVASCRIPT_REPL_DESCRIPTION
- Remove JAVASCRIPT_REPL_CHART_EXAMPLE and JAVASCRIPT_REPL_FOOTER
- Remove buildJavaScriptReplDescription() function
- Update javascript-repl tool to use static description
- Simpler, more scannable structure with clear hierarchy
Add clear decision tree and anti-patterns to prevent LLMs from using
get + rewrite when they should use update for targeted edits.
Changes:
- Add CRITICAL workflow section at top with decision tree
- Emphasize 'update' as PREFERRED for edits (token efficient)
- Mark 'rewrite' as LAST RESORT only
- Add ANTI-PATTERNS section showing wrong approaches
- Clarify use cases for each command
- Add examples emphasizing surgical modifications
Make it clear that runtime functions are ONLY for programmatically generated
content (by code), not for content the LLM authors directly.
Changes:
- Add WHEN TO USE and DO NOT USE sections to runtime provider description
- Emphasize that createOrUpdateArtifact is for code-generated content
- Point users to artifacts tool for LLM-authored content like summaries
LLMs don't need to check existence - they can just list all artifacts.
Simpler API that returns all filenames at once.
Changes:
- Replace hasArtifact(filename) with listArtifacts() returning string[]
- Add 'list' action handler that returns all artifact keys
- Update examples in prompt to use listArtifacts()
LLMs get confused about when to use create vs update. The single function
automatically detects if the artifact exists and chooses the right operation.
Changes:
- Replace createArtifact/updateArtifact with createOrUpdateArtifact in runtime
- Update handler to check existence and use appropriate command (create/rewrite)
- Simplify prompt documentation and examples
When abort happens during tool call streaming, the tool result should show as aborted.
Previously used global isStreaming state which would flip when new messages streamed in,
causing spinner to reappear incorrectly.
Changes:
- Use message.stopReason === "aborted" to detect aborted tool calls
- Create synthetic error result for aborted tool calls in ToolMessage component
- Fix Ollama provider key test to return true (can't know which model to test)
- Add newline before HTML execution logs in artifacts update
- Inject <script>if (window.complete) window.complete();</script> before </html>
- HTML artifacts are long-running and don't time out
- Console logs are sent immediately, completion signals when page is ready
- Removes need for artificial timeout in HTML artifacts
- Take artifactsPanel and agent directly instead of 5 separate function parameters
- Define minimal ArtifactsPanelLike and AgentLike interfaces to avoid circular deps
- Update all call sites (ChatPanel, browser-javascript) to use simplified constructor
- Much cleaner and easier to use
- Throw error instead of returning output when result.success is false
- Ensures tool call is marked as failed when code execution fails or is aborted
- Matches browser-javascript error handling pattern
Console Logging Improvements:
- Changed ConsoleRuntimeProvider to send logs immediately instead of batching
- Track pending send promises and await them in onCompleted callback
- Ensures REPL gets all logs before execution-complete
- Enables real-time console logging for HTML artifacts
Message Routing Fixes:
- Remove "handled" concept from message routing - broadcast all messages to all providers/consumers
- Change handleMessage return type from Promise<boolean> to Promise<void>
- Add debug logging to RuntimeMessageRouter to trace message flow
- Fix duplicate error logging (window error handler now only tracks errors, doesn't log them)
Output Formatting Consistency:
- Remove [LOG], [ERROR] prefixes from console output in both tools
- Show console logs before error messages
- Use "=> value" format for return values in both javascript-repl and browser-javascript
- Remove duplicate "Error:" prefix and extra formatting
Bug Fixes:
- Fix race condition where execution-complete arrived before console logs
- Fix ConsoleRuntimeProvider blocking execution-complete from reaching consumers
- Remove duplicate console log collection from SandboxedIframe
- Fix return value capture by wrapping user code in async function
- console.log('Reporting execution error:', finalError) was logging the error
- This caused duplicate error message in output
- Removed all debug console.log statements from window.complete()
- Error is only shown via execution-error message now
- javascript-repl now displays return values with => prefix
- Objects are JSON.stringify'd with formatting
- Removed console.error from wrapper to prevent duplicate error messages
- Error is already captured and sent via execution-error message
- Fixes missing return value display and duplicate error output
- Return value now passed to window.complete(error, returnValue)
- execution-complete message includes returnValue field
- SandboxResult interface updated to include returnValue
- executionConsumer passes returnValue in resolved promise
- Return values properly captured and available to callers
- User code with return statement was exiting the async IIFE early
- Completion callbacks and window.complete() were never reached
- Now wrap user code in userCodeFunc to capture return value
- Return statement returns from userCodeFunc, not outer IIFE
- Completion callbacks and window.complete() always execute
- Return value is logged to console output
- Fixes 30-second timeout when using return statements in REPL
- Router was stopping propagation after first handler returned true
- This prevented consumers from seeing messages that providers handled
- executionConsumer never received execution-complete because ConsoleRuntimeProvider handled it first
- Now all providers and consumers receive all messages
- Fixes javascript_repl never completing