Share model context across iframe boundaries
The Assistant Frame API enables iframes to provide model context (tools and instructions) to a parent window's assistant. Use it for embedded applications, plugins, or sandboxed components that contribute capabilities to the main assistant.
Overview
The Assistant Frame system consists of two main components:
- AssistantFrameProvider: Runs inside the iframe and provides model context
- AssistantFrameHost: Runs in the parent window and consumes context from iframes
Basic Usage
In the iframe (Provider)
The iframe acts as a provider of model context using AssistantFrameProvider:
// iframe.tsx
import { AssistantFrameProvider } from "@assistant-ui/react";
import { ModelContextRegistry } from "@assistant-ui/react";
import { z } from "zod";
// Create a registry to manage your model context
const registry = new ModelContextRegistry();
// Expose the registry to the parent window
AssistantFrameProvider.addModelContextProvider(
registry,
"https://parent-app.com",
);
// Add tools that will be available to the parent assistant
registry.addTool({
toolName: "searchProducts",
description: "Search for products in the catalog",
parameters: z.object({
query: z.string(),
category: z.string().optional(),
}),
execute: async ({ query, category }) => {
// Tool implementation runs in the iframe
const results = await searchAPI(query, category);
return { products: results };
},
});
// Add system instructions
const instructionHandle = registry.addInstruction(
"You are a helpful assistant.",
);
// update the instruction
instructionHandle.update("You have access to a product catalog search tool.");In the parent window (Host)
The parent window connects to the iframe with useAssistantFrameHost and registers the resulting AssistantFrameHost as a model context provider:
// parent.tsx
import {
useAssistantFrameHost,
useAui,
type AssistantFrameHost,
} from "@assistant-ui/react";
import { useCallback, useRef } from "react";
function ParentComponent() {
const iframeRef = useRef<HTMLIFrameElement>(null);
const aui = useAui();
const register = useCallback(
(frameHost: AssistantFrameHost) => aui.modelContext.register(frameHost),
[aui],
);
// Connect to the iframe's model context
useAssistantFrameHost({
iframeRef,
targetOrigin: "https://trusted-iframe-domain.com",
register,
});
return (
<div>
<Thread /> {/* Your assistant-ui */}
<iframe
ref={iframeRef}
src="https://trusted-iframe-domain.com/embed"
title="Embedded App"
/>
</div>
);
}Advanced Usage
ModelContextRegistry
Use ModelContextRegistry to add, update, and remove model context at runtime:
const registry = new ModelContextRegistry();
// Add a tool with handle for updates
const toolHandle = registry.addTool({
toolName: "convertCurrency",
description: "Convert between currencies",
parameters: z.object({
amount: z.number(),
from: z.string(),
to: z.string(),
}),
execute: async ({ amount, from, to }) => {
const rate = await fetchExchangeRate(from, to);
return { result: amount * rate, currency: to };
},
});
// Update the tool later
toolHandle.update({
toolName: "convertCurrency",
description: "Convert between currencies with live rates", // Updated description
parameters: z.object({
amount: z.number(),
from: z.string(),
to: z.string(),
includesFees: z.boolean().optional(),
}),
execute: async ({ amount, from, to, includesFees }) => {
const rate = await fetchExchangeRate(from, to);
const fee = includesFees ? 0.02 : 0; // 2% fee
return {
result: amount * rate * (1 - fee),
currency: to,
fee: includesFees ? amount * rate * fee : 0,
};
},
});
// Remove the tool when no longer needed
toolHandle.remove();
// Add multiple instructions
const instruction1 = registry.addInstruction("Be helpful and concise.");
const instruction2 = registry.addInstruction("Use metric units.");
// Remove instructions
instruction1.remove();Multiple Providers
You can register multiple model context providers in the same iframe:
const catalogRegistry = new ModelContextRegistry();
const analyticsRegistry = new ModelContextRegistry();
// Add different tools to each registry
catalogRegistry.addTool({
/* ... */
});
analyticsRegistry.addTool({
/* ... */
});
// Register both providers
const unsubscribe1 =
AssistantFrameProvider.addModelContextProvider(
catalogRegistry,
"https://parent-app.com",
);
const unsubscribe2 =
AssistantFrameProvider.addModelContextProvider(
analyticsRegistry,
"https://parent-app.com",
);
// Later, unsubscribe if needed
unsubscribe1();
unsubscribe2();Security Considerations
Origin Validation
Both the provider and host validate message origins. They default to the current page's origin, so cross-origin frames must specify the exact trusted origin on both sides:
// In iframe - only accept messages from specific parent
AssistantFrameProvider.addModelContextProvider(
registry,
"https://parent-app.com",
);
// In parent - only accept messages from specific iframe
useAssistantFrameHost({
iframeRef,
targetOrigin: "https://iframe-app.com",
register,
});Tool Execution
Tools are executed in the iframe's context, keeping sensitive operations sandboxed:
registry.addTool({
toolName: "accessDatabase",
description: "Query the database",
parameters: z.object({ query: z.string() }),
execute: async ({ query }) => {
// This runs in the iframe with iframe's permissions
// Parent cannot directly access the database
const results = await db.query(query);
return results;
},
});API Reference
AssistantFrameProvider
Static class that manages model context providers in an iframe.
Methods
addModelContextProvider(provider, targetOrigin?)
Registers a model context provider to share with parent windows.
const unsubscribe = AssistantFrameProvider.addModelContextProvider(
registry,
"https://parent-domain.com",
);When targetOrigin is omitted, the provider only communicates with window.location.origin. All providers in a frame share one origin policy. Registering an explicit origin tightens an existing wildcard policy, while registering two different explicit origins throws an error. Unsubscribing drops that registration's requirement. The wildcard value * is available as an explicit opt-in, but it should not be used for contexts that expose tools or sensitive instructions.
dispose()
Cleans up all resources and removes all providers. The parent host is told that the frame no longer provides any model context and rejects tool calls still waiting on the frame.
AssistantFrameProvider.dispose();AssistantFrameHost
Class that connects to an iframe's model context providers.
Constructor
const host = new AssistantFrameHost(
iframeWindow,
targetOrigin?, // Defaults to window.location.origin
);Methods
getModelContext()
Returns the current merged model context from the iframe.
const context = host.getModelContext();
// { system: "...", tools: { ... } }subscribe(callback)
Subscribes to model context changes.
const unsubscribe = host.subscribe(() => {
console.log("Context updated:", host.getModelContext());
});frameLoaded()
Call it on every load event of the iframe. The host asks the new page for its tools; if no provider answers within two seconds, it clears the previous page's tools and fails its pending tool calls. useAssistantFrameHost calls it for you.
iframe.addEventListener("load", () => host.frameLoaded());dispose()
Cleans up the connection to the iframe.
host.dispose();useAssistantFrameHost
React hook that manages the lifecycle of an AssistantFrameHost.
useAssistantFrameHost({
iframeRef: RefObject<HTMLIFrameElement>,
targetOrigin?: string, // Defaults to window.location.origin
register: (frameHost: AssistantFrameHost) => Unsubscribe,
});register is required. The hook calls it with the new AssistantFrameHost and calls the returned function on cleanup. Pass a stable function (for example from useCallback), because the hook reconnects whenever register changes.
After a reload, the host asks the frame for its tools. If no provider answers within two seconds, the host clears the previous page's tools and fails its pending tool calls.
ModelContextRegistry
A registry for model context that supports updates at runtime.
Methods
addTool(tool)
Adds a tool and returns a handle for updates/removal.
const handle = registry.addTool({
toolName: string,
description?: string,
parameters: ZodSchema | JSONSchema,
execute: (args, context) => Promise<any>,
});
handle.update(newTool); // Update the tool
handle.remove(); // Remove the tooladdInstruction(instruction)
Adds a system instruction and returns a handle.
const handle = registry.addInstruction("Be concise.");
handle.update("Be detailed."); // Update instruction
handle.remove(); // Remove instructionaddProvider(provider)
Adds another model context provider.
const handle = registry.addProvider(anotherProvider);
handle.remove(); // Remove providerUse Cases
Embedded Analytics Dashboard
An analytics iframe can provide data query tools to the parent assistant:
// In analytics iframe
registry.addTool({
toolName: "queryMetrics",
description: "Query analytics data",
parameters: z.object({
metric: z.string(),
timeRange: z.string(),
}),
execute: async ({ metric, timeRange }) => {
const data = await analyticsAPI.query(metric, timeRange);
return { data, visualization: createChart(data) };
},
});Plugin System
Third-party plugins can extend the assistant's capabilities:
// In plugin iframe
registry.addTool({
toolName: "translateText",
description: "Translate text to another language",
parameters: z.object({
text: z.string(),
targetLanguage: z.string(),
}),
execute: async ({ text, targetLanguage }) => {
return await pluginAPI.translate(text, targetLanguage);
},
});Data Visualization
Provide data visualization tools in an iframe:
// In visualization iframe
registry.addTool({
toolName: "createChart",
description: "Generate a chart from data",
parameters: z.object({
data: z.array(
z.object({
label: z.string(),
value: z.number(),
}),
),
chartType: z.enum(["bar", "line", "pie"]),
title: z.string().optional(),
}),
execute: async ({ data, chartType, title }) => {
// Generate chart using a library like Chart.js or D3
const chartUrl = await generateChart(data, chartType, title);
return {
chartUrl,
summary: `Created ${chartType} chart with ${data.length} data points`,
};
},
});