Assistant Frame API

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 tool
addInstruction(instruction)

Adds a system instruction and returns a handle.

const handle = registry.addInstruction("Be concise.");
handle.update("Be detailed."); // Update instruction
handle.remove(); // Remove instruction
addProvider(provider)

Adds another model context provider.

const handle = registry.addProvider(anotherProvider);
handle.remove(); // Remove provider

Use 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`,
    };
  },
});