@tylertech/forge-ai 0.15.2 → 0.15.3-dev.1

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 (88) hide show
  1. package/custom-elements.json +4501 -1781
  2. package/dist/ai-artifact-card/ai-artifact-card.d.ts +75 -0
  3. package/dist/ai-artifact-card/ai-artifact-card.mjs +142 -0
  4. package/dist/ai-artifact-card/ai-artifact-card.scss.mjs +283 -0
  5. package/dist/ai-artifact-card/index.d.ts +1 -0
  6. package/dist/ai-artifact-card/index.mjs +5 -0
  7. package/dist/ai-artifact-card-group/ai-artifact-card-group.d.ts +57 -0
  8. package/dist/ai-artifact-card-group/ai-artifact-card-group.mjs +137 -0
  9. package/dist/ai-artifact-card-group/ai-artifact-card-group.scss.mjs +173 -0
  10. package/dist/ai-artifact-card-group/index.d.ts +1 -0
  11. package/dist/ai-artifact-card-group/index.mjs +5 -0
  12. package/dist/ai-assistant-response/ai-assistant-response.d.ts +2 -2
  13. package/dist/ai-assistant-response/ai-assistant-response.mjs +26 -2
  14. package/dist/ai-canvas/ai-canvas.d.ts +43 -0
  15. package/dist/ai-canvas/ai-canvas.mjs +95 -0
  16. package/dist/ai-canvas/ai-canvas.scss.mjs +4 -0
  17. package/dist/ai-canvas/index.d.ts +1 -0
  18. package/dist/ai-canvas/index.mjs +5 -0
  19. package/dist/ai-chatbot/ag-ui-adapter.d.ts +2 -2
  20. package/dist/ai-chatbot/ag-ui-adapter.mjs +1 -1
  21. package/dist/ai-chatbot/agent-adapter.d.ts +111 -4
  22. package/dist/ai-chatbot/agent-adapter.mjs +22 -0
  23. package/dist/ai-chatbot/agent-runner.d.ts +2 -2
  24. package/dist/ai-chatbot/ai-chatbot-base.d.ts +29 -2
  25. package/dist/ai-chatbot/ai-chatbot-base.mjs +147 -3
  26. package/dist/ai-chatbot/ai-chatbot-tool-call.d.ts +2 -2
  27. package/dist/ai-chatbot/ai-chatbot-tool-call.mjs +88 -10
  28. package/dist/ai-chatbot/ai-chatbot.d.ts +24 -1
  29. package/dist/ai-chatbot/ai-chatbot.mjs +102 -2
  30. package/dist/ai-chatbot/ai-chatbot.scss.mjs +1 -1
  31. package/dist/ai-chatbot/create-tool-renderer.d.ts +5 -5
  32. package/dist/ai-chatbot/index.d.ts +3 -2
  33. package/dist/ai-chatbot/index.mjs +3 -0
  34. package/dist/ai-chatbot/message-state-controller.d.ts +10 -1
  35. package/dist/ai-chatbot/message-state-controller.mjs +78 -20
  36. package/dist/ai-chatbot/tool-canvas.d.ts +60 -0
  37. package/dist/ai-chatbot/tool-canvas.mjs +128 -0
  38. package/dist/ai-chatbot/types.d.ts +325 -15
  39. package/dist/ai-chatbot/utils.d.ts +8 -3
  40. package/dist/ai-chatbot/utils.mjs +7 -1
  41. package/dist/ai-chatbot-launcher/ai-chatbot-launcher.d.ts +40 -2
  42. package/dist/ai-chatbot-launcher/ai-chatbot-launcher.mjs +224 -7
  43. package/dist/ai-chatbot-launcher/ai-chatbot-launcher.scss.mjs +1 -1
  44. package/dist/ai-embedded-chat/ai-embedded-chat.mjs +1 -0
  45. package/dist/ai-message-thread/ai-message-thread.d.ts +3 -2
  46. package/dist/ai-message-thread/ai-message-thread.mjs +36 -3
  47. package/dist/ai-steps/ai-steps.d.ts +67 -0
  48. package/dist/ai-steps/ai-steps.mjs +163 -0
  49. package/dist/ai-steps/ai-steps.scss.mjs +4 -0
  50. package/dist/ai-steps/index.d.ts +1 -0
  51. package/dist/ai-steps/index.mjs +5 -0
  52. package/dist/ai-tool-call-indicator/ai-tool-call-indicator.d.ts +2 -2
  53. package/dist/ai-tool-call-indicator/ai-tool-call-indicator.mjs +1 -1
  54. package/dist/core/canvas-controller.d.ts +81 -0
  55. package/dist/core/canvas-controller.mjs +221 -0
  56. package/dist/core/chatbot-core-controller.d.ts +4 -2
  57. package/dist/core/chatbot-core-controller.mjs +121 -4
  58. package/dist/core/index.d.ts +2 -1
  59. package/dist/core/index.mjs +2 -0
  60. package/dist/core/resize-controller.d.ts +5 -0
  61. package/dist/core/resize-controller.mjs +7 -5
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.mjs +15 -0
  64. package/dist/tools/ai-data-table/ai-data-table-definition.mjs +1 -0
  65. package/dist/tools/ai-mcp-app/ai-mcp-app.d.ts +70 -0
  66. package/dist/tools/ai-mcp-app/ai-mcp-app.mjs +589 -0
  67. package/dist/tools/ai-mcp-app/ai-mcp-app.scss.mjs +4 -0
  68. package/dist/tools/ai-mcp-app/index.d.ts +3 -0
  69. package/dist/tools/ai-mcp-app/index.mjs +6 -0
  70. package/dist/tools/ai-mcp-app/mcp-app-bridge-controller.d.ts +60 -0
  71. package/dist/tools/ai-mcp-app/mcp-app-bridge-controller.mjs +147 -0
  72. package/dist/tools/ai-mcp-app/mcp-app-bridge-types.d.ts +124 -0
  73. package/dist/tools/ai-mcp-app/mcp-app-bridge.d.ts +41 -0
  74. package/dist/tools/ai-mcp-app/mcp-app-bridge.mjs +293 -0
  75. package/dist/tools/ai-mcp-app/mcp-app-context.d.ts +46 -0
  76. package/dist/tools/ai-mcp-app/mcp-app-context.mjs +5 -0
  77. package/dist/tools/ai-steps/ai-steps-definition.d.ts +2 -0
  78. package/dist/tools/ai-steps/ai-steps-definition.mjs +51 -0
  79. package/dist/tools/ai-steps/ai-steps.d.ts +28 -0
  80. package/dist/tools/ai-steps/ai-steps.mjs +49 -0
  81. package/dist/tools/ai-steps/ai-steps.scss.mjs +4 -0
  82. package/dist/tools/ai-steps/index.d.ts +2 -0
  83. package/dist/tools/ai-steps/index.mjs +6 -0
  84. package/dist/tools/index.d.ts +1 -0
  85. package/dist/tools/index.mjs +5 -1
  86. package/dist/utils/inert-outside.d.ts +7 -0
  87. package/dist/utils/inert-outside.mjs +28 -0
  88. package/package.json +4 -1
@@ -1,4 +1,4 @@
1
- import { ChatMessage, FileUploadCallbacks, ToolDefinition } from './types.js';
1
+ import { ChatMessage, FileUploadCallbacks, McpAppUiResource, McpToolResult, AnyToolDefinition } from './types.js';
2
2
  import { EventEmitter, Subscription } from './event-emitter.js';
3
3
  export interface MessageStartEvent {
4
4
  messageId: string;
@@ -44,11 +44,70 @@ export interface ToolCallEndEvent {
44
44
  }
45
45
  export interface ToolResultEvent {
46
46
  toolCallId: string;
47
+ /** Model-facing result. Never delivered to an MCP-app View. */
47
48
  result: unknown;
48
49
  message: ChatMessage;
50
+ /** Companion structured payload delivered to an MCP-app widget, stripped from LLM context. */
51
+ structuredContent?: unknown;
52
+ /** `true` when the backend dropped `structuredContent` (size caps). */
53
+ structuredContentOmitted?: boolean;
54
+ /** Raw MCP result for an MCP-app tool call; the View's `tool-result` is built from it. */
55
+ mcpResult?: McpToolResult;
56
+ /** `true` when the backend dropped `mcpResult` (size caps). */
57
+ mcpResultOmitted?: boolean;
49
58
  isError?: boolean;
59
+ /** `true` when the tool call was cancelled rather than failed; takes precedence over `isError`. */
60
+ cancelled?: boolean;
50
61
  rawEvent?: unknown;
51
62
  }
63
+ /**
64
+ * Emitted when the backend signals that a tool call is backed by an MCP-app UI
65
+ * resource. Carries only the widget binding — the HTML and the `csp`/`permissions`
66
+ * policy come from {@link AgentAdapter.resolveMcpAppResource}. The core controller
67
+ * resolves the resource and stamps a {@link McpAppUiResource} onto the tool call.
68
+ *
69
+ * May be emitted before or after the matching `tool-call` chunk, and also after
70
+ * `setMessages`/`setThreadState` to remount widgets restored from history.
71
+ */
72
+ export interface McpUiResourceEvent {
73
+ toolCallId: string;
74
+ toolName?: string;
75
+ /** The concrete resolved `ui://` URI (interpolation done server-side). */
76
+ resourceUri: string;
77
+ serverId?: string;
78
+ mimeType?: string;
79
+ rawEvent?: unknown;
80
+ }
81
+ /** Parameters for {@link AgentAdapter.resolveMcpAppResource}. */
82
+ export interface McpAppResourceResolveParams {
83
+ /** The concrete resolved `ui://` URI from the `mcp-ui-resource` event. */
84
+ resourceUri: string;
85
+ /** The widget's tool call id; template reads are bound to it. */
86
+ toolCallId: string;
87
+ }
88
+ /**
89
+ * Parameters an MCP-app widget passes when forwarding a `tools/call` to the server.
90
+ * The library never owns a `Client`; these delegate to the adapter.
91
+ */
92
+ export interface McpToolCallParams {
93
+ name: string;
94
+ arguments?: Record<string, unknown>;
95
+ /** The calling widget's tool call id; the backend derives the MCP server from it. */
96
+ toolCallId: string;
97
+ }
98
+ /** Parameters for an MCP-app widget-initiated `resources/read`. */
99
+ export interface McpResourceReadParams {
100
+ uri: string;
101
+ /** The calling widget's tool call id. */
102
+ toolCallId: string;
103
+ }
104
+ /** Payload of a widget's `ui/update-model-context`, forwarded to the adapter. */
105
+ export interface McpModelContextUpdateParams {
106
+ /** The widget's tool call id. */
107
+ toolCallId: string;
108
+ content?: unknown[];
109
+ structuredContent?: unknown;
110
+ }
52
111
  export interface AdapterState {
53
112
  isConnected: boolean;
54
113
  isConnecting: boolean;
@@ -108,7 +167,7 @@ export interface ActivityDeltaAgentEvent {
108
167
  }
109
168
  export declare abstract class AgentAdapter {
110
169
  protected _state: AdapterState;
111
- protected _tools: ToolDefinition[];
170
+ protected _tools: AnyToolDefinition[];
112
171
  protected _events: {
113
172
  runStarted: EventEmitter<void>;
114
173
  runFinished: EventEmitter<void>;
@@ -121,6 +180,7 @@ export declare abstract class AgentAdapter {
121
180
  toolCallArgs: EventEmitter<ToolCallArgsEvent>;
122
181
  toolCallEnd: EventEmitter<ToolCallEndEvent>;
123
182
  toolResult: EventEmitter<ToolResultEvent>;
183
+ mcpUiResource: EventEmitter<McpUiResourceEvent>;
124
184
  fileUpload: EventEmitter<FileUploadEvent>;
125
185
  fileRemove: EventEmitter<FileRemoveEvent>;
126
186
  error: EventEmitter<ErrorEvent>;
@@ -143,8 +203,46 @@ export declare abstract class AgentAdapter {
143
203
  abstract get threadId(): string;
144
204
  abstract set threadId(value: string);
145
205
  clearMemory?(): Promise<void>;
146
- setTools(tools: ToolDefinition[]): void;
147
- getTools(): ToolDefinition[];
206
+ /**
207
+ * Resolve the MCP-app UI resource for an already-resolved `resourceUri`, bound to the
208
+ * widget's `toolCallId`. The adapter fetches the template for that concrete URI and
209
+ * fills `csp`/`permissions` from the template response's `_meta.ui` (`{}` when absent).
210
+ * It MUST NOT re-derive the URI (templated-URI interpolation is server-side only).
211
+ */
212
+ resolveMcpAppResource?(params: McpAppResourceResolveParams): Promise<McpAppUiResource | undefined>;
213
+ /**
214
+ * Forward a widget-initiated `tools/call` to the MCP server. Resolve with the View's
215
+ * `CallToolResult` (tool-level errors resolve with `isError: true`); reject for
216
+ * transport/HTTP errors (e.g. a 403 for a tool without `"app"` visibility) — the
217
+ * rejection is returned to the View as a JSON-RPC error.
218
+ */
219
+ callMcpTool?(params: McpToolCallParams): Promise<unknown>;
220
+ /**
221
+ * Forward a widget-initiated `resources/read` to the MCP server. When not implemented
222
+ * the View's request is rejected with a JSON-RPC error.
223
+ */
224
+ readMcpResource?(params: McpResourceReadParams): Promise<unknown>;
225
+ /**
226
+ * Called when a widget sends `ui/update-model-context`. Each call replaces the previous
227
+ * context for that `toolCallId`; adapters should debounce and may hold the latest value
228
+ * until the run ends.
229
+ */
230
+ updateMcpModelContext?(params: McpModelContextUpdateParams): Promise<void>;
231
+ /**
232
+ * Called after a widget is torn down (its turn left the visible thread, or the thread was
233
+ * cleared/replaced). Drop any state held for that `toolCallId`, e.g. a pending model context.
234
+ */
235
+ disposeMcpAppView?(params: {
236
+ toolCallId: string;
237
+ }): void;
238
+ setTools(tools: AnyToolDefinition[]): void;
239
+ getTools(): AnyToolDefinition[];
240
+ /**
241
+ * Tools to forward to the backend agent, excluding MCP-app tools (`ToolDefinition.mcpApp`).
242
+ * MCP-app tools are a host-internal mechanism for streaming a UI resource into an existing
243
+ * tool call — the model never calls them directly, so they must never reach the agent.
244
+ */
245
+ getModelFacingTools(): AnyToolDefinition[];
148
246
  getState(): AdapterState;
149
247
  get isConnected(): boolean;
150
248
  get isConnecting(): boolean;
@@ -160,6 +258,7 @@ export declare abstract class AgentAdapter {
160
258
  onToolCallArgs(callback: (event: ToolCallArgsEvent) => void): Subscription;
161
259
  onToolCallEnd(callback: (event: ToolCallEndEvent) => void): Subscription;
162
260
  onToolCallResult(callback: (event: ToolResultEvent) => void): Subscription;
261
+ onMcpUiResource(callback: (event: McpUiResourceEvent) => void): Subscription;
163
262
  onFileUpload(callback: (event: FileUploadEvent) => void): Subscription;
164
263
  onFileRemove(callback: (event: FileRemoveEvent) => void): Subscription;
165
264
  onError(callback: (event: ErrorEvent) => void): Subscription;
@@ -184,6 +283,14 @@ export declare abstract class AgentAdapter {
184
283
  protected _emitToolCallArgs(event: ToolCallArgsEvent, rawEvent?: unknown): void;
185
284
  protected _emitToolCallEnd(event: ToolCallEndEvent, rawEvent?: unknown): void;
186
285
  protected _emitToolResult(event: ToolResultEvent, rawEvent?: unknown): void;
286
+ /**
287
+ * Announce that a tool call renders as an MCP-app widget. Also the way to remount
288
+ * widgets restored from history: call it per widget after `setMessages`/`setThreadState`
289
+ * — the resource is resolved and stamped onto the restored (already finalized) tool
290
+ * call. If the tool call isn't in state yet, the resource is held and applied when it
291
+ * arrives.
292
+ */
293
+ protected _emitMcpUiResource(event: McpUiResourceEvent, rawEvent?: unknown): void;
187
294
  emitFileUpload(file: File, callbacks: FileUploadCallbacks): void;
188
295
  emitFileRemove(fileId: string, callbacks: {
189
296
  onSuccess: () => void;
@@ -15,6 +15,7 @@ class AgentAdapter {
15
15
  toolCallArgs: new EventEmitter(),
16
16
  toolCallEnd: new EventEmitter(),
17
17
  toolResult: new EventEmitter(),
18
+ mcpUiResource: new EventEmitter(),
18
19
  fileUpload: new EventEmitter(),
19
20
  fileRemove: new EventEmitter(),
20
21
  error: new EventEmitter(),
@@ -36,6 +37,14 @@ class AgentAdapter {
36
37
  getTools() {
37
38
  return [...this._tools];
38
39
  }
40
+ /**
41
+ * Tools to forward to the backend agent, excluding MCP-app tools (`ToolDefinition.mcpApp`).
42
+ * MCP-app tools are a host-internal mechanism for streaming a UI resource into an existing
43
+ * tool call — the model never calls them directly, so they must never reach the agent.
44
+ */
45
+ getModelFacingTools() {
46
+ return this._tools.filter((tool) => tool.mcpApp === void 0);
47
+ }
39
48
  getState() {
40
49
  return { ...this._state };
41
50
  }
@@ -81,6 +90,9 @@ class AgentAdapter {
81
90
  onToolCallResult(callback) {
82
91
  return this._events.toolResult.subscribe(callback);
83
92
  }
93
+ onMcpUiResource(callback) {
94
+ return this._events.mcpUiResource.subscribe(callback);
95
+ }
84
96
  onFileUpload(callback) {
85
97
  return this._events.fileUpload.subscribe(callback);
86
98
  }
@@ -153,6 +165,16 @@ class AgentAdapter {
153
165
  _emitToolResult(event, rawEvent) {
154
166
  this._events.toolResult.emit({ ...event, rawEvent });
155
167
  }
168
+ /**
169
+ * Announce that a tool call renders as an MCP-app widget. Also the way to remount
170
+ * widgets restored from history: call it per widget after `setMessages`/`setThreadState`
171
+ * — the resource is resolved and stamped onto the restored (already finalized) tool
172
+ * call. If the tool call isn't in state yet, the resource is held and applied when it
173
+ * arrives.
174
+ */
175
+ _emitMcpUiResource(event, rawEvent) {
176
+ this._events.mcpUiResource.emit({ ...event, rawEvent });
177
+ }
156
178
  emitFileUpload(file, callbacks) {
157
179
  this._events.fileUpload.emit({ file, ...callbacks });
158
180
  }
@@ -1,12 +1,12 @@
1
1
  import { AgentAdapter } from './agent-adapter.js';
2
- import { ChatMessage, ToolCall, ToolDefinition } from './types.js';
2
+ import { ChatMessage, ToolCall, AnyToolDefinition } from './types.js';
3
3
  export interface AgentRunnerConfig {
4
4
  /** The adapter instance that handles communication with the AI model */
5
5
  adapter: AgentAdapter;
6
6
  /** The user prompt to send to the agent */
7
7
  prompt: string;
8
8
  /** Optional tools the agent can call during execution */
9
- tools?: ToolDefinition[];
9
+ tools?: AnyToolDefinition[];
10
10
  /** Execution mode: 'one-shot' stops after first response, 'multi-turn' allows tool usage loops. @default 'one-shot' */
11
11
  mode?: 'one-shot' | 'multi-turn';
12
12
  /** Maximum conversation turns in multi-turn mode to prevent infinite loops. @default 20 */
@@ -10,9 +10,10 @@ import { ForgeAiSuggestionsEventData, Suggestion } from '../ai-suggestions';
10
10
  import { ForgeAiVoiceInputResultEvent } from '../ai-voice-input';
11
11
  import { AgentAdapter } from './agent-adapter.js';
12
12
  import { ChatbotCoreController } from '../core/chatbot-core-controller.js';
13
- import { Agent, ChatMessage, ClientMessageInput, ContextItem, HeadingLevel, MessageItem, SlashCommand, ThreadState, ToolDefinition } from './types.js';
13
+ import { Agent, ChatMessage, ClientMessageInput, ContextItem, HeadingLevel, MessageItem, SlashCommand, ThreadState, ToolCall, AnyToolDefinition } from './types.js';
14
14
  export type FeatureToggle = 'on' | 'off';
15
15
  export declare abstract class AiChatbotBase extends LitElement {
16
+ #private;
16
17
  protected abstract _messageThreadRef: Ref<AiMessageThreadComponent>;
17
18
  protected abstract _promptRef: Ref<AiPromptComponent>;
18
19
  adapter?: AgentAdapter;
@@ -34,14 +35,34 @@ export declare abstract class AiChatbotBase extends LitElement {
34
35
  agents: Agent[];
35
36
  selectedAgentId?: string;
36
37
  contextItems: ContextItem[];
38
+ mcpAppSandboxUrl?: string;
39
+ /**
40
+ * Theme fed into an MCP-app widget's `hostContext.theme`. The chatbot's own appearance
41
+ * comes from Forge tokens and is unaffected. Set this whenever the app switches between
42
+ * light and dark: it is the signal that re-sends `theme` and the Forge-derived
43
+ * `styles.variables` to widgets, since token changes alone can't be detected. Absent →
44
+ * follows `prefers-color-scheme`.
45
+ */
46
+ mcpAppTheme?: 'light' | 'dark';
47
+ private _mcpAppHost;
48
+ /**
49
+ * Sends `ui/resource-teardown` to mounted MCP-app widgets (all, or only `toolCallIds`) and
50
+ * releases them immediately, without waiting for acks. The adapter then drops any state it
51
+ * holds for them (e.g. a pending model context).
52
+ */
53
+ protected _teardownMcpAppViews({ toolCallIds }?: {
54
+ toolCallIds?: string[];
55
+ }): void;
37
56
  protected _coreController: ChatbotCoreController;
38
57
  protected get _isStreaming(): boolean;
39
58
  protected get _isUploading(): boolean;
40
- protected get _tools(): Map<string, ToolDefinition>;
59
+ protected get _tools(): Map<string, AnyToolDefinition>;
41
60
  protected get _messageItems(): MessageItem[];
61
+ protected get _toolCalls(): ToolCall[];
42
62
  protected get _hasMessages(): boolean;
43
63
  protected get _allContextItems(): ContextItem[];
44
64
  connectedCallback(): void;
65
+ disconnectedCallback(): void;
45
66
  willUpdate(changedProperties: PropertyValues<this>): void;
46
67
  protected _onConnected(): void;
47
68
  protected get _slashCommands(): SlashCommand[];
@@ -136,6 +157,12 @@ export declare abstract class AiChatbotBase extends LitElement {
136
157
  removeClientMessage(id: string): void;
137
158
  sendMessage(content: string, files?: File[]): Promise<void>;
138
159
  abort(): void;
160
+ /**
161
+ * Re-sends the Forge-derived `styles.variables` to every MCP-app widget. Call after
162
+ * changing Forge tokens without a light/dark switch (e.g. a brand change); for light/dark,
163
+ * set {@link mcpAppTheme} instead.
164
+ */
165
+ refreshMcpAppStyles(): void;
139
166
  scrollToBottom({ behavior }?: {
140
167
  behavior?: ScrollBehavior;
141
168
  }): Promise<void>;
@@ -1,5 +1,7 @@
1
+ import { provide } from "@lit/context";
1
2
  import { LitElement } from "lit";
2
3
  import { property } from "lit/decorators.js";
4
+ import { mcpAppHostContext } from "../tools/ai-mcp-app/mcp-app-context.mjs";
3
5
  import { ChatbotCoreController } from "../core/chatbot-core-controller.mjs";
4
6
  import { fileAttachmentsToContextItems, generateId, formatChatTranscript, downloadFile, createChatExportFilename } from "./utils.mjs";
5
7
  var __defProp = Object.defineProperty;
@@ -11,6 +13,34 @@ var __decorateClass = (decorators, target, key, kind) => {
11
13
  if (result) __defProp(target, key, result);
12
14
  return result;
13
15
  };
16
+ function mcpAppHostCapabilities(adapter) {
17
+ return {
18
+ openLinks: {},
19
+ logging: {},
20
+ message: { text: {} },
21
+ ...typeof adapter?.callMcpTool === "function" ? { serverTools: {} } : {},
22
+ ...typeof adapter?.updateMcpModelContext === "function" ? { updateModelContext: { text: {}, structuredContent: {} } } : {}
23
+ };
24
+ }
25
+ function mcpAppEnvironment() {
26
+ return {
27
+ availableDisplayModes: ["inline", "fullscreen", "pip"],
28
+ locale: navigator.language,
29
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
30
+ userAgent: navigator.userAgent,
31
+ platform: "web",
32
+ deviceCapabilities: {
33
+ touch: window.matchMedia("(pointer: coarse)").matches || navigator.maxTouchPoints > 0,
34
+ hover: window.matchMedia("(hover: hover)").matches
35
+ },
36
+ safeAreaInsets: { top: 0, right: 0, bottom: 0, left: 0 }
37
+ };
38
+ }
39
+ function mcpMessageText(content) {
40
+ return content.filter(
41
+ (block) => !!block && typeof block === "object" && block.type === "text" && typeof block.text === "string"
42
+ ).map((block) => block.text).join("\n\n");
43
+ }
14
44
  class AiChatbotBase extends LitElement {
15
45
  constructor() {
16
46
  super(...arguments);
@@ -29,6 +59,74 @@ class AiChatbotBase extends LitElement {
29
59
  this.clearOption = "on";
30
60
  this.agents = [];
31
61
  this.contextItems = [];
62
+ this.#themeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
63
+ this.#mcpAppViews = /* @__PURE__ */ new Map();
64
+ this._mcpAppHost = {
65
+ callTool: (params) => this._coreController?.adapter?.callMcpTool?.(params) ?? Promise.reject(new Error("tools/call is not supported by this host")),
66
+ readResource: (params) => this._coreController?.adapter?.readMcpResource?.(params) ?? Promise.reject(new Error("resources/read is not supported by this host")),
67
+ updateModelContext: (params) => this._coreController?.adapter?.updateMcpModelContext?.(params) ?? Promise.resolve(),
68
+ draftMessage: ({ content }) => this.#draftMcpAppMessage(content),
69
+ teardownView: ({ toolCallId }) => this._teardownMcpAppViews({ toolCallIds: [toolCallId] }),
70
+ registerView: (view) => {
71
+ this.#mcpAppViews.set(view.toolCallId, view);
72
+ return () => {
73
+ if (this.#mcpAppViews.get(view.toolCallId) === view) {
74
+ this.#mcpAppViews.delete(view.toolCallId);
75
+ }
76
+ };
77
+ },
78
+ hostContext: { theme: this.#effectiveTheme, ...mcpAppEnvironment() },
79
+ sandboxUrl: void 0,
80
+ hostCapabilities: mcpAppHostCapabilities(void 0)
81
+ };
82
+ this.#handleThemeChange = () => {
83
+ this.#refreshMcpAppHostContext();
84
+ };
85
+ }
86
+ #themeMediaQuery;
87
+ get #effectiveTheme() {
88
+ return this.mcpAppTheme ?? (this.#themeMediaQuery.matches ? "dark" : "light");
89
+ }
90
+ #mcpAppViews;
91
+ /**
92
+ * Places a widget's `ui/message` text in the prompt for the user to review and send. A
93
+ * widget never starts a model turn on its own. Refused when the message has no text.
94
+ */
95
+ async #draftMcpAppMessage(content) {
96
+ const text = mcpMessageText(content);
97
+ const prompt = this._promptRef.value;
98
+ if (!text.trim() || !prompt) {
99
+ return { isError: true };
100
+ }
101
+ prompt.value = text;
102
+ prompt.focus();
103
+ return {};
104
+ }
105
+ /**
106
+ * Sends `ui/resource-teardown` to mounted MCP-app widgets (all, or only `toolCallIds`) and
107
+ * releases them immediately, without waiting for acks. The adapter then drops any state it
108
+ * holds for them (e.g. a pending model context).
109
+ */
110
+ _teardownMcpAppViews({ toolCallIds } = {}) {
111
+ const views = [...this.#mcpAppViews.values()].filter((view) => toolCallIds?.includes(view.toolCallId) ?? true);
112
+ for (const view of views) {
113
+ this.#mcpAppViews.delete(view.toolCallId);
114
+ view.teardown();
115
+ this._coreController?.adapter?.disposeMcpAppView?.({ toolCallId: view.toolCallId });
116
+ }
117
+ }
118
+ /** Tears down widgets rendered at or after `index`, then removes those items. */
119
+ #removeMessageItemsFrom(index) {
120
+ this._teardownMcpAppViews({ toolCallIds: this._coreController.getToolCallIdsFrom(index) });
121
+ this._coreController.removeMessageItemsFrom(index);
122
+ }
123
+ #handleThemeChange;
124
+ /** A new host context object makes every widget re-read its Forge styles and notify its View. */
125
+ #refreshMcpAppHostContext() {
126
+ this._mcpAppHost = {
127
+ ...this._mcpAppHost,
128
+ hostContext: { ...this._mcpAppHost.hostContext, theme: this.#effectiveTheme }
129
+ };
32
130
  }
33
131
  get _isStreaming() {
34
132
  return this._coreController?.isStreaming ?? false;
@@ -42,6 +140,17 @@ class AiChatbotBase extends LitElement {
42
140
  get _messageItems() {
43
141
  return this._coreController?.messageItems ?? [];
44
142
  }
143
+ get _toolCalls() {
144
+ return this._messageItems.flatMap((item) => {
145
+ if (item.type === "toolCall") {
146
+ return [item.data];
147
+ }
148
+ if (item.type === "assistant") {
149
+ return item.data.children.flatMap((child) => child.type === "toolCall" ? [child.data] : []);
150
+ }
151
+ return [];
152
+ });
153
+ }
45
154
  get _hasMessages() {
46
155
  return this._coreController?.hasMessages ?? false;
47
156
  }
@@ -66,12 +175,26 @@ class AiChatbotBase extends LitElement {
66
175
  if (this.adapter) {
67
176
  this._coreController.adapter = this.adapter;
68
177
  }
178
+ this.#themeMediaQuery.addEventListener("change", this.#handleThemeChange);
69
179
  this._onConnected();
70
180
  }
181
+ disconnectedCallback() {
182
+ super.disconnectedCallback();
183
+ this.#themeMediaQuery.removeEventListener("change", this.#handleThemeChange);
184
+ }
71
185
  willUpdate(changedProperties) {
72
186
  if (changedProperties.has("adapter") && this._coreController) {
73
187
  this._coreController.adapter = this.adapter;
74
188
  }
189
+ if (changedProperties.has("adapter")) {
190
+ this._mcpAppHost = { ...this._mcpAppHost, hostCapabilities: mcpAppHostCapabilities(this.adapter) };
191
+ }
192
+ if (changedProperties.has("mcpAppSandboxUrl")) {
193
+ this._mcpAppHost = { ...this._mcpAppHost, sandboxUrl: this.mcpAppSandboxUrl };
194
+ }
195
+ if (changedProperties.has("mcpAppTheme")) {
196
+ this.#refreshMcpAppHostContext();
197
+ }
75
198
  }
76
199
  _onConnected() {
77
200
  }
@@ -239,7 +362,7 @@ class AiChatbotBase extends LitElement {
239
362
  }
240
363
  const responseIndex = messageIndex + 1;
241
364
  if (responseIndex < this._messageItems.length) {
242
- this._coreController.removeMessageItemsFrom(responseIndex);
365
+ this.#removeMessageItemsFrom(responseIndex);
243
366
  }
244
367
  adapter.sendMessage(this.getMessages());
245
368
  }
@@ -256,7 +379,7 @@ class AiChatbotBase extends LitElement {
256
379
  this._coreController.updateMessageContent(messageId, content);
257
380
  const responseIndex = messageIndex + 1;
258
381
  if (responseIndex < this._messageItems.length) {
259
- this._coreController.removeMessageItemsFrom(responseIndex);
382
+ this.#removeMessageItemsFrom(responseIndex);
260
383
  }
261
384
  adapter.sendMessage(this.getMessages());
262
385
  }
@@ -272,7 +395,7 @@ class AiChatbotBase extends LitElement {
272
395
  if (responseIndex === -1) {
273
396
  return;
274
397
  }
275
- this._coreController.removeMessageItemsFrom(responseIndex);
398
+ this.#removeMessageItemsFrom(responseIndex);
276
399
  adapter.sendMessage(this.getMessages());
277
400
  }
278
401
  _handleFeedback(evt, type) {
@@ -348,6 +471,7 @@ class AiChatbotBase extends LitElement {
348
471
  if (event.defaultPrevented) {
349
472
  return false;
350
473
  }
474
+ this._teardownMcpAppViews();
351
475
  this._coreController.clearMessages();
352
476
  return true;
353
477
  }
@@ -365,6 +489,7 @@ class AiChatbotBase extends LitElement {
365
489
  * resetting thread IDs or closing panels.
366
490
  */
367
491
  startNewChat() {
492
+ this._teardownMcpAppViews();
368
493
  this._coreController.clearMessages();
369
494
  }
370
495
  getMessages() {
@@ -431,6 +556,14 @@ class AiChatbotBase extends LitElement {
431
556
  abort() {
432
557
  this._handleStop();
433
558
  }
559
+ /**
560
+ * Re-sends the Forge-derived `styles.variables` to every MCP-app widget. Call after
561
+ * changing Forge tokens without a light/dark switch (e.g. a brand change); for light/dark,
562
+ * set {@link mcpAppTheme} instead.
563
+ */
564
+ refreshMcpAppStyles() {
565
+ this.#refreshMcpAppHostContext();
566
+ }
434
567
  async scrollToBottom({ behavior } = {}) {
435
568
  await this._messageThreadRef.value?.updateComplete;
436
569
  await new Promise(requestAnimationFrame);
@@ -448,6 +581,7 @@ class AiChatbotBase extends LitElement {
448
581
  return this.agents.find((a) => a.id === this.selectedAgentId);
449
582
  }
450
583
  async setThreadState(threadState) {
584
+ this._teardownMcpAppViews();
451
585
  this.setMessages(threadState.messages);
452
586
  this.selectedAgentId = threadState.selectedAgentId;
453
587
  if (threadState.threadId && this._coreController.adapter) {
@@ -535,6 +669,16 @@ __decorateClass([
535
669
  __decorateClass([
536
670
  property({ attribute: false })
537
671
  ], AiChatbotBase.prototype, "contextItems");
672
+ __decorateClass([
673
+ property({ attribute: "mcp-app-sandbox-url" })
674
+ ], AiChatbotBase.prototype, "mcpAppSandboxUrl");
675
+ __decorateClass([
676
+ property({ attribute: "mcp-app-theme" })
677
+ ], AiChatbotBase.prototype, "mcpAppTheme");
678
+ __decorateClass([
679
+ provide({ context: mcpAppHostContext }),
680
+ property({ attribute: false })
681
+ ], AiChatbotBase.prototype, "_mcpAppHost");
538
682
  export {
539
683
  AiChatbotBase
540
684
  };
@@ -1,5 +1,5 @@
1
1
  import { LitElement, TemplateResult, nothing, PropertyValues, CSSResult } from 'lit';
2
- import { ToolCall, ToolDefinition } from './types.js';
2
+ import { ToolCall, AnyToolDefinition } from './types.js';
3
3
  declare global {
4
4
  interface HTMLElementTagNameMap {
5
5
  'forge-ai-chatbot-tool-call': AiChatbotToolCallComponent;
@@ -13,7 +13,7 @@ export declare class AiChatbotToolCallComponent extends LitElement {
13
13
  #private;
14
14
  static styles: CSSResult;
15
15
  toolCall: ToolCall;
16
- toolDefinition?: ToolDefinition;
16
+ toolDefinition?: AnyToolDefinition;
17
17
  updated(changedProperties: PropertyValues<this>): void;
18
18
  render(): TemplateResult | typeof nothing;
19
19
  }