@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.
- package/custom-elements.json +4501 -1781
- 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.d.ts +2 -2
- package/dist/ai-assistant-response/ai-assistant-response.mjs +26 -2
- package/dist/ai-canvas/ai-canvas.d.ts +43 -0
- package/dist/ai-canvas/ai-canvas.mjs +95 -0
- package/dist/ai-canvas/ai-canvas.scss.mjs +4 -0
- package/dist/ai-canvas/index.d.ts +1 -0
- package/dist/ai-canvas/index.mjs +5 -0
- package/dist/ai-chatbot/ag-ui-adapter.d.ts +2 -2
- package/dist/ai-chatbot/ag-ui-adapter.mjs +1 -1
- package/dist/ai-chatbot/agent-adapter.d.ts +111 -4
- package/dist/ai-chatbot/agent-adapter.mjs +22 -0
- package/dist/ai-chatbot/agent-runner.d.ts +2 -2
- package/dist/ai-chatbot/ai-chatbot-base.d.ts +29 -2
- package/dist/ai-chatbot/ai-chatbot-base.mjs +147 -3
- package/dist/ai-chatbot/ai-chatbot-tool-call.d.ts +2 -2
- package/dist/ai-chatbot/ai-chatbot-tool-call.mjs +88 -10
- package/dist/ai-chatbot/ai-chatbot.d.ts +24 -1
- package/dist/ai-chatbot/ai-chatbot.mjs +102 -2
- package/dist/ai-chatbot/ai-chatbot.scss.mjs +1 -1
- package/dist/ai-chatbot/create-tool-renderer.d.ts +5 -5
- package/dist/ai-chatbot/index.d.ts +3 -2
- package/dist/ai-chatbot/index.mjs +3 -0
- 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/tool-canvas.d.ts +60 -0
- package/dist/ai-chatbot/tool-canvas.mjs +128 -0
- package/dist/ai-chatbot/types.d.ts +325 -15
- package/dist/ai-chatbot/utils.d.ts +8 -3
- package/dist/ai-chatbot/utils.mjs +7 -1
- package/dist/ai-chatbot-launcher/ai-chatbot-launcher.d.ts +40 -2
- package/dist/ai-chatbot-launcher/ai-chatbot-launcher.mjs +224 -7
- package/dist/ai-chatbot-launcher/ai-chatbot-launcher.scss.mjs +1 -1
- package/dist/ai-embedded-chat/ai-embedded-chat.mjs +1 -0
- package/dist/ai-message-thread/ai-message-thread.d.ts +3 -2
- package/dist/ai-message-thread/ai-message-thread.mjs +36 -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.d.ts +2 -2
- package/dist/ai-tool-call-indicator/ai-tool-call-indicator.mjs +1 -1
- package/dist/core/canvas-controller.d.ts +81 -0
- package/dist/core/canvas-controller.mjs +221 -0
- package/dist/core/chatbot-core-controller.d.ts +4 -2
- package/dist/core/chatbot-core-controller.mjs +121 -4
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.mjs +2 -0
- package/dist/core/resize-controller.d.ts +5 -0
- package/dist/core/resize-controller.mjs +7 -5
- package/dist/index.d.ts +4 -0
- package/dist/index.mjs +15 -0
- package/dist/tools/ai-data-table/ai-data-table-definition.mjs +1 -0
- package/dist/tools/ai-mcp-app/ai-mcp-app.d.ts +70 -0
- package/dist/tools/ai-mcp-app/ai-mcp-app.mjs +589 -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 +60 -0
- package/dist/tools/ai-mcp-app/mcp-app-bridge-controller.mjs +147 -0
- package/dist/tools/ai-mcp-app/mcp-app-bridge-types.d.ts +124 -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 +51 -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
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { ContextProvider } from "@lit/context";
|
|
2
|
+
import { toolCanvasHostContext, resolveToolCanvasTitle, createToolCanvas } from "../ai-chatbot/tool-canvas.mjs";
|
|
3
|
+
import { shouldShowToolRenderer } from "../ai-chatbot/utils.mjs";
|
|
4
|
+
class CanvasController {
|
|
5
|
+
constructor(host, config) {
|
|
6
|
+
this.#autoOpenedToolCallIds = /* @__PURE__ */ new Set();
|
|
7
|
+
this.#isOpen = false;
|
|
8
|
+
this.#content = null;
|
|
9
|
+
this.handleOpenRequest = (evt) => {
|
|
10
|
+
evt.stopPropagation();
|
|
11
|
+
const toolCall = this.#findToolCall(evt.detail.toolCallId);
|
|
12
|
+
const toolDefinition = this.#config.getTools().get(evt.detail.toolName);
|
|
13
|
+
if (!toolCall || !toolDefinition) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
evt.detail.handled = this.#openToolCall({ toolCall, toolDefinition, title: evt.detail.title });
|
|
17
|
+
};
|
|
18
|
+
this.handleCloseRequest = (evt) => {
|
|
19
|
+
evt.stopPropagation();
|
|
20
|
+
evt.detail.handled = this.#closeToolCall(evt.detail.toolCallId);
|
|
21
|
+
};
|
|
22
|
+
this.#host = host;
|
|
23
|
+
this.#config = config;
|
|
24
|
+
host.addController(this);
|
|
25
|
+
this.#provider = new ContextProvider(host, {
|
|
26
|
+
context: toolCanvasHostContext,
|
|
27
|
+
initialValue: { presentation: "inline-expandable" }
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
#host;
|
|
31
|
+
#config;
|
|
32
|
+
#provider;
|
|
33
|
+
#autoOpenedToolCallIds;
|
|
34
|
+
#isOpen;
|
|
35
|
+
#content;
|
|
36
|
+
hostConnected() {
|
|
37
|
+
}
|
|
38
|
+
get isOpen() {
|
|
39
|
+
return this.#isOpen;
|
|
40
|
+
}
|
|
41
|
+
get content() {
|
|
42
|
+
return this.#content;
|
|
43
|
+
}
|
|
44
|
+
open(options = {}) {
|
|
45
|
+
return this.#commitOpen({ id: options.id, title: options.title, node: options.content });
|
|
46
|
+
}
|
|
47
|
+
/** Closes the canvas. Returns false if it was not open or the close event was prevented. */
|
|
48
|
+
close() {
|
|
49
|
+
if (!this.#isOpen) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const event = this.#config.dispatchHostEvent({ type: this.#config.closeEvent, cancelable: true });
|
|
53
|
+
if (event.defaultPrevented) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
this.#hide();
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
/** Closes without asking, for when the host can no longer show the canvas (e.g. it left its conversation view). */
|
|
60
|
+
forceClose() {
|
|
61
|
+
if (!this.#isOpen) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.#hide();
|
|
65
|
+
this.#config.dispatchHostEvent({ type: this.#config.closeEvent });
|
|
66
|
+
}
|
|
67
|
+
/** Forgets the canvas entirely, for when the thread it belonged to is gone. */
|
|
68
|
+
reset() {
|
|
69
|
+
this.forceClose();
|
|
70
|
+
this.#content = null;
|
|
71
|
+
this.#autoOpenedToolCallIds.clear();
|
|
72
|
+
this.#host.requestUpdate();
|
|
73
|
+
}
|
|
74
|
+
/** Tool calls restored from history are marked as seen so loading a thread never auto-opens the canvas. */
|
|
75
|
+
markSeen(messages) {
|
|
76
|
+
for (const message of messages) {
|
|
77
|
+
for (const toolCall of message.toolCalls ?? []) {
|
|
78
|
+
this.#autoOpenedToolCallIds.add(toolCall.id);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** The tool call object is replaced on every args or status update, so open content follows it by id. */
|
|
83
|
+
syncToolCall() {
|
|
84
|
+
const content = this.#content;
|
|
85
|
+
if (!this.#isOpen || !content?.toolCall || !content.toolDefinition) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const latest = this.#findToolCall(content.toolCall.id);
|
|
89
|
+
if (!latest || latest === content.toolCall) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const { toolDefinition } = content;
|
|
93
|
+
const renderer = toolDefinition.canvas?.renderer ?? toolDefinition.renderer;
|
|
94
|
+
let node = content.node;
|
|
95
|
+
if (renderer?.elementTag && node) {
|
|
96
|
+
node.toolCall = latest;
|
|
97
|
+
} else {
|
|
98
|
+
node = this.#createRenderer(latest, toolDefinition);
|
|
99
|
+
}
|
|
100
|
+
this.#content = {
|
|
101
|
+
...content,
|
|
102
|
+
node,
|
|
103
|
+
toolCall: latest,
|
|
104
|
+
title: content.titleOverride ?? resolveToolCanvasTitle(latest, toolDefinition)
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Opens tools configured with `canvas.open: 'auto'` once their renderer shows. Call after render, not during it. */
|
|
108
|
+
autoOpenPending() {
|
|
109
|
+
const tools = this.#config.getTools();
|
|
110
|
+
if (![...tools.values()].some((tool) => tool.canvas?.open === "auto")) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
for (const toolCall of this.#config.getToolCalls()) {
|
|
114
|
+
const toolDefinition = tools.get(toolCall.name);
|
|
115
|
+
if (toolDefinition?.canvas?.open !== "auto" || this.#autoOpenedToolCallIds.has(toolCall.id) || !shouldShowToolRenderer(toolCall, toolDefinition)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
this.#autoOpenedToolCallIds.add(toolCall.id);
|
|
119
|
+
this.#openToolCall({ toolCall, toolDefinition, reason: "auto" });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
#findToolCall(id) {
|
|
123
|
+
return this.#config.getToolCalls().find((toolCall) => toolCall.id === id);
|
|
124
|
+
}
|
|
125
|
+
#commitOpen(content, reason = "request") {
|
|
126
|
+
if (this.#config.canOpen && !this.#config.canOpen()) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const event = this.#config.dispatchHostEvent({
|
|
130
|
+
type: this.#config.openEvent,
|
|
131
|
+
detail: { id: content.id, title: content.title, toolCall: content.toolCall },
|
|
132
|
+
cancelable: true
|
|
133
|
+
});
|
|
134
|
+
if (event.defaultPrevented) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
this.#content = content;
|
|
138
|
+
if (!this.#isOpen) {
|
|
139
|
+
this.#isOpen = true;
|
|
140
|
+
this.#config.onOpen?.({ reason });
|
|
141
|
+
}
|
|
142
|
+
this.#syncProvider();
|
|
143
|
+
this.#host.requestUpdate();
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
// Content stays mounted while the host collapses, then is released so hidden iframes and timers stop.
|
|
147
|
+
#hide() {
|
|
148
|
+
this.#isOpen = false;
|
|
149
|
+
this.#config.onClose?.();
|
|
150
|
+
this.#syncProvider();
|
|
151
|
+
this.#host.requestUpdate();
|
|
152
|
+
void this.#releaseAfterCollapse();
|
|
153
|
+
}
|
|
154
|
+
async #releaseAfterCollapse() {
|
|
155
|
+
const content = this.#content;
|
|
156
|
+
await this.#host.updateComplete;
|
|
157
|
+
await this.#config.collapsed?.();
|
|
158
|
+
if (!this.#isOpen && this.#content === content) {
|
|
159
|
+
this.#content = null;
|
|
160
|
+
this.#host.requestUpdate();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
#syncProvider() {
|
|
164
|
+
this.#provider.setValue({
|
|
165
|
+
presentation: "inline-expandable",
|
|
166
|
+
openToolCallId: this.#isOpen ? this.#content?.toolCall?.id : void 0
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
#closeToolCall(toolCallId) {
|
|
170
|
+
if (this.#content?.toolCall?.id !== toolCallId) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
return this.close();
|
|
174
|
+
}
|
|
175
|
+
#openToolCall({
|
|
176
|
+
toolCall,
|
|
177
|
+
toolDefinition,
|
|
178
|
+
title,
|
|
179
|
+
reason
|
|
180
|
+
}) {
|
|
181
|
+
if (this.#isOpen && this.#content?.toolCall?.id === toolCall.id) {
|
|
182
|
+
this.#config.onRefocus?.();
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
return this.#commitOpen(
|
|
186
|
+
{
|
|
187
|
+
id: toolCall.id,
|
|
188
|
+
title: title ?? resolveToolCanvasTitle(toolCall, toolDefinition),
|
|
189
|
+
node: this.#createRenderer(toolCall, toolDefinition),
|
|
190
|
+
toolCall,
|
|
191
|
+
toolDefinition,
|
|
192
|
+
titleOverride: title
|
|
193
|
+
},
|
|
194
|
+
reason
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
#createControls(toolCall, toolDefinition) {
|
|
198
|
+
return createToolCanvas({
|
|
199
|
+
available: true,
|
|
200
|
+
// This instance only exists to be shown in the canvas, and is created before the canvas finishes opening.
|
|
201
|
+
isOpen: () => true,
|
|
202
|
+
open: (options) => this.#openToolCall({ toolCall, toolDefinition, title: options?.title }),
|
|
203
|
+
close: () => this.#closeToolCall(toolCall.id)
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
#createRenderer(toolCall, toolDefinition) {
|
|
207
|
+
const renderer = toolDefinition.canvas?.renderer ?? toolDefinition.renderer;
|
|
208
|
+
const canvas = this.#createControls(toolCall, toolDefinition);
|
|
209
|
+
if (renderer?.elementTag) {
|
|
210
|
+
const element = document.createElement(renderer.elementTag);
|
|
211
|
+
element.toolCall = toolCall;
|
|
212
|
+
element.presentation = "canvas";
|
|
213
|
+
element.canvas = canvas;
|
|
214
|
+
return element;
|
|
215
|
+
}
|
|
216
|
+
return renderer?.render?.(toolCall, { presentation: "canvas", canvas });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
export {
|
|
220
|
+
CanvasController
|
|
221
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
2
|
import { AgentAdapter } from '../ai-chatbot/agent-adapter.js';
|
|
3
|
-
import { ChatMessage, ClientMessageInput, FileAttachment, FileUploadCallbacks, MessageItem, ThreadState, ToolCall,
|
|
3
|
+
import { ChatMessage, ClientMessageInput, FileAttachment, FileUploadCallbacks, MessageItem, ThreadState, ToolCall, AnyToolDefinition } from '../ai-chatbot/types.js';
|
|
4
4
|
export interface ChatbotCoreCallbacks {
|
|
5
5
|
onRequestUpdate: () => void;
|
|
6
6
|
onScrollToBottom: () => void;
|
|
@@ -20,7 +20,7 @@ export declare class ChatbotCoreController implements ReactiveController {
|
|
|
20
20
|
get isStreaming(): boolean;
|
|
21
21
|
get isUploading(): boolean;
|
|
22
22
|
get hasMessages(): boolean;
|
|
23
|
-
get tools(): Map<string,
|
|
23
|
+
get tools(): Map<string, AnyToolDefinition>;
|
|
24
24
|
get pendingAttachments(): FileAttachment[];
|
|
25
25
|
get completedAttachments(): FileAttachment[];
|
|
26
26
|
sendMessage(config: {
|
|
@@ -38,6 +38,8 @@ export declare class ChatbotCoreController implements ReactiveController {
|
|
|
38
38
|
getMessages(): ChatMessage[];
|
|
39
39
|
setMessages(messages: ChatMessage[]): void;
|
|
40
40
|
updateMessageContent(id: string, content: string): void;
|
|
41
|
+
/** Tool call ids rendered at or after `index` in {@link messageItems}. */
|
|
42
|
+
getToolCallIdsFrom(index: number): string[];
|
|
41
43
|
removeMessageItemsFrom(index: number): void;
|
|
42
44
|
setResponseFeedback(responseId: string, feedback: {
|
|
43
45
|
type: 'positive' | 'negative';
|
|
@@ -2,6 +2,8 @@ import { SubscriptionManager } from "../ai-chatbot/event-emitter.mjs";
|
|
|
2
2
|
import { FileUploadManager } from "../ai-chatbot/file-upload-manager.mjs";
|
|
3
3
|
import { MessageStateController } from "../ai-chatbot/message-state-controller.mjs";
|
|
4
4
|
import { generateId } from "../ai-chatbot/utils.mjs";
|
|
5
|
+
const NO_UI_RESOURCE = { uiResource: void 0, uiResourceStatus: void 0 };
|
|
6
|
+
const UNSETTLED_TOOL_CALL_STATUSES = ["pending", "parsing", "executing"];
|
|
5
7
|
class ChatbotCoreController {
|
|
6
8
|
#host;
|
|
7
9
|
#callbacks;
|
|
@@ -10,6 +12,18 @@ class ChatbotCoreController {
|
|
|
10
12
|
#adapterSubscriptions;
|
|
11
13
|
#adapter;
|
|
12
14
|
#executingToolHandlers = 0;
|
|
15
|
+
/** Tool calls announced by an `mcp-ui-resource` event; they only settle via a tool result. */
|
|
16
|
+
#mcpAppToolCallIds = /* @__PURE__ */ new Set();
|
|
17
|
+
/** Agent tool calls marked complete on their `tool-call` chunk, before any result arrived. */
|
|
18
|
+
#presumedCompleteToolCallIds = /* @__PURE__ */ new Set();
|
|
19
|
+
/** UI resource fields (resolve status or resolved resource) whose tool call is not in state yet. */
|
|
20
|
+
#pendingUiResources = /* @__PURE__ */ new Map();
|
|
21
|
+
/**
|
|
22
|
+
* Token of the latest in-flight UI resource resolve per tool call. Removed when that resolve
|
|
23
|
+
* settles, or when the thread is cleared/replaced; a resolve whose token is no longer current
|
|
24
|
+
* is dropped, so the latest announcement wins and stale threads never get stamped.
|
|
25
|
+
*/
|
|
26
|
+
#uiResourceResolves = /* @__PURE__ */ new Map();
|
|
13
27
|
constructor(host, config) {
|
|
14
28
|
this.#host = host;
|
|
15
29
|
this.#callbacks = config.callbacks;
|
|
@@ -83,6 +97,7 @@ class ChatbotCoreController {
|
|
|
83
97
|
this.#adapter.onToolCallEnd(this.#handleToolCallEnd.bind(this)),
|
|
84
98
|
this.#adapter.onToolCall(this.#handleToolCall.bind(this)),
|
|
85
99
|
this.#adapter.onToolCallResult(this.#handleToolCallResult.bind(this)),
|
|
100
|
+
this.#adapter.onMcpUiResource(this.#handleMcpUiResource.bind(this)),
|
|
86
101
|
this.#adapter.onStepStarted(this.#handleStepStarted.bind(this)),
|
|
87
102
|
this.#adapter.onStepFinished(this.#handleStepFinished.bind(this)),
|
|
88
103
|
this.#adapter.onRunFinished(this.#handleRunFinished.bind(this)),
|
|
@@ -115,10 +130,29 @@ class ChatbotCoreController {
|
|
|
115
130
|
#handleRunFinished() {
|
|
116
131
|
this.#tryCompleteResponse();
|
|
117
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* A run ended (finished, aborted, or errored) without delivering a result for an
|
|
135
|
+
* MCP-app tool call: mark it cancelled so the widget receives `tool-cancelled`.
|
|
136
|
+
*/
|
|
137
|
+
#cancelUnsettledMcpAppToolCalls() {
|
|
138
|
+
for (const id of this.#mcpAppToolCallIds) {
|
|
139
|
+
const toolCall = this.#messageStateController.getToolCall(id);
|
|
140
|
+
if (toolCall && UNSETTLED_TOOL_CALL_STATUSES.includes(toolCall.status)) {
|
|
141
|
+
this.#messageStateController.updateToolCallInResponse(id, { status: "cancelled" });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.#mcpAppToolCallIds.clear();
|
|
145
|
+
this.#presumedCompleteToolCallIds.clear();
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Finalizes once the run is truly over. An executing client-tool handler or a running
|
|
149
|
+
* adapter means a continuation follows, so widgets stay open for it to settle (§2).
|
|
150
|
+
*/
|
|
118
151
|
#tryCompleteResponse() {
|
|
119
152
|
if (this.#executingToolHandlers > 0 || this.#adapter?.isRunning) {
|
|
120
153
|
return;
|
|
121
154
|
}
|
|
155
|
+
this.#cancelUnsettledMcpAppToolCalls();
|
|
122
156
|
this.#messageStateController.tryFinalizeResponse();
|
|
123
157
|
const messages = this.getMessages();
|
|
124
158
|
const lastMessage = messages[messages.length - 1];
|
|
@@ -134,7 +168,8 @@ class ChatbotCoreController {
|
|
|
134
168
|
args: {},
|
|
135
169
|
argsBuffer: "",
|
|
136
170
|
status: "parsing",
|
|
137
|
-
type: this.tools.has(event.name) ? "client" : "agent"
|
|
171
|
+
type: this.tools.has(event.name) ? "client" : "agent",
|
|
172
|
+
...this.#takePendingUiResource(event.id)
|
|
138
173
|
};
|
|
139
174
|
this.#messageStateController.addToolCallToResponse(toolCall);
|
|
140
175
|
const toolDef = this.tools.get(event.name);
|
|
@@ -195,7 +230,8 @@ class ChatbotCoreController {
|
|
|
195
230
|
name: event.name,
|
|
196
231
|
args: event.args,
|
|
197
232
|
status: "executing",
|
|
198
|
-
type: this.tools.has(event.name) ? "client" : "agent"
|
|
233
|
+
type: this.tools.has(event.name) ? "client" : "agent",
|
|
234
|
+
...this.#takePendingUiResource(event.id)
|
|
199
235
|
};
|
|
200
236
|
this.#messageStateController.addToolCallToResponse(toolCall);
|
|
201
237
|
}
|
|
@@ -211,7 +247,8 @@ class ChatbotCoreController {
|
|
|
211
247
|
} else {
|
|
212
248
|
this.#sendToolResult(event.id, this.#createToolResponse(event.name));
|
|
213
249
|
}
|
|
214
|
-
} else {
|
|
250
|
+
} else if (!this.#mcpAppToolCallIds.has(event.id)) {
|
|
251
|
+
this.#presumedCompleteToolCallIds.add(event.id);
|
|
215
252
|
this.#messageStateController.updateToolCallInResponse(event.id, { status: "complete" });
|
|
216
253
|
}
|
|
217
254
|
}
|
|
@@ -249,6 +286,7 @@ class ChatbotCoreController {
|
|
|
249
286
|
}
|
|
250
287
|
}
|
|
251
288
|
#handleError(event) {
|
|
289
|
+
this.#cancelUnsettledMcpAppToolCalls();
|
|
252
290
|
const errorMessage = {
|
|
253
291
|
id: generateId(),
|
|
254
292
|
role: "assistant",
|
|
@@ -261,6 +299,7 @@ class ChatbotCoreController {
|
|
|
261
299
|
this.#callbacks.onDispatchEvent("forge-ai-chatbot-error", { error: event.message });
|
|
262
300
|
}
|
|
263
301
|
#handleRunAborted() {
|
|
302
|
+
this.#cancelUnsettledMcpAppToolCalls();
|
|
264
303
|
this.#messageStateController.tryFinalizeResponse();
|
|
265
304
|
const id = this.#messageStateController.addClientMessage({ content: "Run cancelled" });
|
|
266
305
|
const abortMessage = this.#messageStateController.getMessage(id);
|
|
@@ -272,8 +311,69 @@ class ChatbotCoreController {
|
|
|
272
311
|
this.#callbacks.onRequestUpdate();
|
|
273
312
|
}
|
|
274
313
|
#handleToolCallResult(event) {
|
|
314
|
+
this.#presumedCompleteToolCallIds.delete(event.toolCallId);
|
|
275
315
|
this.#messageStateController.completeToolCallInResponse(event.toolCallId, event.result, event);
|
|
276
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Marks the tool call as a loading MCP app, then resolves and stamps the UI resource.
|
|
319
|
+
* Works whether the event arrives before or after the tool call's chunks, mid-run, after
|
|
320
|
+
* the run ended, or after `setMessages` for tool calls restored from history. A resolve
|
|
321
|
+
* superseded by a newer announcement, or outliving its thread (cleared/replaced), is
|
|
322
|
+
* dropped; one that outlives its adapter falls back to a plain tool call.
|
|
323
|
+
*/
|
|
324
|
+
async #handleMcpUiResource(event) {
|
|
325
|
+
const adapter = this.#adapter;
|
|
326
|
+
if (!adapter?.resolveMcpAppResource) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const { toolCallId, resourceUri } = event;
|
|
330
|
+
const token = Symbol(toolCallId);
|
|
331
|
+
this.#uiResourceResolves.set(toolCallId, token);
|
|
332
|
+
this.#mcpAppToolCallIds.add(toolCallId);
|
|
333
|
+
if (this.#presumedCompleteToolCallIds.delete(toolCallId)) {
|
|
334
|
+
this.#messageStateController.updateToolCallInResponse(toolCallId, {
|
|
335
|
+
status: "executing",
|
|
336
|
+
endTimestamp: void 0
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
this.#stampUiResource(toolCallId, { uiResourceStatus: "loading" });
|
|
340
|
+
const fields = await this.#resolveUiResource(adapter, { resourceUri, toolCallId });
|
|
341
|
+
if (this.#uiResourceResolves.get(toolCallId) !== token) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
this.#uiResourceResolves.delete(toolCallId);
|
|
345
|
+
this.#stampUiResource(toolCallId, adapter === this.#adapter ? fields : NO_UI_RESOURCE);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* A resolved resource replaces the status; `undefined` (MCP apps unavailable) clears both so
|
|
349
|
+
* the call renders as a plain tool call; a throw marks the widget as failed.
|
|
350
|
+
*/
|
|
351
|
+
async #resolveUiResource(adapter, params) {
|
|
352
|
+
try {
|
|
353
|
+
const uiResource = await adapter.resolveMcpAppResource?.(params);
|
|
354
|
+
return uiResource ? { uiResource, uiResourceStatus: void 0 } : NO_UI_RESOURCE;
|
|
355
|
+
} catch (error) {
|
|
356
|
+
console.warn(`[forge-ai-mcp-app] Failed to resolve UI resource for tool call ${params.toolCallId}:`, error);
|
|
357
|
+
return { uiResource: void 0, uiResourceStatus: "error" };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
#stampUiResource(toolCallId, fields) {
|
|
361
|
+
if (this.#messageStateController.getToolCall(toolCallId)) {
|
|
362
|
+
this.#messageStateController.updateToolCallInResponse(toolCallId, fields);
|
|
363
|
+
} else if (fields.uiResource || fields.uiResourceStatus) {
|
|
364
|
+
this.#pendingUiResources.set(toolCallId, fields);
|
|
365
|
+
} else {
|
|
366
|
+
this.#pendingUiResources.delete(toolCallId);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
#takePendingUiResource(toolCallId) {
|
|
370
|
+
const fields = this.#pendingUiResources.get(toolCallId);
|
|
371
|
+
if (!fields) {
|
|
372
|
+
return {};
|
|
373
|
+
}
|
|
374
|
+
this.#pendingUiResources.delete(toolCallId);
|
|
375
|
+
return fields;
|
|
376
|
+
}
|
|
277
377
|
async #sendToolResult(toolCallId, result) {
|
|
278
378
|
const toolCall = this.#messageStateController.getToolCall(toolCallId);
|
|
279
379
|
if (!toolCall || !this.#adapter) {
|
|
@@ -366,6 +466,10 @@ class ChatbotCoreController {
|
|
|
366
466
|
return this.#messageStateController.getToolCall(id);
|
|
367
467
|
}
|
|
368
468
|
clearMessages() {
|
|
469
|
+
this.#mcpAppToolCallIds.clear();
|
|
470
|
+
this.#presumedCompleteToolCallIds.clear();
|
|
471
|
+
this.#pendingUiResources.clear();
|
|
472
|
+
this.#uiResourceResolves.clear();
|
|
369
473
|
this.#messageStateController.clearMessages();
|
|
370
474
|
this.#fileUploadManager.clear();
|
|
371
475
|
}
|
|
@@ -373,13 +477,26 @@ class ChatbotCoreController {
|
|
|
373
477
|
return this.#messageStateController.getMessages();
|
|
374
478
|
}
|
|
375
479
|
setMessages(messages) {
|
|
480
|
+
this.#mcpAppToolCallIds.clear();
|
|
481
|
+
this.#presumedCompleteToolCallIds.clear();
|
|
482
|
+
this.#pendingUiResources.clear();
|
|
483
|
+
this.#uiResourceResolves.clear();
|
|
376
484
|
this.#messageStateController.setMessages(messages);
|
|
377
485
|
}
|
|
378
486
|
updateMessageContent(id, content) {
|
|
379
487
|
this.#messageStateController.updateMessageContent(id, content);
|
|
380
488
|
}
|
|
489
|
+
/** Tool call ids rendered at or after `index` in {@link messageItems}. */
|
|
490
|
+
getToolCallIdsFrom(index) {
|
|
491
|
+
return this.#messageStateController.getToolCallIdsFrom(index);
|
|
492
|
+
}
|
|
381
493
|
removeMessageItemsFrom(index) {
|
|
382
|
-
this.#messageStateController.removeMessageItemsFrom(index)
|
|
494
|
+
for (const id of this.#messageStateController.removeMessageItemsFrom(index)) {
|
|
495
|
+
this.#mcpAppToolCallIds.delete(id);
|
|
496
|
+
this.#presumedCompleteToolCallIds.delete(id);
|
|
497
|
+
this.#pendingUiResources.delete(id);
|
|
498
|
+
this.#uiResourceResolves.delete(id);
|
|
499
|
+
}
|
|
383
500
|
}
|
|
384
501
|
setResponseFeedback(responseId, feedback) {
|
|
385
502
|
this.#messageStateController.setResponseFeedback(responseId, feedback);
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { CanvasController, type CanvasControllerConfig, type CanvasContent, type CanvasOpenEventData, type OpenCanvasOptions } from './canvas-controller.js';
|
|
1
2
|
export { DragController, type DragControllerOptions } from './drag-controller.js';
|
|
2
|
-
export { ResizeController, type ResizeControllerOptions } from './resize-controller.js';
|
|
3
|
+
export { ResizeController, type ResizeControllerOptions, type ResizeGrowDirection } from './resize-controller.js';
|
|
3
4
|
export { ChatbotCoreController, type ChatbotCoreCallbacks, type ChatbotCoreControllerConfig } from './chatbot-core-controller.js';
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { CanvasController } from "./canvas-controller.mjs";
|
|
1
2
|
import { DragController } from "./drag-controller.mjs";
|
|
2
3
|
import { ResizeController } from "./resize-controller.mjs";
|
|
3
4
|
import { ChatbotCoreController } from "./chatbot-core-controller.mjs";
|
|
4
5
|
export {
|
|
6
|
+
CanvasController,
|
|
5
7
|
ChatbotCoreController,
|
|
6
8
|
DragController,
|
|
7
9
|
ResizeController
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
2
|
import { Ref } from 'lit/directives/ref.js';
|
|
3
|
+
export type ResizeGrowDirection = 'left' | 'right';
|
|
3
4
|
export interface ResizeControllerOptions {
|
|
4
5
|
targetElementRef: Ref<HTMLElement>;
|
|
5
6
|
resizeHandleRef: Ref<HTMLElement>;
|
|
@@ -7,6 +8,10 @@ export interface ResizeControllerOptions {
|
|
|
7
8
|
maxWidth?: number;
|
|
8
9
|
defaultWidth?: number;
|
|
9
10
|
keyboardStep?: number;
|
|
11
|
+
/** Screen direction the handle moves to make the width larger. Defaults to `'left'`, for a panel docked to the right. */
|
|
12
|
+
growDirection?: () => ResizeGrowDirection;
|
|
13
|
+
/** Widest the target may be given its container. Defaults to the window width minus a small buffer. */
|
|
14
|
+
getAvailableWidth?: () => number;
|
|
10
15
|
onResize?: (width: number) => void;
|
|
11
16
|
onResizeStart?: () => void;
|
|
12
17
|
onResizeEnd?: () => void;
|
|
@@ -24,13 +24,14 @@ class ResizeController {
|
|
|
24
24
|
};
|
|
25
25
|
this.handleKeyDown = (event) => {
|
|
26
26
|
const step = this.#options.keyboardStep;
|
|
27
|
+
const growSign = this.#options.growDirection() === "left" ? 1 : -1;
|
|
27
28
|
let delta = 0;
|
|
28
29
|
switch (event.key) {
|
|
29
30
|
case "ArrowLeft":
|
|
30
|
-
delta = step;
|
|
31
|
+
delta = step * growSign;
|
|
31
32
|
break;
|
|
32
33
|
case "ArrowRight":
|
|
33
|
-
delta = -step;
|
|
34
|
+
delta = -step * growSign;
|
|
34
35
|
break;
|
|
35
36
|
default:
|
|
36
37
|
return;
|
|
@@ -46,7 +47,7 @@ class ResizeController {
|
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
48
49
|
event.preventDefault();
|
|
49
|
-
const deltaX = this.#dragStartX - event.clientX;
|
|
50
|
+
const deltaX = this.#options.growDirection() === "left" ? this.#dragStartX - event.clientX : event.clientX - this.#dragStartX;
|
|
50
51
|
const newWidth = this.#dragStartWidth + deltaX;
|
|
51
52
|
this.#currentWidth = this.#constrainWidth(newWidth);
|
|
52
53
|
this.#applyWidth();
|
|
@@ -72,6 +73,8 @@ class ResizeController {
|
|
|
72
73
|
maxWidth: 800,
|
|
73
74
|
defaultWidth: 420,
|
|
74
75
|
keyboardStep: 20,
|
|
76
|
+
growDirection: () => "left",
|
|
77
|
+
getAvailableWidth: () => window.innerWidth - RESIZE_SCREEN_BUFFER,
|
|
75
78
|
onResize: () => {
|
|
76
79
|
},
|
|
77
80
|
onResizeStart: () => {
|
|
@@ -118,8 +121,7 @@ class ResizeController {
|
|
|
118
121
|
}
|
|
119
122
|
}
|
|
120
123
|
#constrainWidth(width) {
|
|
121
|
-
const
|
|
122
|
-
const effectiveMaxWidth = Math.min(this.#options.maxWidth, availableWidth);
|
|
124
|
+
const effectiveMaxWidth = Math.min(this.#options.maxWidth, this.#options.getAvailableWidth());
|
|
123
125
|
return Math.max(this.#options.minWidth, Math.min(width, effectiveMaxWidth));
|
|
124
126
|
}
|
|
125
127
|
#applyWidth() {
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,11 @@ export * from './ai-agent-info';
|
|
|
5
5
|
export * from './ai-agent-selector';
|
|
6
6
|
export * from './ai-tool-call-indicator';
|
|
7
7
|
export * from './ai-artifact';
|
|
8
|
+
export * from './ai-artifact-card';
|
|
9
|
+
export * from './ai-artifact-card-group';
|
|
8
10
|
export * from './ai-attachment';
|
|
9
11
|
export * from './ai-button';
|
|
12
|
+
export * from './ai-canvas';
|
|
10
13
|
export * from './ai-chain-of-thought';
|
|
11
14
|
export * from './ai-chat-header';
|
|
12
15
|
export * from './ai-chat-interface';
|
|
@@ -40,6 +43,7 @@ export * from './ai-response-message';
|
|
|
40
43
|
export * from './ai-sidebar';
|
|
41
44
|
export * from './ai-sidebar-chat';
|
|
42
45
|
export * from './ai-slash-command-menu';
|
|
46
|
+
export * from './ai-steps';
|
|
43
47
|
export * from './ai-suggestions';
|
|
44
48
|
export * from './ai-thinking-indicator';
|
|
45
49
|
export * from './ai-threads';
|
package/dist/index.mjs
CHANGED
|
@@ -5,8 +5,11 @@ import { AiAgentInfoComponent, AiAgentInfoComponentTagName } from "./ai-agent-in
|
|
|
5
5
|
import { AiAgentSelectorComponent, AiAgentSelectorComponentTagName } from "./ai-agent-selector/ai-agent-selector.mjs";
|
|
6
6
|
import { AiToolCallIndicatorComponent, AiToolCallIndicatorComponentTagName } from "./ai-tool-call-indicator/ai-tool-call-indicator.mjs";
|
|
7
7
|
import { AiArtifactComponent, AiArtifactComponentTagName } from "./ai-artifact/ai-artifact.mjs";
|
|
8
|
+
import { AiArtifactCardComponent, AiArtifactCardComponentTagName } from "./ai-artifact-card/ai-artifact-card.mjs";
|
|
9
|
+
import { AiArtifactCardGroupComponent, AiArtifactCardGroupComponentTagName } from "./ai-artifact-card-group/ai-artifact-card-group.mjs";
|
|
8
10
|
import { AiAttachmentComponent, AiAttachmentComponentTagName } from "./ai-attachment/ai-attachment.mjs";
|
|
9
11
|
import { AiButtonComponent, AiButtonComponentTagName } from "./ai-button/ai-button.mjs";
|
|
12
|
+
import { AiCanvasComponent, AiCanvasComponentTagName } from "./ai-canvas/ai-canvas.mjs";
|
|
10
13
|
import { AiChainOfThoughtComponent, AiChainOfThoughtComponentTagName } from "./ai-chain-of-thought/ai-chain-of-thought.mjs";
|
|
11
14
|
import { ThoughtBaseComponent, ThoughtBaseComponentTagName } from "./ai-chain-of-thought/thought-base/thought-base.mjs";
|
|
12
15
|
import { ThoughtSearchResultComponent, ThoughtSearchResultComponentTagName } from "./ai-chain-of-thought/thought-search-result/thought-search-result.mjs";
|
|
@@ -23,6 +26,7 @@ import { HttpAgentWithCredentials } from "./ai-chatbot/http-agent-with-credentia
|
|
|
23
26
|
import { EventEmitter, SubscriptionManager } from "./ai-chatbot/event-emitter.mjs";
|
|
24
27
|
import { AgentRunner } from "./ai-chatbot/agent-runner.mjs";
|
|
25
28
|
import { createToolRenderer } from "./ai-chatbot/create-tool-renderer.mjs";
|
|
29
|
+
import { requestCanvasClose, requestCanvasOpen } from "./ai-chatbot/tool-canvas.mjs";
|
|
26
30
|
import { MessageStateController } from "./ai-chatbot/message-state-controller.mjs";
|
|
27
31
|
import { FileUploadManager } from "./ai-chatbot/file-upload-manager.mjs";
|
|
28
32
|
import { MarkdownStreamController } from "./ai-chatbot/markdown-stream-controller.mjs";
|
|
@@ -61,6 +65,7 @@ import { AiResponseMessageComponent, AiResponseMessageComponentTagName } from ".
|
|
|
61
65
|
import { AiSidebarComponent, AiSidebarComponentTagName } from "./ai-sidebar/ai-sidebar.mjs";
|
|
62
66
|
import { AiSidebarChatComponent, AiSidebarChatComponentTagName } from "./ai-sidebar-chat/ai-sidebar-chat.mjs";
|
|
63
67
|
import { AiSlashCommandMenuComponent, AiSlashCommandMenuComponentTagName } from "./ai-slash-command-menu/ai-slash-command-menu.mjs";
|
|
68
|
+
import { AiStepsComponent, AiStepsComponentTagName } from "./ai-steps/ai-steps.mjs";
|
|
64
69
|
import { AiSuggestionsComponent, AiSuggestionsComponentTagName } from "./ai-suggestions/ai-suggestions.mjs";
|
|
65
70
|
import { AiThinkingIndicatorComponent, AiThinkingIndicatorComponentTagName } from "./ai-thinking-indicator/ai-thinking-indicator.mjs";
|
|
66
71
|
import { AiThreadsComponent, AiThreadsComponentTagName } from "./ai-threads/ai-threads.mjs";
|
|
@@ -77,6 +82,10 @@ export {
|
|
|
77
82
|
AiAgentInfoComponentTagName,
|
|
78
83
|
AiAgentSelectorComponent,
|
|
79
84
|
AiAgentSelectorComponentTagName,
|
|
85
|
+
AiArtifactCardComponent,
|
|
86
|
+
AiArtifactCardComponentTagName,
|
|
87
|
+
AiArtifactCardGroupComponent,
|
|
88
|
+
AiArtifactCardGroupComponentTagName,
|
|
80
89
|
AiArtifactComponent,
|
|
81
90
|
AiArtifactComponentTagName,
|
|
82
91
|
AiAssistantResponseComponent,
|
|
@@ -85,6 +94,8 @@ export {
|
|
|
85
94
|
AiAttachmentComponentTagName,
|
|
86
95
|
AiButtonComponent,
|
|
87
96
|
AiButtonComponentTagName,
|
|
97
|
+
AiCanvasComponent,
|
|
98
|
+
AiCanvasComponentTagName,
|
|
88
99
|
AiChainOfThoughtComponent,
|
|
89
100
|
AiChainOfThoughtComponentTagName,
|
|
90
101
|
AiChatHeaderComponent,
|
|
@@ -152,6 +163,8 @@ export {
|
|
|
152
163
|
AiSidebarComponentTagName,
|
|
153
164
|
AiSlashCommandMenuComponent,
|
|
154
165
|
AiSlashCommandMenuComponentTagName,
|
|
166
|
+
AiStepsComponent,
|
|
167
|
+
AiStepsComponentTagName,
|
|
155
168
|
AiSuggestionsComponent,
|
|
156
169
|
AiSuggestionsComponentTagName,
|
|
157
170
|
AiThinkingIndicatorComponent,
|
|
@@ -194,6 +207,8 @@ export {
|
|
|
194
207
|
generateId,
|
|
195
208
|
renderInlineMarkdown,
|
|
196
209
|
renderMarkdown,
|
|
210
|
+
requestCanvasClose,
|
|
211
|
+
requestCanvasOpen,
|
|
197
212
|
toRequestMessages,
|
|
198
213
|
toRequestToolCall
|
|
199
214
|
};
|
|
@@ -5,6 +5,7 @@ import "@ag-ui/client";
|
|
|
5
5
|
import "uuid";
|
|
6
6
|
import "../../utils/markdown.mjs";
|
|
7
7
|
import { createToolRenderer } from "../../ai-chatbot/create-tool-renderer.mjs";
|
|
8
|
+
import "../../ai-chatbot/tool-canvas.mjs";
|
|
8
9
|
const displayDataTableTool = {
|
|
9
10
|
name: "displayDataTable",
|
|
10
11
|
displayName: "Display Data Table",
|