@assistant-ui/react 0.10.44 → 0.10.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/model-context/frame/AssistantFrameHost.d.ts +37 -0
  2. package/dist/model-context/frame/AssistantFrameHost.d.ts.map +1 -0
  3. package/dist/model-context/frame/AssistantFrameHost.js +151 -0
  4. package/dist/model-context/frame/AssistantFrameHost.js.map +1 -0
  5. package/dist/model-context/frame/AssistantFrameProvider.d.ts +41 -0
  6. package/dist/model-context/frame/AssistantFrameProvider.d.ts.map +1 -0
  7. package/dist/model-context/frame/AssistantFrameProvider.js +142 -0
  8. package/dist/model-context/frame/AssistantFrameProvider.js.map +1 -0
  9. package/dist/model-context/frame/AssistantFrameTypes.d.ts +29 -0
  10. package/dist/model-context/frame/AssistantFrameTypes.d.ts.map +1 -0
  11. package/dist/model-context/frame/AssistantFrameTypes.js +6 -0
  12. package/dist/model-context/frame/AssistantFrameTypes.js.map +1 -0
  13. package/dist/model-context/frame/index.d.ts +5 -0
  14. package/dist/model-context/frame/index.d.ts.map +1 -0
  15. package/dist/model-context/frame/index.js +6 -0
  16. package/dist/model-context/frame/index.js.map +1 -0
  17. package/dist/model-context/frame/useAssistantFrameHost.d.ts +28 -0
  18. package/dist/model-context/frame/useAssistantFrameHost.d.ts.map +1 -0
  19. package/dist/model-context/frame/useAssistantFrameHost.js +25 -0
  20. package/dist/model-context/frame/useAssistantFrameHost.js.map +1 -0
  21. package/dist/model-context/index.d.ts +2 -0
  22. package/dist/model-context/index.d.ts.map +1 -1
  23. package/dist/model-context/index.js +2 -0
  24. package/dist/model-context/index.js.map +1 -1
  25. package/dist/model-context/registry/ModelContextRegistry.d.ts +19 -0
  26. package/dist/model-context/registry/ModelContextRegistry.d.ts.map +1 -0
  27. package/dist/model-context/registry/ModelContextRegistry.js +117 -0
  28. package/dist/model-context/registry/ModelContextRegistry.js.map +1 -0
  29. package/dist/model-context/registry/ModelContextRegistryHandles.d.ts +14 -0
  30. package/dist/model-context/registry/ModelContextRegistryHandles.d.ts.map +1 -0
  31. package/dist/model-context/registry/ModelContextRegistryHandles.js +1 -0
  32. package/dist/model-context/registry/ModelContextRegistryHandles.js.map +1 -0
  33. package/dist/model-context/registry/index.d.ts +3 -0
  34. package/dist/model-context/registry/index.d.ts.map +1 -0
  35. package/dist/model-context/registry/index.js +4 -0
  36. package/dist/model-context/registry/index.js.map +1 -0
  37. package/dist/model-context/useAssistantInstructions.d.ts +1 -2
  38. package/dist/model-context/useAssistantInstructions.d.ts.map +1 -1
  39. package/dist/model-context/useAssistantInstructions.js.map +1 -1
  40. package/package.json +3 -3
  41. package/src/model-context/frame/AssistantFrame.test.ts +353 -0
  42. package/src/model-context/frame/AssistantFrameHost.ts +218 -0
  43. package/src/model-context/frame/AssistantFrameProvider.ts +225 -0
  44. package/src/model-context/frame/AssistantFrameTypes.ts +40 -0
  45. package/src/model-context/frame/SPEC_AssistantFrame.md +104 -0
  46. package/src/model-context/frame/index.ts +4 -0
  47. package/src/model-context/frame/useAssistantFrameHost.ts +48 -0
  48. package/src/model-context/index.ts +3 -0
  49. package/src/model-context/registry/ModelContextRegistry.ts +165 -0
  50. package/src/model-context/registry/ModelContextRegistryHandles.ts +19 -0
  51. package/src/model-context/registry/SPEC_ModelContextRegistry.md +40 -0
  52. package/src/model-context/registry/index.ts +2 -0
  53. package/src/model-context/useAssistantInstructions.tsx +1 -1
@@ -0,0 +1,218 @@
1
+ import {
2
+ ModelContextProvider,
3
+ ModelContext,
4
+ } from "../../model-context/ModelContextTypes";
5
+ import { Unsubscribe } from "../../types/Unsubscribe";
6
+ import { Tool } from "assistant-stream";
7
+ import {
8
+ FrameMessage,
9
+ FRAME_MESSAGE_CHANNEL,
10
+ SerializedModelContext,
11
+ SerializedTool,
12
+ } from "./AssistantFrameTypes";
13
+
14
+ /**
15
+ * Deserializes tools from JSON Schema format back to Tool objects
16
+ */
17
+ const deserializeTool = (serializedTool: SerializedTool): Tool<any, any> =>
18
+ ({
19
+ parameters: serializedTool.parameters,
20
+ ...(serializedTool.description && {
21
+ description: serializedTool.description,
22
+ }),
23
+ ...(serializedTool.disabled !== undefined && {
24
+ disabled: serializedTool.disabled,
25
+ }),
26
+ ...(serializedTool.type && { type: serializedTool.type }),
27
+ }) as Tool<any, any>;
28
+
29
+ /**
30
+ * Deserializes a ModelContext from transmission format
31
+ */
32
+ const deserializeModelContext = (
33
+ serialized: SerializedModelContext,
34
+ ): ModelContext => ({
35
+ ...(serialized.system !== undefined && { system: serialized.system }),
36
+ ...(serialized.tools && {
37
+ tools: Object.fromEntries(
38
+ Object.entries(serialized.tools).map(([name, tool]) => [
39
+ name,
40
+ deserializeTool(tool),
41
+ ]),
42
+ ),
43
+ }),
44
+ });
45
+
46
+ /**
47
+ * AssistantFrameHost - Runs in the parent window and acts as a ModelContextProvider
48
+ * that receives context from an iframe's AssistantFrameProvider.
49
+ *
50
+ * Usage example:
51
+ * ```typescript
52
+ * // In parent window
53
+ * const frameHost = new AssistantFrameHost(iframeWindow);
54
+ *
55
+ * // Register with assistant runtime
56
+ * const runtime = useAssistantRuntime();
57
+ * runtime.registerModelContextProvider(frameHost);
58
+ *
59
+ * // The assistant now has access to tools from the iframe
60
+ * ```
61
+ */
62
+ export class AssistantFrameHost implements ModelContextProvider {
63
+ private _context: ModelContext = {};
64
+ private _subscribers = new Set<() => void>();
65
+ private _pendingRequests = new Map<
66
+ string,
67
+ {
68
+ resolve: (value: any) => void;
69
+ reject: (error: any) => void;
70
+ }
71
+ >();
72
+ private _requestCounter = 0;
73
+ private _iframeWindow: Window;
74
+ private _targetOrigin: string;
75
+
76
+ constructor(iframeWindow: Window, targetOrigin: string = "*") {
77
+ this._iframeWindow = iframeWindow;
78
+ this._targetOrigin = targetOrigin;
79
+
80
+ this.handleMessage = this.handleMessage.bind(this);
81
+ window.addEventListener("message", this.handleMessage);
82
+
83
+ // Request initial context
84
+ this.requestContext();
85
+ }
86
+
87
+ private handleMessage(event: MessageEvent) {
88
+ // Security: Validate origin and source
89
+ if (this._targetOrigin !== "*" && event.origin !== this._targetOrigin)
90
+ return;
91
+ if (event.source !== this._iframeWindow) return;
92
+ if (event.data?.channel !== FRAME_MESSAGE_CHANNEL) return;
93
+
94
+ const message = event.data.message as FrameMessage;
95
+
96
+ switch (message.type) {
97
+ case "model-context-update": {
98
+ this.updateContext(message.context);
99
+ break;
100
+ }
101
+
102
+ case "tool-result": {
103
+ const pending = this._pendingRequests.get(message.id);
104
+ if (pending) {
105
+ if (message.error) {
106
+ pending.reject(new Error(message.error));
107
+ } else {
108
+ pending.resolve(message.result);
109
+ }
110
+ this._pendingRequests.delete(message.id);
111
+ }
112
+ break;
113
+ }
114
+ }
115
+ }
116
+
117
+ private updateContext(serializedContext: SerializedModelContext) {
118
+ const context = deserializeModelContext(serializedContext);
119
+ this._context = {
120
+ ...context,
121
+ tools:
122
+ context.tools &&
123
+ Object.fromEntries(
124
+ Object.entries(context.tools).map(([name, tool]) => [
125
+ name,
126
+ {
127
+ ...tool,
128
+ execute: (args: any) => this.callTool(name, args),
129
+ } as Tool<any, any>,
130
+ ]),
131
+ ),
132
+ };
133
+ this.notifySubscribers();
134
+ }
135
+
136
+ private callTool(toolName: string, args: any): Promise<any> {
137
+ return this.sendRequest(
138
+ {
139
+ type: "tool-call",
140
+ id: `tool-${this._requestCounter++}`,
141
+ toolName,
142
+ args,
143
+ },
144
+ 30000,
145
+ `Tool call "${toolName}" timed out`,
146
+ );
147
+ }
148
+
149
+ private sendRequest<T extends FrameMessage & { id: string }>(
150
+ message: T,
151
+ timeout = 30000,
152
+ timeoutMessage = "Request timed out",
153
+ ): Promise<any> {
154
+ return new Promise((resolve, reject) => {
155
+ this._pendingRequests.set(message.id, { resolve, reject });
156
+
157
+ this._iframeWindow.postMessage(
158
+ { channel: FRAME_MESSAGE_CHANNEL, message },
159
+ this._targetOrigin,
160
+ );
161
+
162
+ const timeoutId = setTimeout(() => {
163
+ const pending = this._pendingRequests.get(message.id);
164
+ if (pending) {
165
+ pending.reject(new Error(timeoutMessage));
166
+ this._pendingRequests.delete(message.id);
167
+ }
168
+ }, timeout);
169
+
170
+ // Store original resolve/reject with timeout cleanup
171
+ const originalResolve = this._pendingRequests.get(message.id)!.resolve;
172
+ const originalReject = this._pendingRequests.get(message.id)!.reject;
173
+
174
+ this._pendingRequests.set(message.id, {
175
+ resolve: (value: any) => {
176
+ clearTimeout(timeoutId);
177
+ originalResolve(value);
178
+ },
179
+ reject: (error: any) => {
180
+ clearTimeout(timeoutId);
181
+ originalReject(error);
182
+ },
183
+ });
184
+ });
185
+ }
186
+
187
+ private requestContext() {
188
+ // Request current context from iframe
189
+ this._iframeWindow.postMessage(
190
+ {
191
+ channel: FRAME_MESSAGE_CHANNEL,
192
+ message: {
193
+ type: "model-context-request",
194
+ } as FrameMessage,
195
+ },
196
+ this._targetOrigin,
197
+ );
198
+ }
199
+
200
+ private notifySubscribers() {
201
+ this._subscribers.forEach((callback) => callback());
202
+ }
203
+
204
+ getModelContext(): ModelContext {
205
+ return this._context;
206
+ }
207
+
208
+ subscribe(callback: () => void): Unsubscribe {
209
+ this._subscribers.add(callback);
210
+ return () => this._subscribers.delete(callback);
211
+ }
212
+
213
+ dispose() {
214
+ window.removeEventListener("message", this.handleMessage);
215
+ this._subscribers.clear();
216
+ this._pendingRequests.clear();
217
+ }
218
+ }
@@ -0,0 +1,225 @@
1
+ import {
2
+ ModelContextProvider,
3
+ ModelContext,
4
+ } from "../../model-context/ModelContextTypes";
5
+ import { Unsubscribe } from "../../types/Unsubscribe";
6
+ import { Tool } from "assistant-stream";
7
+ import { z } from "zod";
8
+ import {
9
+ FrameMessage,
10
+ FRAME_MESSAGE_CHANNEL,
11
+ SerializedModelContext,
12
+ SerializedTool,
13
+ } from "./AssistantFrameTypes";
14
+
15
+ /**
16
+ * Converts tools to JSON Schema format for serialization
17
+ */
18
+ const serializeTool = (tool: Tool<any, any>): SerializedTool => ({
19
+ ...(tool.description && { description: tool.description }),
20
+ parameters:
21
+ tool.parameters instanceof z.ZodType
22
+ ? ((z as any).toJSONSchema?.(tool.parameters) ?? tool.parameters)
23
+ : tool.parameters,
24
+ ...(tool.disabled !== undefined && { disabled: tool.disabled }),
25
+ ...(tool.type && { type: tool.type }),
26
+ });
27
+
28
+ /**
29
+ * Serializes a ModelContext for transmission across iframe boundary
30
+ */
31
+ const serializeModelContext = (
32
+ context: ModelContext,
33
+ ): SerializedModelContext => ({
34
+ ...(context.system !== undefined && { system: context.system }),
35
+ ...(context.tools && {
36
+ tools: Object.fromEntries(
37
+ Object.entries(context.tools).map(([name, tool]) => [
38
+ name,
39
+ serializeTool(tool),
40
+ ]),
41
+ ),
42
+ }),
43
+ });
44
+
45
+ /**
46
+ * AssistantFrameProvider - Runs inside an iframe and provides ModelContextProviders
47
+ * to the parent window's AssistantFrameHost.
48
+ *
49
+ * Usage example:
50
+ * ```typescript
51
+ * // Inside the iframe
52
+ * // Add model context providers
53
+ * const registry = new ModelContextRegistry();
54
+ * AssistantFrameProvider.addModelContextProvider(registry);
55
+ *
56
+ * // Add tools to registry
57
+ * registry.addTool({
58
+ * toolName: "search",
59
+ * description: "Search the web",
60
+ * parameters: z.object({ query: z.string() }),
61
+ * execute: async (args) => {
62
+ * // Tool implementation runs in iframe
63
+ * return { results: ["..."] };
64
+ * }
65
+ * });
66
+ * ```
67
+ */
68
+ export class AssistantFrameProvider {
69
+ private static _instance: AssistantFrameProvider | null = null;
70
+
71
+ private _providers = new Set<ModelContextProvider>();
72
+ private _providerUnsubscribes = new Map<
73
+ ModelContextProvider,
74
+ Unsubscribe | undefined
75
+ >();
76
+ private _targetOrigin: string;
77
+
78
+ private constructor(targetOrigin: string = "*") {
79
+ this._targetOrigin = targetOrigin;
80
+ this.handleMessage = this.handleMessage.bind(this);
81
+ window.addEventListener("message", this.handleMessage);
82
+
83
+ // Send initial update on initialization
84
+ setTimeout(() => this.broadcastUpdate(), 0);
85
+ }
86
+
87
+ private static getInstance(targetOrigin?: string): AssistantFrameProvider {
88
+ if (!AssistantFrameProvider._instance) {
89
+ AssistantFrameProvider._instance = new AssistantFrameProvider(
90
+ targetOrigin,
91
+ );
92
+ }
93
+ return AssistantFrameProvider._instance;
94
+ }
95
+
96
+ private handleMessage(event: MessageEvent) {
97
+ // Security: Validate origin if specified
98
+ if (this._targetOrigin !== "*" && event.origin !== this._targetOrigin)
99
+ return;
100
+ if (event.data?.channel !== FRAME_MESSAGE_CHANNEL) return;
101
+
102
+ const message = event.data.message as FrameMessage;
103
+
104
+ switch (message.type) {
105
+ case "model-context-request":
106
+ // Respond with current context
107
+ this.sendMessage(event, {
108
+ type: "model-context-update",
109
+ context: serializeModelContext(this.getModelContext()),
110
+ });
111
+ break;
112
+
113
+ case "tool-call":
114
+ this.handleToolCall(message, event);
115
+ break;
116
+ }
117
+ }
118
+
119
+ private async handleToolCall(
120
+ message: Extract<FrameMessage, { type: "tool-call" }>,
121
+ event: MessageEvent,
122
+ ) {
123
+ const tool = this.getModelContext().tools?.[message.toolName];
124
+
125
+ let result: any;
126
+ let error: string | undefined;
127
+
128
+ if (!tool) {
129
+ error = `Tool "${message.toolName}" not found`;
130
+ } else {
131
+ try {
132
+ result = tool.execute
133
+ ? await tool.execute(message.args, {
134
+ toolCallId: message.id,
135
+ abortSignal: new AbortController().signal,
136
+ })
137
+ : undefined;
138
+ } catch (e) {
139
+ error = e instanceof Error ? e.message : String(e);
140
+ }
141
+ }
142
+
143
+ this.sendMessage(event, {
144
+ type: "tool-result",
145
+ id: message.id,
146
+ ...(error ? { error } : { result }),
147
+ });
148
+ }
149
+
150
+ private sendMessage(event: MessageEvent, message: FrameMessage) {
151
+ event.source?.postMessage(
152
+ { channel: FRAME_MESSAGE_CHANNEL, message },
153
+ { targetOrigin: event.origin },
154
+ );
155
+ }
156
+
157
+ private getModelContext(): ModelContext {
158
+ const contexts = Array.from(this._providers).map((p) =>
159
+ p.getModelContext(),
160
+ );
161
+
162
+ return contexts.reduce(
163
+ (merged, context) => ({
164
+ system: context.system
165
+ ? merged.system
166
+ ? `${merged.system}\n\n${context.system}`
167
+ : context.system
168
+ : merged.system,
169
+ tools: { ...(merged.tools || {}), ...(context.tools || {}) },
170
+ }),
171
+ {} as ModelContext,
172
+ );
173
+ }
174
+
175
+ private broadcastUpdate() {
176
+ // Always broadcast to parent window
177
+ if (window.parent && window.parent !== window) {
178
+ const updateMessage: FrameMessage = {
179
+ type: "model-context-update",
180
+ context: serializeModelContext(this.getModelContext()),
181
+ };
182
+
183
+ window.parent.postMessage(
184
+ { channel: FRAME_MESSAGE_CHANNEL, message: updateMessage },
185
+ this._targetOrigin,
186
+ );
187
+ }
188
+ }
189
+
190
+ static addModelContextProvider(
191
+ provider: ModelContextProvider,
192
+ targetOrigin?: string,
193
+ ): Unsubscribe {
194
+ const instance = AssistantFrameProvider.getInstance(targetOrigin);
195
+ instance._providers.add(provider);
196
+
197
+ const unsubscribe = provider.subscribe?.(() => instance.broadcastUpdate());
198
+ if (unsubscribe) {
199
+ instance._providerUnsubscribes.set(provider, unsubscribe);
200
+ }
201
+
202
+ instance.broadcastUpdate();
203
+
204
+ return () => {
205
+ instance._providers.delete(provider);
206
+ instance._providerUnsubscribes.get(provider)?.();
207
+ instance._providerUnsubscribes.delete(provider);
208
+ instance.broadcastUpdate();
209
+ };
210
+ }
211
+
212
+ static dispose() {
213
+ if (AssistantFrameProvider._instance) {
214
+ const instance = AssistantFrameProvider._instance;
215
+ window.removeEventListener("message", instance.handleMessage);
216
+
217
+ // Unsubscribe from all providers
218
+ instance._providerUnsubscribes.forEach((unsubscribe) => unsubscribe?.());
219
+ instance._providerUnsubscribes.clear();
220
+ instance._providers.clear();
221
+
222
+ AssistantFrameProvider._instance = null;
223
+ }
224
+ }
225
+ }
@@ -0,0 +1,40 @@
1
+ export type SerializedTool = {
2
+ description?: string;
3
+ parameters: any; // JSON Schema
4
+ disabled?: boolean;
5
+ type?: string;
6
+ };
7
+
8
+ export type SerializedModelContext = {
9
+ system?: string;
10
+ tools?: Record<string, SerializedTool>;
11
+ };
12
+
13
+ export type FrameMessageType =
14
+ | "model-context-request"
15
+ | "model-context-update"
16
+ | "tool-call"
17
+ | "tool-result";
18
+
19
+ export type FrameMessage =
20
+ | {
21
+ type: "model-context-request";
22
+ }
23
+ | {
24
+ type: "model-context-update";
25
+ context: SerializedModelContext;
26
+ }
27
+ | {
28
+ type: "tool-call";
29
+ id: string;
30
+ toolName: string;
31
+ args: unknown;
32
+ }
33
+ | {
34
+ type: "tool-result";
35
+ id: string;
36
+ result?: unknown;
37
+ error?: string;
38
+ };
39
+
40
+ export const FRAME_MESSAGE_CHANNEL = "assistant-ui-frame";
@@ -0,0 +1,104 @@
1
+ ## Assistant Frames
2
+
3
+ Assistant frames allow an iframe to provide model context (tools, instructions) to a parent window's assistant.
4
+
5
+ ### Scope
6
+
7
+ Supported features are:
8
+
9
+ - ModelContextProvider API
10
+ - support for tools (defining tool name, description, parameters, execute)
11
+ - support for instructions (system instructions)
12
+
13
+ Out of scope for now:
14
+
15
+ - model configuration (temprature, etc.)
16
+ - ToolCallReader API (incremental reading support)
17
+
18
+ ### API design
19
+
20
+ [SPEC_ModelContextRegistry](../registry/SPEC_ModelContextRegistry.md)
21
+
22
+ ### Inside the iframe (provides context)
23
+
24
+ ```typescript
25
+ // Add model context providers
26
+ const registry = new ModelContextRegistry();
27
+ AssistantFrameProvider.addModelContextProvider(registry);
28
+
29
+ // Add tools/instructions to registry
30
+ registry.addTool({
31
+ toolName: "search",
32
+ description: "Search the web",
33
+ parameters: z.object({ query: z.string() }),
34
+ execute: async (args) => {
35
+ // Tool implementation runs in iframe
36
+ return { results: ["..."] };
37
+ },
38
+ });
39
+ ```
40
+
41
+ ### In the parent window (consumes context)
42
+
43
+ ```typescript
44
+ // The parent window hosts the assistant that needs the context
45
+ const frameHost = new AssistantFrameHost(iframeWindow);
46
+
47
+ // Register with assistant runtime
48
+ const runtime = useAssistantRuntime();
49
+ runtime.registerModelContextProvider(frameHost);
50
+
51
+ // The assistant now has access to tools from the iframe
52
+ ```
53
+
54
+ ### Communication Channel Design
55
+
56
+ The communication between `AssistantFrameProvider` (iframe) and `AssistantFrameHost` (parent window) uses the `window.postMessage` API with a structured protocol. The iframe provides model context to the parent window's assistant.
57
+
58
+ #### ModelContextProvider API
59
+
60
+ AssistantFrameHost implements the ModelContextProvider API. It immediately subscribes to the iframe for updates. This is necssary because ModelContextProvider.getModelContext() is synchronous.
61
+
62
+ #### Message Channel
63
+
64
+ All messages are wrapped with a channel identifier to avoid conflicts with other postMessage usage:
65
+
66
+ ```typescript
67
+ {
68
+ channel: "assistant-ui-frame",
69
+ message: FrameMessage
70
+ }
71
+ ```
72
+
73
+ #### Message Types
74
+
75
+ 1. **Context Discovery**
76
+ - `model-context-request`: Parent (Host) requests current context from iframe (Provider)
77
+ - `model-context-update`: Iframe pushes context changes to parent
78
+ 2. **Tool Execution**
79
+ - `tool-call`: Parent requests tool execution in iframe (where tools are defined)
80
+ - `tool-result`: Iframe returns execution result or error to parent
81
+
82
+ #### Serialization
83
+
84
+ - **Tools**: Zod schemas are converted to JSON Schema format using `z.toJSONSchema()`
85
+ - **Parameters**: Tool parameters are serialized as JSON
86
+ - **System messages**: Passed as strings
87
+ - **Unsupported features**: Model config, call settings, and priority are not transmitted
88
+
89
+ #### Security Considerations
90
+
91
+ 1. **Origin Validation**: Both sides can specify `targetOrigin` to restrict message sources
92
+ 2. **Window Reference**: Host (parent) only accepts messages from the specific iframe window it's connected to
93
+ 3. **Message Channel**: Using a unique channel identifier prevents cross-talk with other postMessage users
94
+
95
+ #### Connection Lifecycle
96
+
97
+ 1. **Initialization**: Parent (Host) sends `model-context-request` to iframe on creation
98
+ 2. **Updates**: Iframe (Provider) notifies parent whenever any registered ModelContextProvider changes
99
+
100
+ #### Error Handling
101
+
102
+ - Tool execution errors are serialized and sent back as error messages
103
+ - Connection failures (timeout, no response) are silently handled - the Host continues to work as an empty ModelContextProvider
104
+ - If the iframe doesn't register any providers, the AssistantFrameHost acts as a no-op empty ModelContextProvider returning `{}` from `getModelContext()`
@@ -0,0 +1,4 @@
1
+ export * from "./AssistantFrameHost";
2
+ export * from "./AssistantFrameProvider";
3
+ export * from "./AssistantFrameTypes";
4
+ export * from "./useAssistantFrameHost";
@@ -0,0 +1,48 @@
1
+ "use client";
2
+
3
+ import { useEffect, RefObject } from "react";
4
+ import { AssistantFrameHost } from "./AssistantFrameHost";
5
+ import { Unsubscribe } from "../../types";
6
+
7
+ type UseAssistantFrameHostOptions = {
8
+ iframeRef: Readonly<RefObject<HTMLIFrameElement | null | undefined>>;
9
+ targetOrigin?: string;
10
+ register: (frameHost: AssistantFrameHost) => Unsubscribe;
11
+ };
12
+
13
+ /**
14
+ * React hook that manages the lifecycle of an AssistantFrameHost and its binding to the current AssistantRuntime.
15
+ *
16
+ * Usage example:
17
+ * ```typescript
18
+ * function MyComponent() {
19
+ * const iframeRef = useRef<HTMLIFrameElement>(null);
20
+ *
21
+ * useAssistantFrameHost({
22
+ * iframeRef,
23
+ * targetOrigin: "https://trusted-domain.com", // optional
24
+ * });
25
+ *
26
+ * return <iframe ref={iframeRef} src="..." />;
27
+ * }
28
+ * ```
29
+ */
30
+ export const useAssistantFrameHost = ({
31
+ iframeRef,
32
+ targetOrigin = "*",
33
+ register,
34
+ }: UseAssistantFrameHostOptions): void => {
35
+ useEffect(() => {
36
+ const iframeWindow = iframeRef.current?.contentWindow;
37
+ if (!iframeWindow) return;
38
+
39
+ const frameHost = new AssistantFrameHost(iframeWindow, targetOrigin);
40
+
41
+ const unsubscribe = register(frameHost);
42
+
43
+ return () => {
44
+ frameHost.dispose();
45
+ unsubscribe();
46
+ };
47
+ }, [iframeRef, targetOrigin, register]);
48
+ };
@@ -33,3 +33,6 @@ export { tool } from "./tool";
33
33
  */
34
34
  export { makeAssistantVisible as makeAssistantReadable } from "./makeAssistantVisible";
35
35
  export { makeAssistantVisible } from "./makeAssistantVisible";
36
+
37
+ export * from "./registry";
38
+ export * from "./frame";