@tylertech/forge-ai 0.15.2 → 0.15.3-dev.0
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.
- package/custom-elements.json +8266 -6476
- package/dist/ai-artifact-card/ai-artifact-card.d.ts +75 -0
- package/dist/ai-artifact-card/ai-artifact-card.mjs +142 -0
- package/dist/ai-artifact-card/ai-artifact-card.scss.mjs +283 -0
- package/dist/ai-artifact-card/index.d.ts +1 -0
- package/dist/ai-artifact-card/index.mjs +5 -0
- package/dist/ai-artifact-card-group/ai-artifact-card-group.d.ts +57 -0
- package/dist/ai-artifact-card-group/ai-artifact-card-group.mjs +137 -0
- package/dist/ai-artifact-card-group/ai-artifact-card-group.scss.mjs +173 -0
- package/dist/ai-artifact-card-group/index.d.ts +1 -0
- package/dist/ai-artifact-card-group/index.mjs +5 -0
- package/dist/ai-assistant-response/ai-assistant-response.mjs +25 -2
- package/dist/ai-chatbot/ag-ui-adapter.mjs +1 -1
- package/dist/ai-chatbot/agent-adapter.d.ts +108 -1
- package/dist/ai-chatbot/agent-adapter.mjs +22 -0
- package/dist/ai-chatbot/ai-chatbot-base.d.ts +26 -0
- package/dist/ai-chatbot/ai-chatbot-base.mjs +136 -3
- package/dist/ai-chatbot/index.d.ts +2 -2
- package/dist/ai-chatbot/message-state-controller.d.ts +10 -1
- package/dist/ai-chatbot/message-state-controller.mjs +78 -20
- package/dist/ai-chatbot/types.d.ts +199 -1
- package/dist/ai-chatbot/utils.mjs +3 -1
- package/dist/ai-chatbot-launcher/ai-chatbot-launcher.mjs +0 -2
- package/dist/ai-message-thread/ai-message-thread.d.ts +1 -0
- package/dist/ai-message-thread/ai-message-thread.mjs +35 -3
- package/dist/ai-steps/ai-steps.d.ts +67 -0
- package/dist/ai-steps/ai-steps.mjs +163 -0
- package/dist/ai-steps/ai-steps.scss.mjs +4 -0
- package/dist/ai-steps/index.d.ts +1 -0
- package/dist/ai-steps/index.mjs +5 -0
- package/dist/ai-tool-call-indicator/ai-tool-call-indicator.mjs +1 -1
- package/dist/core/chatbot-core-controller.d.ts +2 -0
- package/dist/core/chatbot-core-controller.mjs +99 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +9 -0
- package/dist/tools/ai-mcp-app/ai-mcp-app.d.ts +69 -0
- package/dist/tools/ai-mcp-app/ai-mcp-app.mjs +574 -0
- package/dist/tools/ai-mcp-app/ai-mcp-app.scss.mjs +4 -0
- package/dist/tools/ai-mcp-app/index.d.ts +3 -0
- package/dist/tools/ai-mcp-app/index.mjs +6 -0
- package/dist/tools/ai-mcp-app/mcp-app-bridge-controller.d.ts +61 -0
- package/dist/tools/ai-mcp-app/mcp-app-bridge-controller.mjs +156 -0
- package/dist/tools/ai-mcp-app/mcp-app-bridge-types.d.ts +123 -0
- package/dist/tools/ai-mcp-app/mcp-app-bridge.d.ts +41 -0
- package/dist/tools/ai-mcp-app/mcp-app-bridge.mjs +293 -0
- package/dist/tools/ai-mcp-app/mcp-app-context.d.ts +46 -0
- package/dist/tools/ai-mcp-app/mcp-app-context.mjs +5 -0
- package/dist/tools/ai-steps/ai-steps-definition.d.ts +2 -0
- package/dist/tools/ai-steps/ai-steps-definition.mjs +50 -0
- package/dist/tools/ai-steps/ai-steps.d.ts +28 -0
- package/dist/tools/ai-steps/ai-steps.mjs +49 -0
- package/dist/tools/ai-steps/ai-steps.scss.mjs +4 -0
- package/dist/tools/ai-steps/index.d.ts +2 -0
- package/dist/tools/ai-steps/index.mjs +6 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.mjs +5 -1
- package/dist/utils/inert-outside.d.ts +7 -0
- package/dist/utils/inert-outside.mjs +28 -0
- package/package.json +4 -1
|
@@ -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;
|
|
@@ -66,12 +164,26 @@ class AiChatbotBase extends LitElement {
|
|
|
66
164
|
if (this.adapter) {
|
|
67
165
|
this._coreController.adapter = this.adapter;
|
|
68
166
|
}
|
|
167
|
+
this.#themeMediaQuery.addEventListener("change", this.#handleThemeChange);
|
|
69
168
|
this._onConnected();
|
|
70
169
|
}
|
|
170
|
+
disconnectedCallback() {
|
|
171
|
+
super.disconnectedCallback();
|
|
172
|
+
this.#themeMediaQuery.removeEventListener("change", this.#handleThemeChange);
|
|
173
|
+
}
|
|
71
174
|
willUpdate(changedProperties) {
|
|
72
175
|
if (changedProperties.has("adapter") && this._coreController) {
|
|
73
176
|
this._coreController.adapter = this.adapter;
|
|
74
177
|
}
|
|
178
|
+
if (changedProperties.has("adapter")) {
|
|
179
|
+
this._mcpAppHost = { ...this._mcpAppHost, hostCapabilities: mcpAppHostCapabilities(this.adapter) };
|
|
180
|
+
}
|
|
181
|
+
if (changedProperties.has("mcpAppSandboxUrl")) {
|
|
182
|
+
this._mcpAppHost = { ...this._mcpAppHost, sandboxUrl: this.mcpAppSandboxUrl };
|
|
183
|
+
}
|
|
184
|
+
if (changedProperties.has("mcpAppTheme")) {
|
|
185
|
+
this.#refreshMcpAppHostContext();
|
|
186
|
+
}
|
|
75
187
|
}
|
|
76
188
|
_onConnected() {
|
|
77
189
|
}
|
|
@@ -239,7 +351,7 @@ class AiChatbotBase extends LitElement {
|
|
|
239
351
|
}
|
|
240
352
|
const responseIndex = messageIndex + 1;
|
|
241
353
|
if (responseIndex < this._messageItems.length) {
|
|
242
|
-
this
|
|
354
|
+
this.#removeMessageItemsFrom(responseIndex);
|
|
243
355
|
}
|
|
244
356
|
adapter.sendMessage(this.getMessages());
|
|
245
357
|
}
|
|
@@ -256,7 +368,7 @@ class AiChatbotBase extends LitElement {
|
|
|
256
368
|
this._coreController.updateMessageContent(messageId, content);
|
|
257
369
|
const responseIndex = messageIndex + 1;
|
|
258
370
|
if (responseIndex < this._messageItems.length) {
|
|
259
|
-
this
|
|
371
|
+
this.#removeMessageItemsFrom(responseIndex);
|
|
260
372
|
}
|
|
261
373
|
adapter.sendMessage(this.getMessages());
|
|
262
374
|
}
|
|
@@ -272,7 +384,7 @@ class AiChatbotBase extends LitElement {
|
|
|
272
384
|
if (responseIndex === -1) {
|
|
273
385
|
return;
|
|
274
386
|
}
|
|
275
|
-
this
|
|
387
|
+
this.#removeMessageItemsFrom(responseIndex);
|
|
276
388
|
adapter.sendMessage(this.getMessages());
|
|
277
389
|
}
|
|
278
390
|
_handleFeedback(evt, type) {
|
|
@@ -348,6 +460,7 @@ class AiChatbotBase extends LitElement {
|
|
|
348
460
|
if (event.defaultPrevented) {
|
|
349
461
|
return false;
|
|
350
462
|
}
|
|
463
|
+
this._teardownMcpAppViews();
|
|
351
464
|
this._coreController.clearMessages();
|
|
352
465
|
return true;
|
|
353
466
|
}
|
|
@@ -365,6 +478,7 @@ class AiChatbotBase extends LitElement {
|
|
|
365
478
|
* resetting thread IDs or closing panels.
|
|
366
479
|
*/
|
|
367
480
|
startNewChat() {
|
|
481
|
+
this._teardownMcpAppViews();
|
|
368
482
|
this._coreController.clearMessages();
|
|
369
483
|
}
|
|
370
484
|
getMessages() {
|
|
@@ -431,6 +545,14 @@ class AiChatbotBase extends LitElement {
|
|
|
431
545
|
abort() {
|
|
432
546
|
this._handleStop();
|
|
433
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* Re-sends the Forge-derived `styles.variables` to every MCP-app widget. Call after
|
|
550
|
+
* changing Forge tokens without a light/dark switch (e.g. a brand change); for light/dark,
|
|
551
|
+
* set {@link mcpAppTheme} instead.
|
|
552
|
+
*/
|
|
553
|
+
refreshMcpAppStyles() {
|
|
554
|
+
this.#refreshMcpAppHostContext();
|
|
555
|
+
}
|
|
434
556
|
async scrollToBottom({ behavior } = {}) {
|
|
435
557
|
await this._messageThreadRef.value?.updateComplete;
|
|
436
558
|
await new Promise(requestAnimationFrame);
|
|
@@ -448,6 +570,7 @@ class AiChatbotBase extends LitElement {
|
|
|
448
570
|
return this.agents.find((a) => a.id === this.selectedAgentId);
|
|
449
571
|
}
|
|
450
572
|
async setThreadState(threadState) {
|
|
573
|
+
this._teardownMcpAppViews();
|
|
451
574
|
this.setMessages(threadState.messages);
|
|
452
575
|
this.selectedAgentId = threadState.selectedAgentId;
|
|
453
576
|
if (threadState.threadId && this._coreController.adapter) {
|
|
@@ -535,6 +658,16 @@ __decorateClass([
|
|
|
535
658
|
__decorateClass([
|
|
536
659
|
property({ attribute: false })
|
|
537
660
|
], AiChatbotBase.prototype, "contextItems");
|
|
661
|
+
__decorateClass([
|
|
662
|
+
property({ attribute: "mcp-app-sandbox-url" })
|
|
663
|
+
], AiChatbotBase.prototype, "mcpAppSandboxUrl");
|
|
664
|
+
__decorateClass([
|
|
665
|
+
property({ attribute: "mcp-app-theme" })
|
|
666
|
+
], AiChatbotBase.prototype, "mcpAppTheme");
|
|
667
|
+
__decorateClass([
|
|
668
|
+
provide({ context: mcpAppHostContext }),
|
|
669
|
+
property({ attribute: false })
|
|
670
|
+
], AiChatbotBase.prototype, "_mcpAppHost");
|
|
538
671
|
export {
|
|
539
672
|
AiChatbotBase
|
|
540
673
|
};
|
|
@@ -3,8 +3,8 @@ export { AiChatbotComponent, AiChatbotComponentTagName } from './ai-chatbot.js';
|
|
|
3
3
|
export type { ForgeAiChatbotMessageEventData, ForgeAiChatbotToolCallEventData, ForgeAiChatbotErrorEventData, ForgeAiChatbotResponseFeedbackEventData, ForgeAiChatbotAgentChangeEventData, ForgeAiChatbotFileSelectEventData, ForgeAiChatbotFileRemoveEventData } from './ai-chatbot.js';
|
|
4
4
|
export { AiChatbotToolCallComponent, AiChatbotToolCallComponentTagName } from './ai-chatbot-tool-call.js';
|
|
5
5
|
export type { Suggestion } from '../ai-suggestions/index.js';
|
|
6
|
-
export { AgentAdapter, type MessageStartEvent, type MessageDeltaEvent, type MessageEndEvent, type ToolCallEvent, type ToolCallStartEvent, type ToolCallArgsEvent, type ToolCallEndEvent, type FileUploadEvent, type FileRemoveEvent, type AdapterState, type ErrorEvent, type CustomAgentEvent, type RawAgentEvent, type RunStartedAgentEvent, type StepStartedAgentEvent, type StepFinishedAgentEvent, type StateSnapshotAgentEvent, type StateDeltaAgentEvent, type ActivitySnapshotAgentEvent, type ActivityDeltaAgentEvent } from './agent-adapter.js';
|
|
7
|
-
export type { Agent, ChatMessage, ClientMessageAction, ClientMessageInput, ClientMessageKind, ContextItem, ToolDefinition, ToolCall, ToolRenderer, IToolRenderer, HandlerContext, ToolStartContext, ToolDeltaContext, ToolEndContext, FileAttachment, UploadedFileMetadata, FileUploadCallbacks, FileUploadHandler, FileRemoveCallbacks, ThreadState, FeedbackType, ResponseFeedback, MessageItem, ForgeAiChatbotContextRemoveEventData, ForgeAiChatbotThreadSelectEventData, ForgeAiChatbotThreadSearchEventData, ForgeAiChatbotThreadLoadMoreEventData, ForgeAiChatbotThreadRenameEventData, ForgeAiChatbotThreadDeleteEventData } from './types.js';
|
|
6
|
+
export { AgentAdapter, type MessageStartEvent, type MessageDeltaEvent, type MessageEndEvent, type ToolCallEvent, type ToolCallStartEvent, type ToolCallArgsEvent, type ToolCallEndEvent, type FileUploadEvent, type FileRemoveEvent, type AdapterState, type ErrorEvent, type CustomAgentEvent, type RawAgentEvent, type RunStartedAgentEvent, type StepStartedAgentEvent, type StepFinishedAgentEvent, type StateSnapshotAgentEvent, type StateDeltaAgentEvent, type ActivitySnapshotAgentEvent, type ActivityDeltaAgentEvent, type McpUiResourceEvent, type McpToolCallParams, type McpResourceReadParams, type McpAppResourceResolveParams, type McpModelContextUpdateParams, type ToolResultEvent } from './agent-adapter.js';
|
|
7
|
+
export type { Agent, ChatMessage, ClientMessageAction, ClientMessageInput, ClientMessageKind, ContextItem, ToolDefinition, ToolCall, ToolCallStatus, ToolRenderer, IToolRenderer, HandlerContext, ToolStartContext, ToolDeltaContext, ToolEndContext, FileAttachment, UploadedFileMetadata, FileUploadCallbacks, FileUploadHandler, FileRemoveCallbacks, ThreadState, FeedbackType, ResponseFeedback, MessageItem, ForgeAiChatbotContextRemoveEventData, ForgeAiChatbotThreadSelectEventData, ForgeAiChatbotThreadSearchEventData, ForgeAiChatbotThreadLoadMoreEventData, ForgeAiChatbotThreadRenameEventData, ForgeAiChatbotThreadDeleteEventData, McpAppUiResource, McpAppResourceCsp, McpAppResourcePermissions, McpAppHostContext, McpAppHostStyles, McpAppHostCapabilities, McpAppContainerDimensions, McpAppContentModalities, McpAppToolInfo, McpAppSandboxCapabilities, McpAppDisplayMode, McpToolResult } from './types.js';
|
|
8
8
|
export * from './ag-ui-adapter.js';
|
|
9
9
|
export * from './http-agent-with-credentials.js';
|
|
10
10
|
export * from './event-emitter.js';
|
|
@@ -34,6 +34,10 @@ export declare class MessageStateController implements ReactiveController {
|
|
|
34
34
|
appendTextDelta(messageId: string, delta: string): void;
|
|
35
35
|
markTextComplete(messageId: string): void;
|
|
36
36
|
addToolCallToResponse(toolCall: ToolCall): void;
|
|
37
|
+
/**
|
|
38
|
+
* Applies `updates` to a tool call wherever it is rendered: the in-progress response,
|
|
39
|
+
* an already finalized/restored response, or a top-level tool-call item.
|
|
40
|
+
*/
|
|
37
41
|
updateToolCallInResponse(toolCallId: string, updates: Partial<ToolCall>): void;
|
|
38
42
|
completeToolCallInResponse(toolCallId: string, result: unknown, event?: ToolResultEvent): void;
|
|
39
43
|
tryFinalizeResponse(): void;
|
|
@@ -52,10 +56,15 @@ export declare class MessageStateController implements ReactiveController {
|
|
|
52
56
|
updateMessageContent(id: string, content: string): void;
|
|
53
57
|
removeMessageItem(index: number): void;
|
|
54
58
|
clearMessages(): void;
|
|
55
|
-
|
|
59
|
+
/** Tool call ids rendered at or after `index`, inside assistant responses or as top-level items. */
|
|
60
|
+
getToolCallIdsFrom(index: number): string[];
|
|
61
|
+
/** Removes items from `index` onward and returns the ids of the tool calls they held. */
|
|
62
|
+
removeMessageItemsFrom(index: number): string[];
|
|
56
63
|
/**
|
|
57
64
|
* Reconstructs the message hierarchy for API compatibility.
|
|
58
65
|
* Converts assistant responses back to ChatMessage format with tool calls.
|
|
66
|
+
* MCP-app `uiResource`s are stripped: they are re-resolved via the adapter on restore,
|
|
67
|
+
* never persisted or sent back to the agent.
|
|
59
68
|
*/
|
|
60
69
|
getMessages(): ChatMessage[];
|
|
61
70
|
setMessages(messages: ChatMessage[]): void;
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { generateId } from "./utils.mjs";
|
|
2
|
+
function withoutUiResource(toolCall) {
|
|
3
|
+
if (!toolCall.uiResource) {
|
|
4
|
+
return toolCall;
|
|
5
|
+
}
|
|
6
|
+
const { uiResource: _uiResource, ...rest } = toolCall;
|
|
7
|
+
return rest;
|
|
8
|
+
}
|
|
9
|
+
function responseItemWithoutUiResource(item) {
|
|
10
|
+
return item.type === "toolCall" ? { type: "toolCall", data: withoutUiResource(item.data) } : item;
|
|
11
|
+
}
|
|
2
12
|
class MessageStateController {
|
|
3
13
|
constructor(_host, _config) {
|
|
4
14
|
this._host = _host;
|
|
@@ -103,33 +113,61 @@ class MessageStateController {
|
|
|
103
113
|
this.#updateResponseInItems();
|
|
104
114
|
this.#notifyStateChange();
|
|
105
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Applies `updates` to a tool call wherever it is rendered: the in-progress response,
|
|
118
|
+
* an already finalized/restored response, or a top-level tool-call item.
|
|
119
|
+
*/
|
|
106
120
|
updateToolCallInResponse(toolCallId, updates) {
|
|
107
121
|
const toolCall = this._toolCalls.get(toolCallId);
|
|
108
122
|
if (!toolCall) {
|
|
109
123
|
return;
|
|
110
124
|
}
|
|
111
|
-
const
|
|
125
|
+
const isFinishing = updates.status === "complete" || updates.status === "error" || updates.status === "cancelled";
|
|
112
126
|
const updated = {
|
|
113
127
|
...toolCall,
|
|
114
128
|
...updates,
|
|
115
|
-
...
|
|
129
|
+
...isFinishing && !toolCall.endTimestamp ? { endTimestamp: Date.now() } : {}
|
|
116
130
|
};
|
|
117
131
|
this._toolCalls.set(toolCallId, updated);
|
|
118
|
-
|
|
119
|
-
for (const child of this._activeResponse.children) {
|
|
120
|
-
if (child.type === "toolCall" && child.data.id === toolCallId) {
|
|
121
|
-
child.data = updated;
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
this.#updateResponseInItems();
|
|
132
|
+
this.#replaceToolCall(updated);
|
|
127
133
|
this.#notifyStateChange();
|
|
128
134
|
}
|
|
129
135
|
completeToolCallInResponse(toolCallId, result, event) {
|
|
130
136
|
this.updateToolCallInResponse(toolCallId, {
|
|
131
137
|
result,
|
|
132
|
-
status: event
|
|
138
|
+
status: this.#resultStatus(event),
|
|
139
|
+
...event ? {
|
|
140
|
+
structuredContent: event.structuredContent,
|
|
141
|
+
structuredContentOmitted: event.structuredContentOmitted,
|
|
142
|
+
mcpResult: event.mcpResult,
|
|
143
|
+
mcpResultOmitted: event.mcpResultOmitted
|
|
144
|
+
} : {}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
#resultStatus(event) {
|
|
148
|
+
if (event?.cancelled) {
|
|
149
|
+
return "cancelled";
|
|
150
|
+
}
|
|
151
|
+
return event?.isError ? "error" : "complete";
|
|
152
|
+
}
|
|
153
|
+
#replaceToolCall(updated) {
|
|
154
|
+
const matches = (child) => child.type === "toolCall" && child.data.id === updated.id;
|
|
155
|
+
const replace = (children) => children.map((child) => matches(child) ? { type: "toolCall", data: updated } : child);
|
|
156
|
+
const activeResponse = this._activeResponse;
|
|
157
|
+
if (activeResponse?.children.some(matches)) {
|
|
158
|
+
activeResponse.children = replace(activeResponse.children);
|
|
159
|
+
}
|
|
160
|
+
this._messageItems = this._messageItems.map((item) => {
|
|
161
|
+
if (item.type === "toolCall" && item.data.id === updated.id) {
|
|
162
|
+
return { type: "toolCall", data: updated };
|
|
163
|
+
}
|
|
164
|
+
if (item.type !== "assistant" || !item.data.children.some(matches)) {
|
|
165
|
+
return item;
|
|
166
|
+
}
|
|
167
|
+
if (item.data.id === activeResponse?.id) {
|
|
168
|
+
return { type: "assistant", data: { ...activeResponse } };
|
|
169
|
+
}
|
|
170
|
+
return { type: "assistant", data: { ...item.data, children: replace(item.data.children) } };
|
|
133
171
|
});
|
|
134
172
|
}
|
|
135
173
|
tryFinalizeResponse() {
|
|
@@ -242,14 +280,32 @@ class MessageStateController {
|
|
|
242
280
|
this.#notifyStateChange();
|
|
243
281
|
this._config.onThreadSettled?.();
|
|
244
282
|
}
|
|
283
|
+
/** Tool call ids rendered at or after `index`, inside assistant responses or as top-level items. */
|
|
284
|
+
getToolCallIdsFrom(index) {
|
|
285
|
+
return this._messageItems.slice(index).flatMap((item) => {
|
|
286
|
+
if (item.type === "toolCall") {
|
|
287
|
+
return [item.data.id];
|
|
288
|
+
}
|
|
289
|
+
if (item.type === "assistant") {
|
|
290
|
+
return item.data.children.flatMap((child) => child.type === "toolCall" ? [child.data.id] : []);
|
|
291
|
+
}
|
|
292
|
+
return [];
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/** Removes items from `index` onward and returns the ids of the tool calls they held. */
|
|
245
296
|
removeMessageItemsFrom(index) {
|
|
297
|
+
const removedToolCallIds = this.getToolCallIdsFrom(index);
|
|
298
|
+
removedToolCallIds.forEach((id) => this._toolCalls.delete(id));
|
|
246
299
|
this._messageItems = this._messageItems.slice(0, index);
|
|
247
300
|
this._activeResponse = null;
|
|
248
301
|
this.#notifyStateChange();
|
|
302
|
+
return removedToolCallIds;
|
|
249
303
|
}
|
|
250
304
|
/**
|
|
251
305
|
* Reconstructs the message hierarchy for API compatibility.
|
|
252
306
|
* Converts assistant responses back to ChatMessage format with tool calls.
|
|
307
|
+
* MCP-app `uiResource`s are stripped: they are re-resolved via the adapter on restore,
|
|
308
|
+
* never persisted or sent back to the agent.
|
|
253
309
|
*/
|
|
254
310
|
getMessages() {
|
|
255
311
|
const messages = [];
|
|
@@ -258,8 +314,9 @@ class MessageStateController {
|
|
|
258
314
|
messages.push({ ...item.data, toolCalls: [] });
|
|
259
315
|
} else if (item.type === "assistant") {
|
|
260
316
|
const response = item.data;
|
|
261
|
-
const
|
|
262
|
-
const
|
|
317
|
+
const children = response.children.map(responseItemWithoutUiResource);
|
|
318
|
+
const textContent = children.filter((c) => c.type === "text").map((c) => c.content).join("");
|
|
319
|
+
const toolCalls = children.filter((c) => c.type === "toolCall").map((c) => c.data);
|
|
263
320
|
const message = {
|
|
264
321
|
id: response.id,
|
|
265
322
|
role: "assistant",
|
|
@@ -268,13 +325,13 @@ class MessageStateController {
|
|
|
268
325
|
status: response.status === "streaming" ? "streaming" : response.status === "error" ? "error" : "complete",
|
|
269
326
|
toolCalls: toolCalls.length ? toolCalls : void 0,
|
|
270
327
|
feedback: response.feedback,
|
|
271
|
-
children
|
|
328
|
+
children
|
|
272
329
|
};
|
|
273
330
|
messages.push(message);
|
|
274
331
|
} else if (item.type === "toolCall") {
|
|
275
332
|
const lastMsg = messages[messages.length - 1];
|
|
276
333
|
if (lastMsg && lastMsg.role === "assistant") {
|
|
277
|
-
lastMsg.toolCalls = [...lastMsg.toolCalls || [], item.data];
|
|
334
|
+
lastMsg.toolCalls = [...lastMsg.toolCalls || [], withoutUiResource(item.data)];
|
|
278
335
|
}
|
|
279
336
|
}
|
|
280
337
|
}
|
|
@@ -289,10 +346,11 @@ class MessageStateController {
|
|
|
289
346
|
let children;
|
|
290
347
|
if (msg.children?.length) {
|
|
291
348
|
children = msg.children.map((child) => {
|
|
292
|
-
|
|
293
|
-
|
|
349
|
+
const restored = responseItemWithoutUiResource(child);
|
|
350
|
+
if (restored.type === "toolCall") {
|
|
351
|
+
this._toolCalls.set(restored.data.id, restored.data);
|
|
294
352
|
}
|
|
295
|
-
return
|
|
353
|
+
return restored;
|
|
296
354
|
});
|
|
297
355
|
} else {
|
|
298
356
|
children = [];
|
|
@@ -300,7 +358,7 @@ class MessageStateController {
|
|
|
300
358
|
children.push({ type: "text", messageId: msg.id, content: msg.content, status: "complete" });
|
|
301
359
|
}
|
|
302
360
|
if (msg.toolCalls) {
|
|
303
|
-
for (const toolCall of msg.toolCalls) {
|
|
361
|
+
for (const toolCall of msg.toolCalls.map(withoutUiResource)) {
|
|
304
362
|
children.push({ type: "toolCall", data: toolCall });
|
|
305
363
|
this._toolCalls.set(toolCall.id, toolCall);
|
|
306
364
|
}
|
|
@@ -88,6 +88,15 @@ export interface ToolDefinition<THandlerArgs = Record<string, unknown>> {
|
|
|
88
88
|
renderer?: ToolRenderer;
|
|
89
89
|
/** If true, renderer shows immediately on tool call start (default: false). */
|
|
90
90
|
renderOnStart?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Opt-in marker declaring this tool is backed by an MCP-app UI resource. Presence
|
|
93
|
+
* signals the core controller to resolve + stamp a {@link McpAppUiResource} when the
|
|
94
|
+
* tool is called. `resourceUri` is informational; the resolved URI arrives on the
|
|
95
|
+
* `mcp-ui-resource` event.
|
|
96
|
+
*/
|
|
97
|
+
mcpApp?: {
|
|
98
|
+
resourceUri?: string;
|
|
99
|
+
};
|
|
91
100
|
/** Called when tool call starts (before args stream). */
|
|
92
101
|
onStart?: (context: ToolStartContext) => void;
|
|
93
102
|
/** Called during streaming with partial args. */
|
|
@@ -100,6 +109,176 @@ export interface ToolDefinition<THandlerArgs = Record<string, unknown>> {
|
|
|
100
109
|
*/
|
|
101
110
|
handler?: (context: HandlerContext<THandlerArgs>) => Promise<string | Record<string, unknown> | void> | string | Record<string, unknown> | void;
|
|
102
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Content-Security-Policy domain allow-lists for an MCP-app resource, mirroring the
|
|
114
|
+
* spec's `_meta.ui.csp` shape (2026-01-26). The sandbox proxy composes these into the
|
|
115
|
+
* enforced CSP; when omitted a restrictive default block applies.
|
|
116
|
+
*
|
|
117
|
+
* Local type — intentionally NOT aliased from `@modelcontextprotocol/ext-apps` so the
|
|
118
|
+
* type layer stays bridge-agnostic.
|
|
119
|
+
*/
|
|
120
|
+
export interface McpAppResourceCsp {
|
|
121
|
+
/** Domains the widget may `connect-src` to (fetch/XHR/WebSocket). */
|
|
122
|
+
connectDomains?: string[];
|
|
123
|
+
/** Domains the widget may load passive resources from (img/media/etc.). */
|
|
124
|
+
resourceDomains?: string[];
|
|
125
|
+
/** Domains allowed in `frame-src`; absent → `frame-src 'none'`. */
|
|
126
|
+
frameDomains?: string[];
|
|
127
|
+
/** Domains allowed in `base-uri`; absent → `base-uri 'self'`. */
|
|
128
|
+
baseUriDomains?: string[];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Iframe permission policy for an MCP-app resource, mirroring the spec's
|
|
132
|
+
* `_meta.ui.permissions` shape (2026-01-26): a key being present (with an empty object
|
|
133
|
+
* value) means the permission is requested. The sandbox maps requested keys onto the
|
|
134
|
+
* iframe `allow` attribute.
|
|
135
|
+
*
|
|
136
|
+
* Local type — not aliased from ext-apps.
|
|
137
|
+
*/
|
|
138
|
+
export interface McpAppResourcePermissions {
|
|
139
|
+
camera?: Record<string, never>;
|
|
140
|
+
microphone?: Record<string, never>;
|
|
141
|
+
geolocation?: Record<string, never>;
|
|
142
|
+
clipboardWrite?: Record<string, never>;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The resolved UI resource stamped onto a {@link ToolCall} once a `mcp-ui-resource`
|
|
146
|
+
* event arrives. Presence of this on a tool call is the single render discriminator for
|
|
147
|
+
* mounting `<forge-ai-mcp-app>`.
|
|
148
|
+
*
|
|
149
|
+
* Returned by `AgentAdapter.resolveMcpAppResource`, which fills `csp`/`permissions` from
|
|
150
|
+
* the template response's `_meta.ui` (pass `{}` when the template declares none). The
|
|
151
|
+
* widget refuses to render when either is missing.
|
|
152
|
+
*
|
|
153
|
+
* Local type — not aliased from ext-apps.
|
|
154
|
+
*/
|
|
155
|
+
export interface McpAppUiResource {
|
|
156
|
+
/** The complete standalone app HTML document, resolved server-side. */
|
|
157
|
+
html: string;
|
|
158
|
+
/** CSP domain allow-lists to enforce in the sandbox. Empty → restrictive default. */
|
|
159
|
+
csp: McpAppResourceCsp;
|
|
160
|
+
/** Iframe permission policy for the sandbox inner frame. */
|
|
161
|
+
permissions: McpAppResourcePermissions;
|
|
162
|
+
/** The concrete resolved `ui://` resource URI (never a template). */
|
|
163
|
+
resourceUri?: string;
|
|
164
|
+
/**
|
|
165
|
+
* Whether the widget prefers the host to draw a border/background around it.
|
|
166
|
+
* `true` → visible chrome, `false` → none, omitted → renderer default.
|
|
167
|
+
*/
|
|
168
|
+
prefersBorder?: boolean;
|
|
169
|
+
}
|
|
170
|
+
/** Display modes an MCP-app widget can be presented in (spec 2026-01-26). */
|
|
171
|
+
export type McpAppDisplayMode = 'inline' | 'fullscreen' | 'pip';
|
|
172
|
+
/**
|
|
173
|
+
* The raw MCP `CallToolResult` (minus `structuredContent`) for an MCP-app tool call. The
|
|
174
|
+
* View's `ui/notifications/tool-result` is built from this plus
|
|
175
|
+
* {@link ToolCall.structuredContent} — never from the model-facing `result`.
|
|
176
|
+
*/
|
|
177
|
+
export interface McpToolResult {
|
|
178
|
+
content: unknown[];
|
|
179
|
+
isError?: boolean;
|
|
180
|
+
_meta?: Record<string, unknown>;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Size of the container holding a widget, in CSS pixels (spec `containerDimensions`): a
|
|
184
|
+
* fixed `height` or a `maxHeight` bound, and a fixed `width` or a `maxWidth` bound.
|
|
185
|
+
*/
|
|
186
|
+
export type McpAppContainerDimensions = ({
|
|
187
|
+
height: number;
|
|
188
|
+
} | {
|
|
189
|
+
maxHeight?: number;
|
|
190
|
+
}) & ({
|
|
191
|
+
width: number;
|
|
192
|
+
} | {
|
|
193
|
+
maxWidth?: number;
|
|
194
|
+
});
|
|
195
|
+
/** The tool call that instantiated a widget (spec `toolInfo`). */
|
|
196
|
+
export interface McpAppToolInfo {
|
|
197
|
+
id?: string | number;
|
|
198
|
+
tool: {
|
|
199
|
+
name: string;
|
|
200
|
+
inputSchema: {
|
|
201
|
+
type: 'object';
|
|
202
|
+
[key: string]: unknown;
|
|
203
|
+
};
|
|
204
|
+
[key: string]: unknown;
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Theming sent to MCP-app widgets (spec `hostContext.styles`): spec CSS variables (e.g.
|
|
209
|
+
* `--color-background-primary`) resolved from Forge tokens. Font loading (`css.fonts`) is not sent.
|
|
210
|
+
*/
|
|
211
|
+
export interface McpAppHostStyles {
|
|
212
|
+
variables?: Record<string, string>;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Host context projected into an MCP-app widget.
|
|
216
|
+
*
|
|
217
|
+
* Local type — not aliased from ext-apps.
|
|
218
|
+
*/
|
|
219
|
+
export interface McpAppHostContext {
|
|
220
|
+
theme: 'light' | 'dark';
|
|
221
|
+
/** Spec CSS variables resolved from the widget's Forge tokens. */
|
|
222
|
+
styles?: McpAppHostStyles;
|
|
223
|
+
/** The tool call that instantiated the widget. */
|
|
224
|
+
toolInfo?: McpAppToolInfo;
|
|
225
|
+
/** Size of the container holding the widget. */
|
|
226
|
+
containerDimensions?: McpAppContainerDimensions;
|
|
227
|
+
/** The widget's current display mode. */
|
|
228
|
+
displayMode?: McpAppDisplayMode;
|
|
229
|
+
/** Display modes the host is able to present. */
|
|
230
|
+
availableDisplayModes?: McpAppDisplayMode[];
|
|
231
|
+
/** BCP 47 language tag. */
|
|
232
|
+
locale?: string;
|
|
233
|
+
/** IANA time zone. */
|
|
234
|
+
timeZone?: string;
|
|
235
|
+
userAgent?: string;
|
|
236
|
+
platform?: 'web' | 'desktop' | 'mobile';
|
|
237
|
+
deviceCapabilities?: {
|
|
238
|
+
touch?: boolean;
|
|
239
|
+
hover?: boolean;
|
|
240
|
+
};
|
|
241
|
+
safeAreaInsets?: {
|
|
242
|
+
top: number;
|
|
243
|
+
right: number;
|
|
244
|
+
bottom: number;
|
|
245
|
+
left: number;
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/** Content-block types a host accepts for `ui/message` / `ui/update-model-context`. */
|
|
249
|
+
export interface McpAppContentModalities {
|
|
250
|
+
text?: Record<string, never>;
|
|
251
|
+
image?: Record<string, never>;
|
|
252
|
+
audio?: Record<string, never>;
|
|
253
|
+
resource?: Record<string, never>;
|
|
254
|
+
resourceLink?: Record<string, never>;
|
|
255
|
+
structuredContent?: Record<string, never>;
|
|
256
|
+
}
|
|
257
|
+
/** The sandbox policy the host enforces for a widget, echoed in its host capabilities. */
|
|
258
|
+
export interface McpAppSandboxCapabilities {
|
|
259
|
+
csp?: McpAppResourceCsp;
|
|
260
|
+
permissions?: McpAppResourcePermissions;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Host capability advertisement returned to the widget in `McpUiInitializeResult`.
|
|
264
|
+
* Spec-compliant widgets gate `ui/open-link` / `tools/call` / `resources/read` on these,
|
|
265
|
+
* so they must be declared, not left implicit.
|
|
266
|
+
*
|
|
267
|
+
* Local type — not aliased from ext-apps.
|
|
268
|
+
*/
|
|
269
|
+
export interface McpAppHostCapabilities {
|
|
270
|
+
openLinks?: Record<string, never>;
|
|
271
|
+
serverTools?: {
|
|
272
|
+
listChanged?: boolean;
|
|
273
|
+
};
|
|
274
|
+
serverResources?: {
|
|
275
|
+
listChanged?: boolean;
|
|
276
|
+
};
|
|
277
|
+
logging?: Record<string, never>;
|
|
278
|
+
message?: McpAppContentModalities;
|
|
279
|
+
updateModelContext?: McpAppContentModalities;
|
|
280
|
+
sandbox?: McpAppSandboxCapabilities;
|
|
281
|
+
}
|
|
103
282
|
export interface ChatMessage {
|
|
104
283
|
id: string;
|
|
105
284
|
role: 'user' | 'assistant' | 'system' | 'tool';
|
|
@@ -144,11 +323,30 @@ export interface ToolCall<TArgs = Record<string, unknown>> {
|
|
|
144
323
|
args: TArgs;
|
|
145
324
|
argsBuffer?: string;
|
|
146
325
|
result?: unknown;
|
|
147
|
-
status:
|
|
326
|
+
status: ToolCallStatus;
|
|
148
327
|
type: ToolType;
|
|
149
328
|
startTimestamp?: number;
|
|
150
329
|
endTimestamp?: number;
|
|
330
|
+
/**
|
|
331
|
+
* The resolved MCP-app UI resource. When present, this tool call renders as a
|
|
332
|
+
* sandboxed `<forge-ai-mcp-app>` widget instead of the standard tool-call UI. Its
|
|
333
|
+
* presence is the single render discriminator.
|
|
334
|
+
*/
|
|
335
|
+
uiResource?: McpAppUiResource;
|
|
336
|
+
/** Companion structured payload for the widget, stripped from LLM context. */
|
|
337
|
+
structuredContent?: unknown;
|
|
338
|
+
/** `true` when `structuredContent` was dropped by the backend (size caps). */
|
|
339
|
+
structuredContentOmitted?: boolean;
|
|
340
|
+
/** Raw MCP result for an MCP-app tool call; the source of the View's `tool-result`. */
|
|
341
|
+
mcpResult?: McpToolResult;
|
|
342
|
+
/** `true` when `mcpResult` was dropped by the backend (size caps). */
|
|
343
|
+
mcpResultOmitted?: boolean;
|
|
151
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* Tool call lifecycle. `cancelled` means the call ended without a result (run aborted,
|
|
347
|
+
* stream ended/errored, or the adapter reported `cancelled: true`).
|
|
348
|
+
*/
|
|
349
|
+
export type ToolCallStatus = 'pending' | 'parsing' | 'executing' | 'complete' | 'error' | 'cancelled';
|
|
152
350
|
export type ResponseItem = {
|
|
153
351
|
type: 'text';
|
|
154
352
|
messageId: string;
|
|
@@ -10,7 +10,7 @@ function shouldShowToolRenderer(toolCall, toolDefinition) {
|
|
|
10
10
|
return !!toolDefinition.renderOnStart && (toolCall.status === "parsing" || toolCall.status === "executing");
|
|
11
11
|
}
|
|
12
12
|
function isToolCallSettled(toolCall, toolDefinition) {
|
|
13
|
-
return toolCall.status === "complete" || toolCall.status === "error" || shouldShowToolRenderer(toolCall, toolDefinition);
|
|
13
|
+
return toolCall.status === "complete" || toolCall.status === "error" || toolCall.status === "cancelled" || shouldShowToolRenderer(toolCall, toolDefinition);
|
|
14
14
|
}
|
|
15
15
|
function generateId() {
|
|
16
16
|
return v4();
|
|
@@ -49,6 +49,8 @@ function formatToolCallForExport(toolCall) {
|
|
|
49
49
|
if (toolCall.status === "error") {
|
|
50
50
|
const errorMsg = typeof toolCall.result === "object" && toolCall.result !== null && "error" in toolCall.result ? toolCall.result.error : "Unknown error";
|
|
51
51
|
lines.push(` Error: ${errorMsg}`);
|
|
52
|
+
} else if (toolCall.status === "cancelled") {
|
|
53
|
+
lines.push(" Cancelled");
|
|
52
54
|
} else if (toolCall.result !== void 0) {
|
|
53
55
|
lines.push(` Result: ${JSON.stringify(toolCall.result)}`);
|
|
54
56
|
}
|