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