@molecule/app-ai-assistant
Core interface · ai-assistant · App (browser) · v1.0.2 · Apache-2.0
AI assistant panel core interface — headless contextual side-panel state: streaming chat via your backend, context items, suggestions, session persistence.
npm install @molecule/app-ai-assistantHow it works
@molecule/app-ai-assistant is the ai-assistant core interface on the app (browser) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 1 provider: @molecule/app-ai-assistant-default.
import { requireProvider, setProvider } from '@molecule/app-ai-assistant'
import { createProvider } from '@molecule/app-ai-assistant-default'
setProvider(createProvider()) // at startup; same-origin base URL by default
const assistant = requireProvider()
const config = { endpoint: '/api/assistant' }
const unsubscribe = assistant.subscribe((state) => renderPanel(state))
assistant.setContext([{ type: 'page', label: 'Invoices', value: '/invoices' }])
await assistant.sendMessage('Why is this invoice overdue?', config, (event) => {
if (event.type === 'error') showError(event.message)
})Providers (1): @molecule/app-ai-assistant-default
Works with: @molecule/app-bond
Live demos
Running apps built with Molecule that install this package — open the app or study the template.
Reference
AI assistant panel core interface for molecule.dev.
Defines the AIAssistantProvider contract for a contextual AI side-panel:
panel open/close state, streaming chat (sendMessage), context items
(setContext — what the user is currently looking at), quick-action
suggestions, and session persistence. HEADLESS: the provider manages state
and streaming only — your app renders the panel UI from getState() /
subscribe().
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { requireProvider, setProvider } from '@molecule/app-ai-assistant'
import { createProvider } from '@molecule/app-ai-assistant-default'
setProvider(createProvider()) // at startup; same-origin base URL by default
const assistant = requireProvider()
const config = { endpoint: '/api/assistant' }
const unsubscribe = assistant.subscribe((state) => renderPanel(state))
assistant.setContext([{ type: 'page', label: 'Invoices', value: '/invoices' }])
await assistant.sendMessage('Why is this invoice overdue?', config, (event) => {
if (event.type === 'error') showError(event.message)
})
Type
core
Installation
npm install @molecule/app-ai-assistant @molecule/app-bond
API
Interfaces
AssistantMessage
A single message in the assistant conversation.
interface AssistantMessage {
/** Unique message identifier. */
id: string
/** Who sent the message. */
role: 'user' | 'assistant' | 'system'
/** Text content of the message. */
content: string
/** Unix timestamp in milliseconds. */
timestamp: number
/** Whether the message is currently being streamed. */
isStreaming?: boolean
/** Whether the stream was aborted before completion. */
aborted?: boolean
}
AssistantContext
A contextual item describing what the user is currently looking at. Providers use context to enrich prompts with relevant information.
interface AssistantContext {
/** Context category (e.g. 'page', 'file', 'selection', 'error'). */
type: string
/** Human-readable label for the context item. */
label: string
/** The actual context value (path, code snippet, URL, etc.). */
value: string
/** Additional metadata for the context item. */
metadata?: Record<string, unknown>
}
AssistantSuggestion
A suggested action the user can take. Displayed as quick-action chips in the assistant panel.
interface AssistantSuggestion {
/** Unique suggestion identifier. */
id: string
/** Short label displayed on the chip. */
label: string
/** Optional longer description shown on hover. */
description?: string
/** The message to send when the suggestion is activated. */
action: string
/** Optional icon identifier. */
icon?: string
}
AssistantPanelState
Snapshot of the assistant panel state.
interface AssistantPanelState {
/** Whether the panel is currently visible. */
isOpen: boolean
/** Panel position. */
position: AssistantPanelPosition
/** Conversation messages. */
messages: AssistantMessage[]
/** Whether a response is currently streaming. */
isLoading: boolean
/** Current error message, if any. */
error: string | null
/** Current suggestions to display. */
suggestions: AssistantSuggestion[]
/** Active context items. */
context: AssistantContext[]
}
AIAssistantConfig
Configuration for the AI assistant provider.
interface AIAssistantConfig {
/** API endpoint for sending messages. */
endpoint: string
/** Panel position. Defaults to 'right'. */
position?: AssistantPanelPosition
/** System-level context string prepended to conversations. */
systemContext?: string
/** Initial suggestions to display before the first message. */
suggestions?: AssistantSuggestion[]
/** Maximum number of messages to retain in the conversation. */
maxMessages?: number
/** Whether to persist the session across page reloads. Defaults to true. */
persistSession?: boolean
/** Session key for storage. Defaults to 'mol-assistant'. */
sessionKey?: string
/** Custom HTTP headers for API requests. */
headers?: Record<string, string>
}
AIAssistantProvider
AI assistant provider interface.
Implementations manage panel lifecycle, messaging, context, and state.
Bond packages implement this interface and register via
setProvider() during app initialization.
interface AIAssistantProvider {
/** Provider name identifier. */
readonly name: string
/** Open the assistant panel. */
open(config: AIAssistantConfig): void
/** Close the assistant panel. */
close(): void
/** Toggle the assistant panel open/closed. */
toggle(config: AIAssistantConfig): void
/**
* Send a message and stream the response.
*
* @param message - The user's message text
* @param config - Assistant configuration
* @param onEvent - Callback for stream events
* @returns Promise that resolves when the stream completes
*/
sendMessage(
message: string,
config: AIAssistantConfig,
onEvent: AssistantEventHandler,
): Promise<void>
/** Abort the current streaming response. */
abort(): void
/**
* Set the current context items.
* Context is included in subsequent messages to provide relevance.
*
* @param context - Array of context items
*/
setContext(context: AssistantContext[]): void
/** Clear all context items. */
clearContext(): void
/**
* Clear the conversation history.
*
* @param config - Assistant configuration
*/
clearHistory(config: AIAssistantConfig): Promise<void>
/**
* Load the conversation history from the backend.
*
* @param config - Assistant configuration
* @returns Array of past messages
*/
loadHistory(config: AIAssistantConfig): Promise<AssistantMessage[]>
/** Get the current panel state snapshot. */
getState(): AssistantPanelState
/**
* Subscribe to panel state changes.
*
* @param listener - Callback invoked on every state change
* @returns Unsubscribe function
*/
subscribe(listener: AssistantStateListener): () => void
}
Types
AssistantPanelPosition
Position of the assistant panel relative to the viewport.
type AssistantPanelPosition = 'right' | 'left' | 'bottom' | 'floating'
AssistantStreamEvent
Stream events emitted during an assistant response.
type AssistantStreamEvent =
| { type: 'text'; content: string }
| { type: 'thinking'; content: string }
| { type: 'suggestion'; suggestions: AssistantSuggestion[] }
| { type: 'done'; usage?: { inputTokens: number; outputTokens: number } }
| { type: 'error'; message: string }
AssistantEventHandler
Callback for receiving stream events.
type AssistantEventHandler = (event: AssistantStreamEvent) => void
AssistantStateListener
Listener for panel state changes.
type AssistantStateListener = (state: AssistantPanelState) => void
Functions
setProvider(provider)
Register the active AI assistant provider.
function setProvider(provider: AIAssistantProvider): void
provider— The provider instance to register
getProvider()
Get the active AI assistant provider, or null if none is registered.
function getProvider(): AIAssistantProvider | null
Returns: The current provider or null
hasProvider()
Check whether an AI assistant provider has been registered.
function hasProvider(): boolean
Returns: True if a provider is available
requireProvider()
Get the active AI assistant provider, throwing if none is registered.
function requireProvider(): AIAssistantProvider
Returns: The current provider
Available Providers
| Provider | Package |
|---|---|
| Ai Assistant | @molecule/app-ai-assistant-default |
Injection Notes
Requirements
Peer dependencies:
@molecule/app-bond^1.0.1
Runtime Dependencies
-
@molecule/app-bond -
Wire it with THIS package's
setProvider()orbond('ai-assistant', …).setProvider()delegates into the shared@molecule/app-bondregistry, so both write the same slot;requireProvider()throws until one has run. -
Messages go through YOUR backend, never an AI vendor directly. The bundled bond (
@molecule/app-ai-assistant-default) POSTs{ message, systemContext?, context? }toconfig.endpointand reads an SSE stream ofdata: <AssistantStreamEvent JSON>lines (text/thinking/suggestion/done/error);clearHistorysends DELETE andloadHistoryGETs the same endpoint. Your API implements that route and holds the AI provider key server-side (see@molecule/api-ai) — the browser never holds a vendor key. -
Render model output through a sanitizing markdown renderer — never inject it as raw HTML.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- Opening the panel (
open/toggle→getState().isOpenis true) and sending a message throughsendMessagerenders the user turn immediately, then the assistant reply below it — both present ingetState().messages. - The reply STREAMS:
textevents append the assistant message progressively (token by token) while itsisStreamingstays true, andisStreamingclears when thedoneevent lands — not one atomic blob at the end. - A stop/cancel control mid-stream calls
abort()and actually halts the reply: the message stops growing and is markedaborted, not left spinning. - A thinking/loading indicator driven by
getState().isLoadingshows while a response is in flight and clears once it settles — ondoneAND onabort. - A provider failure surfaces
getState().error(from theerrorstream event) as a visible message in the panel — never a blank or perpetually spinning panel. - Suggestions from
getState().suggestionsrender as chips, and clicking one sends THAT chip's ownactionstring — the suggestion's wired message, not a generic prompt. - Context set via
setContext(the selected code / current page) is actually attached to the request so the answer is context-aware;clearContextdrops it, and one user's context never bleeds into another user's session. - Conversation history persists across turns and reloads —
loadHistoryrehydratesgetState().messagesandclearHistoryempties the panel — and model output renders through the app's sanitizing markdown renderer, never as raw or executable HTML.