@mcp-b/react-components 0.2.2 → 0.4.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.
Files changed (61) hide show
  1. package/dist/{CodeBlock-BrCoypnc.js → CodeBlock-C2Xjxu9M.js} +1 -1
  2. package/dist/FileTree-D0c9Yj9U.js +310 -0
  3. package/dist/attachment-preview-ChtC3HiU.js +25 -0
  4. package/dist/components/Activity.js +1 -1
  5. package/dist/components/AgentChat.d.ts +56 -9
  6. package/dist/components/AgentChat.js +146 -47
  7. package/dist/components/AgentConnection.js +3 -3
  8. package/dist/components/Artifact.js +1 -1
  9. package/dist/components/BrowserLiveView.d.ts +47 -0
  10. package/dist/components/BrowserLiveView.js +135 -0
  11. package/dist/components/BrowserReplay.d.ts +40 -0
  12. package/dist/components/BrowserReplay.js +187 -0
  13. package/dist/components/ChainOfThought.d.ts +1 -1
  14. package/dist/components/ChainOfThought.js +1 -1
  15. package/dist/components/CodeBlock.js +1 -1
  16. package/dist/components/Commit.js +1 -1
  17. package/dist/components/Context.d.ts +28 -27
  18. package/dist/components/Context.js +48 -47
  19. package/dist/components/ContextMenu.js +1 -1
  20. package/dist/components/Conversation.js +1 -1
  21. package/dist/components/Dialog.js +1 -1
  22. package/dist/components/EnvironmentVariables.js +1 -1
  23. package/dist/components/FileTree.js +1 -309
  24. package/dist/components/Menubar.js +1 -1
  25. package/dist/components/Message.d.ts +17 -2
  26. package/dist/components/Message.js +36 -2
  27. package/dist/components/ModelSelector.js +1 -1
  28. package/dist/components/NavigationMenu.js +1 -1
  29. package/dist/components/PricingPage.js +2 -2
  30. package/dist/components/PromptInput.d.ts +143 -10
  31. package/dist/components/PromptInput.js +318 -5
  32. package/dist/components/Queue.js +1 -1
  33. package/dist/components/Reasoning.js +1 -1
  34. package/dist/components/Select.js +1 -1
  35. package/dist/components/Sources.js +1 -1
  36. package/dist/components/Stepper.js +1 -1
  37. package/dist/components/Suggestion.d.ts +43 -0
  38. package/dist/components/Suggestion.js +39 -0
  39. package/dist/components/Task.js +1 -1
  40. package/dist/components/Terminal.js +2 -2
  41. package/dist/components/TestResults.js +1 -1
  42. package/dist/components/ThinkChat.d.ts +207 -0
  43. package/dist/components/ThinkChat.js +798 -0
  44. package/dist/components/Tool.d.ts +32 -28
  45. package/dist/components/Tool.js +29 -16
  46. package/dist/components/WebPreview.js +1 -1
  47. package/dist/components/WorkspaceExplorer.d.ts +102 -0
  48. package/dist/components/WorkspaceExplorer.js +352 -0
  49. package/dist/components/icons.d.ts +2 -2
  50. package/dist/components/icons.js +2 -2
  51. package/dist/{copy-button-DAPlB8Z0.js → copy-button-Dd6DEhOm.js} +1 -1
  52. package/dist/{icons-Bf7JMKk-.js → icons-DQC4Zw5R.js} +5 -1
  53. package/dist/{icons-BklFyZbx.d.ts → icons-ctdVxng0.d.ts} +2 -1
  54. package/dist/styles/agent-chat.css +19 -0
  55. package/dist/styles/attachment-preview.css +21 -0
  56. package/dist/styles/context.css +9 -17
  57. package/dist/styles/index.css +2 -0
  58. package/dist/styles/message.css +48 -0
  59. package/dist/styles/prompt-input.css +321 -0
  60. package/dist/styles/suggestions.css +21 -0
  61. package/package.json +26 -3
@@ -0,0 +1,798 @@
1
+ import { Activity, ActivityContent, ActivityItem, ActivityItemGroup, ActivityOutput, ActivityPanel, ActivityText, ActivityTrigger } from "./Activity.js";
2
+ import { Tab, TabPanel, Tabs, TabsList } from "./Tabs.js";
3
+ import { t as CodeBlock } from "../CodeBlock-C2Xjxu9M.js";
4
+ import { AgentChat } from "./AgentChat.js";
5
+ import { BrowserLiveView } from "./BrowserLiveView.js";
6
+ import { BrowserReplay } from "./BrowserReplay.js";
7
+ import { WorkspaceExplorer, inferWorkspaceLanguage, useWorkspace } from "./WorkspaceExplorer.js";
8
+ import * as React from "react";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ import { getToolName, isReasoningUIPart, isToolUIPart } from "ai";
11
+ import { getToolPartState, useAgentChat } from "@cloudflare/think/react";
12
+ import { useAgent } from "agents/react";
13
+ //#region src/components/ThinkChat.tsx
14
+ /**
15
+ * Think-specific chat rendering: interprets a Think agent's UIMessage stream
16
+ * (reasoning + workspace/skill/fetch/browser/execute tool parts) into
17
+ * Activity blocks.
18
+ *
19
+ * Every shape here mirrors an upstream contract — see docs/think-chat.md
20
+ * ("Upstream source map" and the re-alignment checklist) for where each comes from,
21
+ * which are compile-time typed vs. hand-rolled mirrors of untyped upstream
22
+ * ToolSets, and how to re-align when @cloudflare/think (experimental) changes.
23
+ */
24
+ const WORKSPACE_TOOL_NAMES = new Set([
25
+ "read",
26
+ "write",
27
+ "edit",
28
+ "list",
29
+ "find",
30
+ "grep",
31
+ "delete",
32
+ "bash"
33
+ ]);
34
+ function isWorkspaceToolPart(part) {
35
+ return isToolUIPart(part) && WORKSPACE_TOOL_NAMES.has(getToolName(part));
36
+ }
37
+ const SKILL_TOOL_NAMES = new Set([
38
+ "activate_skill",
39
+ "read_skill_resource",
40
+ "run_skill_script"
41
+ ]);
42
+ function isSkillToolPart(part) {
43
+ return isToolUIPart(part) && SKILL_TOOL_NAMES.has(getToolName(part));
44
+ }
45
+ function isFetchToolPart(part) {
46
+ return isToolUIPart(part) && getToolName(part).startsWith("fetch_");
47
+ }
48
+ const BROWSER_TOOL_NAMES = new Set([
49
+ "browser_execute",
50
+ "browser_markdown",
51
+ "browser_extract",
52
+ "browser_links",
53
+ "browser_scrape",
54
+ "browser_content"
55
+ ]);
56
+ function isBrowserToolPart(part) {
57
+ return isToolUIPart(part) && BROWSER_TOOL_NAMES.has(getToolName(part));
58
+ }
59
+ function isExecuteToolPart(part) {
60
+ return isToolUIPart(part) && getToolName(part) === "execute";
61
+ }
62
+ const ThinkChatContext = React.createContext(null);
63
+ function useThinkChatContext(component) {
64
+ const context = React.useContext(ThinkChatContext);
65
+ if (!context) throw new Error(`${component} must be used inside <ThinkChat.Provider> or <ThinkChat.Root>.`);
66
+ return context;
67
+ }
68
+ function toAgentChatStatus(chat) {
69
+ if (chat.status === "submitted") return "submitted";
70
+ if (chat.isStreaming || chat.isRecovering) return "streaming";
71
+ return chat.status ?? "ready";
72
+ }
73
+ /**
74
+ * Chat UI for Cloudflare Think agents. Provide a `useAgentChat` value (or let
75
+ * `ThinkChat.Root` create one) and `ThinkChat.Messages` renders the message
76
+ * stream with agent work folded into collapsed Activity blocks.
77
+ */
78
+ function ThinkChatProvider({ chat, children }) {
79
+ return /* @__PURE__ */ jsx(ThinkChatContext.Provider, {
80
+ value: chat,
81
+ children
82
+ });
83
+ }
84
+ /**
85
+ * Batteries-included root: connects to the named Think agent (upstream's
86
+ * `useAgent` + `useAgentChat`) and provides the live chat to everything
87
+ * inside. If the app already owns the chat hook, use `ThinkChat.Provider`.
88
+ *
89
+ * @example
90
+ * ```tsx
91
+ * <ThinkChat.Root agent="my-agent" name="session-1">
92
+ * <ThinkChat.Messages />
93
+ * </ThinkChat.Root>
94
+ * ```
95
+ */
96
+ function ThinkChatRoot({ agent: agentOption, name, children }) {
97
+ return /* @__PURE__ */ jsx(ThinkChatProvider, {
98
+ chat: useAgentChat({ agent: useAgent(typeof agentOption === "string" ? {
99
+ agent: agentOption,
100
+ name
101
+ } : {
102
+ ...agentOption,
103
+ name: name ?? agentOption.name
104
+ }) }),
105
+ children
106
+ });
107
+ }
108
+ /**
109
+ * The full conversation surface — message list plus composer — with the
110
+ * activity renderer as the default `renderPart`: runs of reasoning/tool parts
111
+ * fold into one collapsed Activity block with derived labels and per-tool
112
+ * panels. Extend for app-specific tools with `toolRenderers` (label/panel
113
+ * inside the log) or `excludeTools` + your own `renderPart` (standalone
114
+ * card) — see README "App-specific tools" and the Custom Tool Panel story.
115
+ */
116
+ function ThinkChatMessages({ renderPart, excludeTools, toolRenderers, executionApprovals, notice, ...props }) {
117
+ const chat = useThinkChatContext("ThinkChat.Messages");
118
+ const status = toAgentChatStatus(chat);
119
+ const defaultRenderPart = React.useMemo(() => createThinkChatActivityRenderer({
120
+ excludeTools,
121
+ toolRenderers
122
+ }), [excludeTools, toolRenderers]);
123
+ const connectionNotice = chat.isRecovering ? /* @__PURE__ */ jsx(Fragment, { children: "Reconnecting to the agent…" }) : chat.connectionError ? /* @__PURE__ */ jsxs("span", {
124
+ "data-tone": "danger",
125
+ children: ["Connection lost", chat.connectionError.reason ? ` — ${chat.connectionError.reason}` : ""]
126
+ }) : void 0;
127
+ return /* @__PURE__ */ jsx(AgentChat, {
128
+ ...props,
129
+ renderPart: renderPart ?? defaultRenderPart,
130
+ messages: chat.messages,
131
+ status,
132
+ onStop: chat.stop,
133
+ onApprovalResponse: chat.addToolApprovalResponse,
134
+ executionApprovals,
135
+ notice: notice ?? connectionNotice,
136
+ onRegenerate: (messageId) => void chat.regenerate({ messageId }),
137
+ onSubmit: ({ text, files }) => {
138
+ const trimmed = text.trim();
139
+ if (trimmed || files.length) chat.sendMessage({
140
+ text: trimmed,
141
+ files
142
+ });
143
+ }
144
+ });
145
+ }
146
+ /** Agent work (reasoning, tool calls, step boundaries) vs. content shown to the user. */
147
+ function isThinkChatActivityPart(part, excludeTools) {
148
+ if (part === void 0) return false;
149
+ if (isToolUIPart(part) && excludeTools?.has(getToolName(part))) return false;
150
+ return part.type === "step-start" || isReasoningUIPart(part) || isToolUIPart(part);
151
+ }
152
+ /** In-flight: the stream opened this part but hasn't delivered its result yet. */
153
+ function isPartWorking(part) {
154
+ if (isReasoningUIPart(part)) return part.state === "streaming";
155
+ if (isToolUIPart(part)) {
156
+ const state = getToolPartState(part);
157
+ return state === "loading" || state === "streaming" || state === "waiting-approval";
158
+ }
159
+ return false;
160
+ }
161
+ /** Errored or denied — upstream's simplified tool-part state vocabulary. */
162
+ function isPartFailed(part) {
163
+ const state = getToolPartState(part);
164
+ return state === "error" || state === "denied";
165
+ }
166
+ function summarizeToolInput(input) {
167
+ if (!input || typeof input !== "object") return null;
168
+ for (const value of Object.values(input)) if (typeof value === "string" && value.trim()) {
169
+ const line = value.trim().split("\n", 1)[0] ?? "";
170
+ return line.length > 64 ? `${line.slice(0, 64)}…` : line;
171
+ }
172
+ return null;
173
+ }
174
+ /**
175
+ * One-line natural-language label for an activity part ("Ran vp test",
176
+ * "Fetched developers.cloudflare.com/agents/"); `null` for boundaries that
177
+ * aren't steps (step-start). Exported so a custom `renderPart` can reuse the
178
+ * built-in labels for tools it doesn't override.
179
+ */
180
+ function describeThinkChatActivityPart(part, toolRenderers) {
181
+ if (isToolUIPart(part)) {
182
+ const custom = toolRenderers?.[getToolName(part)]?.describe?.(part);
183
+ if (custom !== void 0) return custom;
184
+ }
185
+ if (isReasoningUIPart(part)) {
186
+ const snippet = summarizeToolInput({ text: part.text });
187
+ const label = part.state === "streaming" ? "Thinking…" : "Thought";
188
+ return snippet ? `${label} · ${snippet}` : label;
189
+ }
190
+ if (isWorkspaceToolPart(part)) return describeWorkspaceToolPart(part);
191
+ if (isSkillToolPart(part)) return describeSkillToolPart(part);
192
+ if (isFetchToolPart(part)) return describeFetchToolPart(part);
193
+ if (isBrowserToolPart(part)) return describeBrowserToolPart(part);
194
+ if (isExecuteToolPart(part)) return describeExecuteToolPart(part);
195
+ if (isToolUIPart(part)) {
196
+ const name = getToolName(part);
197
+ const verb = isPartFailed(part) ? `${name} failed` : isPartWorking(part) ? `Running ${name}` : `Ran ${name}`;
198
+ const detail = summarizeToolInput(part.input);
199
+ return detail ? `${verb} · ${detail}` : verb;
200
+ }
201
+ return null;
202
+ }
203
+ function basename(path) {
204
+ return path?.split("/").filter(Boolean).at(-1) ?? null;
205
+ }
206
+ function describeWorkspaceToolPart(part) {
207
+ const working = isPartWorking(part);
208
+ const failed = isPartFailed(part);
209
+ if (part.type === "tool-bash") {
210
+ const command = part.input?.script?.trim() ?? "";
211
+ if (failed) return command ? `Command failed · ${command}` : "Command failed";
212
+ const verb = working ? "Running" : "Ran";
213
+ return command ? `${verb} ${command}` : `${verb} a command`;
214
+ }
215
+ const label = (() => {
216
+ switch (part.type) {
217
+ case "tool-read": return `${working ? "Reading" : "Read"} ${basename(part.input?.path) ?? "a file"}`;
218
+ case "tool-write": return `${working ? "Writing" : "Wrote"} ${basename(part.input?.path) ?? "a file"}`;
219
+ case "tool-edit": return `${working ? "Editing" : "Edited"} ${basename(part.input?.path) ?? "a file"}`;
220
+ case "tool-delete": return `${working ? "Deleting" : "Deleted"} ${part.input?.path || "a file"}`;
221
+ case "tool-list": {
222
+ const path = part.input?.path;
223
+ return `${working ? "Listing" : "Listed"} ${path && path !== "/" ? path : "files"}`;
224
+ }
225
+ case "tool-find": return `${working ? "Searching" : "Searched"} for ${part.input?.pattern || "files"}`;
226
+ case "tool-grep": return `${working ? "Searching" : "Searched"} for ${part.input?.query || "text"}`;
227
+ }
228
+ })();
229
+ return failed ? `${label} · failed` : label;
230
+ }
231
+ function describeSkillToolPart(part) {
232
+ const working = isPartWorking(part);
233
+ const failed = isPartFailed(part);
234
+ const label = (() => {
235
+ switch (part.type) {
236
+ case "tool-activate_skill": {
237
+ const name = part.input?.name;
238
+ return `${working ? "Using" : "Used"} ${name ? `the ${name} skill` : "a skill"}`;
239
+ }
240
+ case "tool-read_skill_resource": return `${working ? "Reading" : "Read"} ${part.input?.path ?? "a skill resource"}`;
241
+ case "tool-run_skill_script": return `${working ? "Running" : "Ran"} ${part.input?.path ?? "a skill script"}`;
242
+ }
243
+ })();
244
+ return failed ? `${label} · failed` : label;
245
+ }
246
+ function displayUrl(url) {
247
+ return url ? url.replace(/^https?:\/\//, "") : "";
248
+ }
249
+ function describeBrowserToolPart(part) {
250
+ const working = isPartWorking(part);
251
+ const failed = isPartFailed(part);
252
+ if (part.type === "tool-browser_execute") {
253
+ const output = part.state === "output-available" ? part.output : void 0;
254
+ if (failed || output?.status === "error") return "Browser automation failed";
255
+ if (output?.status === "paused") return "Browser waiting for approval";
256
+ return working ? "Using the browser" : "Used the browser";
257
+ }
258
+ const page = displayUrl(part.input?.url) || "a page";
259
+ const label = (() => {
260
+ switch (part.type) {
261
+ case "tool-browser_extract": return `${working ? "Extracting" : "Extracted"} data from ${page}`;
262
+ case "tool-browser_links": return `${working ? "Listing" : "Listed"} links on ${page}`;
263
+ case "tool-browser_scrape": return `${working ? "Scraping" : "Scraped"} ${page}`;
264
+ default: return `${working ? "Browsing" : "Browsed"} ${page}`;
265
+ }
266
+ })();
267
+ return failed ? `${label} · failed` : label;
268
+ }
269
+ function describeExecuteToolPart(part) {
270
+ const output = part.state === "output-available" ? part.output : void 0;
271
+ if (isPartFailed(part) || output?.status === "error") return "Code failed";
272
+ if (output?.status === "paused") return "Code waiting for approval";
273
+ const label = isPartWorking(part) ? "Running code" : "Ran code";
274
+ const snippet = summarizeToolInput(part.input);
275
+ return snippet ? `${label} · ${snippet}` : label;
276
+ }
277
+ function describeFetchToolPart(part) {
278
+ const target = part.input?.url ?? part.input?.path ?? "";
279
+ const output = part.state === "output-available" ? part.output : void 0;
280
+ if (isPartFailed(part) || output?.ok === false) return target ? `Fetch failed · ${target}` : "Fetch failed";
281
+ const verb = isPartWorking(part) ? "Fetching" : "Fetched";
282
+ return target ? `${verb} ${target}` : `${verb} a URL`;
283
+ }
284
+ function summarizeActivitySteps(parts) {
285
+ const byTool = /* @__PURE__ */ new Map();
286
+ const otherTools = /* @__PURE__ */ new Set();
287
+ const skillNames = /* @__PURE__ */ new Set();
288
+ let thoughts = 0;
289
+ let skillParts = 0;
290
+ let fetches = 0;
291
+ let browses = 0;
292
+ let codeRuns = 0;
293
+ for (const part of parts) if (isReasoningUIPart(part)) thoughts++;
294
+ else if (isWorkspaceToolPart(part)) {
295
+ const name = getToolName(part);
296
+ byTool.set(name, (byTool.get(name) ?? 0) + 1);
297
+ } else if (isSkillToolPart(part)) {
298
+ skillParts++;
299
+ if (typeof part.input?.name === "string") skillNames.add(part.input.name);
300
+ } else if (isFetchToolPart(part)) fetches++;
301
+ else if (isBrowserToolPart(part)) browses++;
302
+ else if (isExecuteToolPart(part)) codeRuns++;
303
+ else if (isToolUIPart(part)) otherTools.add(getToolName(part));
304
+ const count = (name) => byTool.get(name) ?? 0;
305
+ const files = (n) => n === 1 ? "a file" : `${n} files`;
306
+ const reads = count("read");
307
+ const edits = count("edit") + count("write");
308
+ const deletes = count("delete");
309
+ const commands = count("bash");
310
+ const segments = [];
311
+ if (thoughts > 0) segments.push("thought");
312
+ if (skillParts > 0) segments.push(skillNames.size === 1 ? `used the ${[...skillNames][0]} skill` : skillNames.size > 1 ? `used ${skillNames.size} skills` : "used a skill");
313
+ if (reads > 0) segments.push(`read ${files(reads)}`);
314
+ if (edits > 0) segments.push(`edited ${files(edits)}`);
315
+ if (deletes > 0) segments.push(`deleted ${files(deletes)}`);
316
+ if (count("find") + count("grep") > 0) segments.push("searched code");
317
+ if (count("list") > 0) segments.push("listed files");
318
+ if (fetches > 0) segments.push(fetches === 1 ? "fetched a URL" : `fetched ${fetches} URLs`);
319
+ if (browses > 0) segments.push("used the browser");
320
+ if (codeRuns > 0) segments.push("ran code");
321
+ if (commands > 0) segments.push(commands === 1 ? "ran a command" : `ran ${commands} commands`);
322
+ for (const tool of otherTools) segments.push(`used ${tool}`);
323
+ if (segments.length === 0) return "Worked";
324
+ const top = segments.slice(0, 3);
325
+ const joined = top.length === 1 ? top[0] : `${top.slice(0, -1).join(", ")} and ${top.at(-1)}`;
326
+ return joined.charAt(0).toUpperCase() + joined.slice(1);
327
+ }
328
+ /**
329
+ * One run of agent work as a collapsed Activity block: while streaming the
330
+ * label shimmers with the live step; settled, it becomes a short summary
331
+ * ("Thought, read 2 files and ran a command") over an expandable step log
332
+ * with per-tool panels. Normally reached through `ThinkChat.Messages`;
333
+ * exported for apps that lay out messages themselves.
334
+ */
335
+ function ThinkChatActivity({ parts, defaultOpen, isStreaming, toolRenderers }) {
336
+ const steps = parts.flatMap((part, index) => {
337
+ const text = describeThinkChatActivityPart(part, toolRenderers);
338
+ if (!text) return [];
339
+ return [{
340
+ key: isToolUIPart(part) ? part.toolCallId : `${part.type}-${index}`,
341
+ text,
342
+ part
343
+ }];
344
+ });
345
+ if (steps.length === 0) return null;
346
+ const streaming = isStreaming ?? parts.some(isPartWorking);
347
+ const settledLabel = summarizeActivitySteps(parts);
348
+ const latest = steps.at(-1);
349
+ return /* @__PURE__ */ jsxs(Activity, {
350
+ isStreaming: streaming,
351
+ defaultOpen,
352
+ children: [/* @__PURE__ */ jsx(ActivityTrigger, { label: streaming && latest ? latest.text : settledLabel }), /* @__PURE__ */ jsx(ActivityContent, { children: steps.map((step) => /* @__PURE__ */ jsx(ThinkChatActivityStep, {
353
+ text: step.text,
354
+ part: step.part,
355
+ toolRenderers
356
+ }, step.key)) })]
357
+ });
358
+ }
359
+ function ThinkChatActivityStep({ text, part, toolRenderers }) {
360
+ if (isToolUIPart(part)) {
361
+ const customPanel = toolRenderers?.[getToolName(part)]?.panel?.(part);
362
+ if (customPanel === null) return /* @__PURE__ */ jsx(ActivityItem, { children: text });
363
+ if (customPanel !== void 0) return /* @__PURE__ */ jsx(ActivityItemGroup, {
364
+ label: text,
365
+ children: customPanel
366
+ });
367
+ }
368
+ if (isReasoningUIPart(part) && part.text.trim()) return /* @__PURE__ */ jsx(ActivityItemGroup, {
369
+ label: text,
370
+ children: /* @__PURE__ */ jsx(ActivityText, { children: part.text })
371
+ });
372
+ if (isWorkspaceToolPart(part) && part.type !== "tool-delete") return /* @__PURE__ */ jsx(ActivityItemGroup, {
373
+ label: text,
374
+ children: /* @__PURE__ */ jsx(ThinkChatWorkspacePanel, { part })
375
+ });
376
+ if (isSkillToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
377
+ label: text,
378
+ children: /* @__PURE__ */ jsx(ThinkChatSkillPanel, { part })
379
+ });
380
+ if (isFetchToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
381
+ label: text,
382
+ children: /* @__PURE__ */ jsx(ThinkChatFetchPanel, { part })
383
+ });
384
+ if (isBrowserToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
385
+ label: text,
386
+ children: /* @__PURE__ */ jsx(ThinkChatBrowserPanel, { part })
387
+ });
388
+ if (isExecuteToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
389
+ label: text,
390
+ children: /* @__PURE__ */ jsx(ThinkChatExecutePanel, { part })
391
+ });
392
+ return /* @__PURE__ */ jsx(ActivityItem, { children: text });
393
+ }
394
+ function renderDeniedPanel(label, reason) {
395
+ return /* @__PURE__ */ jsx(ActivityPanel, {
396
+ label,
397
+ text: reason,
398
+ status: "denied",
399
+ error: true
400
+ });
401
+ }
402
+ function numberLines(content, from = 1) {
403
+ const lines = content.split("\n");
404
+ const width = String(from + lines.length - 1).length;
405
+ return lines.map((line, i) => `${String(from + i).padStart(width)} ${line}`).join("\n");
406
+ }
407
+ function prefixLines(text, prefix) {
408
+ return text.split("\n").map((line) => `${prefix}${line}`).join("\n");
409
+ }
410
+ /** Default panels for the Think workspace tools, Claude Code-style: read/write
411
+ show the file with line numbers, edit shows a mini diff, list/find/grep
412
+ show their result lists, bash gets the shell panel. Everything is typed by
413
+ the upstream tool contract. */
414
+ function ThinkChatWorkspacePanel({ part }) {
415
+ if (part.type === "tool-bash") return /* @__PURE__ */ jsx(ThinkChatShellPanel, { part });
416
+ if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
417
+ label: getToolName(part),
418
+ text: part.errorText,
419
+ status: "error",
420
+ error: true
421
+ });
422
+ if (part.state === "output-denied") return renderDeniedPanel(getToolName(part), part.approval.reason);
423
+ switch (part.type) {
424
+ case "tool-read": {
425
+ const label = part.input?.path ?? "file";
426
+ const output = part.state === "output-available" ? part.output : void 0;
427
+ if (output && "error" in output) return /* @__PURE__ */ jsx(ActivityPanel, {
428
+ label,
429
+ text: output.error,
430
+ status: "error",
431
+ error: true
432
+ });
433
+ if (output && "content" in output) return /* @__PURE__ */ jsx(ActivityPanel, {
434
+ label,
435
+ text: output.content,
436
+ status: `${output.totalLines} lines`
437
+ });
438
+ if (output) return /* @__PURE__ */ jsx(ActivityPanel, {
439
+ label,
440
+ text: `${output.name} · ${output.mediaType} · ${output.sizeBytes} bytes`
441
+ });
442
+ return /* @__PURE__ */ jsx(ActivityPanel, { label });
443
+ }
444
+ case "tool-write": {
445
+ const output = part.state === "output-available" ? part.output : void 0;
446
+ return /* @__PURE__ */ jsx(ActivityPanel, {
447
+ label: part.input?.path ?? "file",
448
+ text: part.input?.content ? numberLines(part.input.content) : void 0,
449
+ status: output ? `${output.lines} lines written` : void 0
450
+ });
451
+ }
452
+ case "tool-edit": {
453
+ const input = part.input;
454
+ const output = part.state === "output-available" ? part.output : void 0;
455
+ const status = !output ? void 0 : output.error ? output.error : output.created ? "created" : output.fuzzyMatch ? "replaced (fuzzy match)" : "replaced";
456
+ return /* @__PURE__ */ jsx(ActivityPanel, {
457
+ label: input?.path ?? "file",
458
+ status,
459
+ error: Boolean(output?.error),
460
+ children: input?.old_string !== void 0 && input.new_string !== void 0 ? /* @__PURE__ */ jsxs(ActivityOutput, { children: [
461
+ /* @__PURE__ */ jsx("span", {
462
+ className: "activity__diff-del",
463
+ children: prefixLines(input.old_string, "- ")
464
+ }),
465
+ "\n",
466
+ /* @__PURE__ */ jsx("span", {
467
+ className: "activity__diff-add",
468
+ children: prefixLines(input.new_string, "+ ")
469
+ })
470
+ ] }) : null
471
+ });
472
+ }
473
+ case "tool-list": {
474
+ const output = part.state === "output-available" ? part.output : void 0;
475
+ return /* @__PURE__ */ jsx(ActivityPanel, {
476
+ label: part.input?.path ?? "/",
477
+ text: output?.entries.join("\n"),
478
+ status: output ? `${output.count} entries` : void 0
479
+ });
480
+ }
481
+ case "tool-find": {
482
+ const output = part.state === "output-available" ? part.output : void 0;
483
+ const files = Array.isArray(output?.files) ? output.files.map(String) : [];
484
+ const count = typeof output?.count === "number" ? output.count : files.length;
485
+ return /* @__PURE__ */ jsx(ActivityPanel, {
486
+ label: part.input?.pattern ?? "files",
487
+ text: files.join("\n"),
488
+ status: output ? `${count} files${output.truncated ? " (truncated)" : ""}` : void 0
489
+ });
490
+ }
491
+ case "tool-grep": {
492
+ const output = part.state === "output-available" ? part.output : void 0;
493
+ const matches = Array.isArray(output?.matches) ? output.matches.map((m) => typeof m === "string" ? m : m.context ?? JSON.stringify(m)) : [];
494
+ const status = output && typeof output.totalMatches === "number" ? `${output.totalMatches} matches in ${String(output.filesWithMatches)} files` : void 0;
495
+ return /* @__PURE__ */ jsx(ActivityPanel, {
496
+ label: part.input?.query ?? "search",
497
+ text: matches.join("\n"),
498
+ status
499
+ });
500
+ }
501
+ default: return null;
502
+ }
503
+ }
504
+ /** Skill panels show what the model actually received: the activated skill's
505
+ body, the resource text, or the script result. */
506
+ function ThinkChatSkillPanel({ part }) {
507
+ if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
508
+ label: getToolName(part),
509
+ text: part.errorText,
510
+ status: "error",
511
+ error: true
512
+ });
513
+ if (part.state === "output-denied") return renderDeniedPanel(getToolName(part), part.approval.reason);
514
+ switch (part.type) {
515
+ case "tool-activate_skill": {
516
+ const output = part.state === "output-available" ? part.output : void 0;
517
+ return /* @__PURE__ */ jsx(ActivityPanel, {
518
+ label: part.input?.name ?? "skill",
519
+ text: output
520
+ });
521
+ }
522
+ case "tool-read_skill_resource": {
523
+ const output = part.state === "output-available" ? part.output : void 0;
524
+ return /* @__PURE__ */ jsx(ActivityPanel, {
525
+ label: part.input?.path ?? "resource",
526
+ text: output
527
+ });
528
+ }
529
+ case "tool-run_skill_script": {
530
+ const output = part.state === "output-available" ? part.output : void 0;
531
+ const text = output === void 0 ? void 0 : typeof output === "string" ? output : JSON.stringify(output, null, 2);
532
+ return /* @__PURE__ */ jsx(ActivityPanel, {
533
+ label: part.input?.path ?? "script",
534
+ text
535
+ });
536
+ }
537
+ }
538
+ }
539
+ function ThinkChatFetchPanel({ part }) {
540
+ const label = part.input?.url ?? part.input?.path ?? "fetch";
541
+ if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
542
+ label,
543
+ text: part.errorText,
544
+ status: "error",
545
+ error: true
546
+ });
547
+ if (part.state === "output-denied") return renderDeniedPanel(label, part.approval.reason);
548
+ const output = part.state === "output-available" ? part.output : void 0;
549
+ if (!output) return /* @__PURE__ */ jsx(ActivityPanel, { label });
550
+ if (!output.ok) return /* @__PURE__ */ jsx(ActivityPanel, {
551
+ label: output.finalUrl ?? label,
552
+ text: output.message,
553
+ status: output.code,
554
+ error: true
555
+ });
556
+ const body = output.body ?? (output.json !== void 0 ? JSON.stringify(output.json, null, 2) : output.path ? `saved to ${output.path}` : "");
557
+ return /* @__PURE__ */ jsx(ActivityPanel, {
558
+ label: output.finalUrl,
559
+ text: body || void 0,
560
+ status: `${output.status} · ${output.bytes} bytes${output.truncated ? " (truncated)" : ""}`
561
+ });
562
+ }
563
+ function findBrowserResultMedia(value, depth = 0) {
564
+ const media = {};
565
+ const visit = (node, level) => {
566
+ if (level > 4 || node === null) return;
567
+ if (typeof node === "string") {
568
+ if (!media.liveViewUrl && node.includes("live.browser.run")) media.liveViewUrl = node;
569
+ else if (!media.screenshot && node.startsWith("data:image/")) media.screenshot = node;
570
+ return;
571
+ }
572
+ if (Array.isArray(node)) {
573
+ for (const item of node) visit(item, level + 1);
574
+ return;
575
+ }
576
+ if (typeof node === "object") {
577
+ const record = node;
578
+ if (media.expiresInMs === void 0 && typeof record.url === "string" && typeof record.expiresInMs === "number") media.expiresInMs = record.expiresInMs;
579
+ for (const item of Object.values(record)) visit(item, level + 1);
580
+ }
581
+ };
582
+ visit(value, depth);
583
+ return media;
584
+ }
585
+ function ExecuteCodeView({ code, output, hideResult }) {
586
+ const sections = [
587
+ output && "logs" in output && output.logs?.length ? output.logs.join("\n") : "",
588
+ output?.status === "error" ? output.error : "",
589
+ output?.status === "paused" ? formatPendingActions(output.pending) : "",
590
+ output?.status === "completed" && output.result !== void 0 && !hideResult ? typeof output.result === "string" ? output.result : JSON.stringify(output.result, null, 2) : ""
591
+ ].filter(Boolean);
592
+ return /* @__PURE__ */ jsxs(Fragment, { children: [code ? /* @__PURE__ */ jsx(CodeBlock, {
593
+ className: "activity__code-block",
594
+ code,
595
+ language: "ts"
596
+ }) : null, sections.length ? /* @__PURE__ */ jsx(ActivityOutput, { children: sections.join("\n\n") }) : null] });
597
+ }
598
+ function formatPendingActions(pending) {
599
+ return pending.map((action) => {
600
+ const args = action.args === void 0 ? "" : JSON.stringify(action.args);
601
+ return `awaiting approval: ${action.connector}.${action.method}(${args})`;
602
+ }).join("\n");
603
+ }
604
+ function executeStatusText(output) {
605
+ if (!output) return void 0;
606
+ return output.status === "paused" ? "paused · needs approval" : output.status;
607
+ }
608
+ /** The codemode execute tool as an activity panel: highlighted TypeScript
609
+ plus logs, result, and the paused pending-approval list. */
610
+ function ThinkChatExecutePanel({ part }) {
611
+ if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
612
+ label: "Code",
613
+ text: part.errorText,
614
+ status: "error",
615
+ error: true
616
+ });
617
+ if (part.state === "output-denied") return renderDeniedPanel("Code", part.approval.reason);
618
+ const output = part.state === "output-available" ? part.output : void 0;
619
+ return /* @__PURE__ */ jsx(ActivityPanel, {
620
+ label: "Code",
621
+ status: executeStatusText(output),
622
+ error: output?.status === "error",
623
+ children: /* @__PURE__ */ jsx(ExecuteCodeView, {
624
+ code: part.input?.code ?? "",
625
+ output
626
+ })
627
+ });
628
+ }
629
+ function ThinkChatBrowserPanel({ part }) {
630
+ const [seenAt] = React.useState(() => Date.now());
631
+ if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
632
+ label: "Browser",
633
+ text: part.errorText,
634
+ status: "error",
635
+ error: true
636
+ });
637
+ if (part.state === "output-denied") return renderDeniedPanel("Browser", part.approval.reason);
638
+ if (part.type === "tool-browser_execute") {
639
+ const output = part.state === "output-available" ? part.output : void 0;
640
+ const code = part.input?.code ?? "";
641
+ const media = output?.status === "completed" ? findBrowserResultMedia(output.result) : {};
642
+ const hasMedia = Boolean(media.liveViewUrl || media.screenshot);
643
+ const browserView = media.liveViewUrl ? /* @__PURE__ */ jsx(BrowserLiveView, {
644
+ url: media.liveViewUrl,
645
+ expiresAt: seenAt + (media.expiresInMs ?? 300 * 1e3),
646
+ fallback: media.screenshot ? /* @__PURE__ */ jsx("img", {
647
+ src: media.screenshot,
648
+ alt: "Last browser state"
649
+ }) : void 0
650
+ }) : media.screenshot ? /* @__PURE__ */ jsx("img", {
651
+ className: "activity__browser-screenshot",
652
+ src: media.screenshot,
653
+ alt: "Browser screenshot"
654
+ }) : null;
655
+ const codeView = /* @__PURE__ */ jsx(ExecuteCodeView, {
656
+ code,
657
+ output,
658
+ hideResult: hasMedia
659
+ });
660
+ return /* @__PURE__ */ jsx(ActivityPanel, {
661
+ label: "Browser",
662
+ status: executeStatusText(output),
663
+ error: output?.status === "error",
664
+ children: browserView && code ? /* @__PURE__ */ jsxs(Tabs, {
665
+ className: "activity__browser-tabs",
666
+ defaultValue: "browser",
667
+ children: [
668
+ /* @__PURE__ */ jsxs(TabsList, {
669
+ variant: "line",
670
+ children: [/* @__PURE__ */ jsx(Tab, {
671
+ value: "browser",
672
+ children: "Preview"
673
+ }), /* @__PURE__ */ jsx(Tab, {
674
+ value: "code",
675
+ children: "Code"
676
+ })]
677
+ }),
678
+ /* @__PURE__ */ jsx(TabPanel, {
679
+ value: "browser",
680
+ children: browserView
681
+ }),
682
+ /* @__PURE__ */ jsx(TabPanel, {
683
+ value: "code",
684
+ children: codeView
685
+ })
686
+ ]
687
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [browserView, codeView] })
688
+ });
689
+ }
690
+ const output = part.state === "output-available" ? part.output : void 0;
691
+ return /* @__PURE__ */ jsx(ActivityPanel, {
692
+ label: displayUrl(part.input?.url) || "page",
693
+ text: output === void 0 ? void 0 : typeof output === "string" ? output : Array.isArray(output) ? output.map((item) => typeof item === "string" ? item : JSON.stringify(item)).join("\n") : JSON.stringify(output, null, 2),
694
+ status: part.type === "tool-browser_links" && Array.isArray(output) ? `${output.length} links` : void 0
695
+ });
696
+ }
697
+ function ThinkChatShellPanel({ part }) {
698
+ const output = part.state === "output-available" ? part.output : void 0;
699
+ const errorText = part.state === "output-error" ? part.errorText : "";
700
+ const deniedReason = part.state === "output-denied" ? part.approval.reason ?? "denied" : "";
701
+ const failed = Boolean(errorText) || Boolean(deniedReason) || output !== void 0 && (output.exitCode !== 0 || Boolean(output.errors?.length));
702
+ return /* @__PURE__ */ jsx(ActivityPanel, {
703
+ label: "Shell",
704
+ text: formatBashPanelText(part.input, output, errorText || deniedReason),
705
+ status: output !== void 0 || errorText || deniedReason ? getBashStatusText(output, errorText, deniedReason) : void 0,
706
+ error: failed
707
+ });
708
+ }
709
+ function ThinkChatActivityGroup({ message, parts, isTrailing, toolRenderers }) {
710
+ const chat = React.useContext(ThinkChatContext);
711
+ const chatStillWorking = isTrailing && Boolean(chat?.isStreaming) && chat?.messages.at(-1)?.id === message.id;
712
+ return /* @__PURE__ */ jsx(ThinkChatActivity, {
713
+ parts,
714
+ isStreaming: parts.some(isPartWorking) || chatStillWorking,
715
+ toolRenderers
716
+ });
717
+ }
718
+ /**
719
+ * Builds a renderPart handler that folds every run of consecutive activity
720
+ * parts (reasoning + tool calls) into one collapsed Activity box. Everything
721
+ * is derived from the UIMessage stream: part order, tool states, reasoning
722
+ * state. Excluded tools split the run and return undefined so your own
723
+ * renderPart (or the generic Tool card) takes over for them.
724
+ */
725
+ function createThinkChatActivityRenderer({ excludeTools, toolRenderers } = {}) {
726
+ const excluded = excludeTools?.length ? new Set(excludeTools) : void 0;
727
+ const isActivity = (part) => isThinkChatActivityPart(part, excluded);
728
+ return (context) => {
729
+ const { message, part, partIndex } = context;
730
+ if (!isActivity(part)) return void 0;
731
+ let start = partIndex;
732
+ while (start > 0 && isActivity(message.parts[start - 1])) start--;
733
+ if (partIndex !== start) return null;
734
+ let end = partIndex;
735
+ while (end + 1 < message.parts.length && isActivity(message.parts[end + 1])) end++;
736
+ return /* @__PURE__ */ jsx(ThinkChatActivityGroup, {
737
+ message,
738
+ parts: message.parts.slice(start, end + 1),
739
+ isTrailing: end === message.parts.length - 1,
740
+ toolRenderers
741
+ });
742
+ };
743
+ }
744
+ /** Default activity renderer — `createThinkChatActivityRenderer()` with no overrides. */
745
+ const renderThinkChatActivityPart = createThinkChatActivityRenderer();
746
+ /**
747
+ * The ambient chat value from `ThinkChat.Root`/`Provider` — for custom tool
748
+ * panels, cards, or composer actions that need `messages`, `status`, or
749
+ * `sendMessage`. Throws outside a provider.
750
+ */
751
+ function useThinkChat() {
752
+ return useThinkChatContext("useThinkChat");
753
+ }
754
+ /**
755
+ * Namespace entry point. `Root` + `Messages` is the standard surface;
756
+ * `Provider` + `useThinkChat` when the app owns the chat hook; `Activity`,
757
+ * `renderActivityPart`, and `createActivityRenderer` for custom layouts and
758
+ * app-specific tools.
759
+ */
760
+ const ThinkChat = {
761
+ Provider: ThinkChatProvider,
762
+ Root: ThinkChatRoot,
763
+ Messages: ThinkChatMessages,
764
+ Activity: ThinkChatActivity,
765
+ renderActivityPart: renderThinkChatActivityPart,
766
+ createActivityRenderer: createThinkChatActivityRenderer
767
+ };
768
+ function formatBashPanelText(input, output, errorText) {
769
+ return [
770
+ input?.script?.trim() ? `$ bash\n${input.script}` : "",
771
+ output?.stdout,
772
+ output?.stderr,
773
+ errorText,
774
+ output?.skippedFiles?.length ? ["skipped files", ...output.skippedFiles].join("\n") : "",
775
+ output?.errors?.length ? ["sync errors", ...output.errors].join("\n") : "",
776
+ output ? formatChangedFiles(output.changedFiles) : ""
777
+ ].filter(Boolean).join("\n\n");
778
+ }
779
+ function formatChangedFiles(changedFiles) {
780
+ if (!changedFiles) return "";
781
+ const lines = [
782
+ ...changedFiles.created.map((path) => `created ${path}`),
783
+ ...changedFiles.updated.map((path) => `updated ${path}`),
784
+ ...changedFiles.deleted.map((path) => `deleted ${path}`),
785
+ ...changedFiles.directoriesCreated.map((path) => `created directory ${path}`),
786
+ ...changedFiles.directoriesDeleted.map((path) => `deleted directory ${path}`)
787
+ ];
788
+ return lines.length ? ["workspace changes", ...lines].join("\n") : "";
789
+ }
790
+ function getBashStatusText(output, errorText, deniedReason) {
791
+ if (errorText) return "error";
792
+ if (deniedReason) return "denied";
793
+ if (output?.errors?.length) return "sync errors";
794
+ if (output && output.exitCode !== 0) return `exit ${output.exitCode}`;
795
+ return "success";
796
+ }
797
+ //#endregion
798
+ export { BrowserLiveView, BrowserReplay, ThinkChat, ThinkChatActivity, ThinkChatMessages, ThinkChatProvider, ThinkChatRoot, WorkspaceExplorer, createThinkChatActivityRenderer, describeThinkChatActivityPart, inferWorkspaceLanguage, isThinkChatActivityPart, renderThinkChatActivityPart, useThinkChat, useWorkspace };