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