@tangle-network/ui 9.0.0 → 9.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/chat.d.ts +15 -3
- package/dist/chat.js +3 -3
- package/dist/{chunk-DLSGUNRD.js → chunk-IVQYFG5D.js} +1 -1
- package/dist/{chunk-2TRMNB6L.js → chunk-KWRLQ3MR.js} +5 -5
- package/dist/{chunk-IWQZXL6A.js → chunk-V6BF4AZ7.js} +1 -1
- package/dist/{chunk-RKQDBRTC.js → chunk-WVW4KHEH.js} +9 -6
- package/dist/{chunk-LHOGIUGY.js → chunk-YUY6D62P.js} +41 -21
- package/dist/hooks.js +3 -3
- package/dist/index.js +5 -5
- package/dist/run.js +3 -3
- package/dist/sdk-hooks.js +3 -3
- package/package.json +1 -1
- package/src/chat/agent-timeline.test.tsx +162 -0
- package/src/chat/agent-timeline.tsx +28 -11
- package/src/chat/chat-container.test.tsx +68 -0
- package/src/chat/chat-container.tsx +17 -4
- package/src/run/inline-tool-item.tsx +2 -2
- package/src/run/run-group.tsx +4 -4
- package/src/run/tool-call-step.tsx +26 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @tangle-network/ui
|
|
2
2
|
|
|
3
|
+
## 9.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a8d770e: Give run/timeline tool rows a proper elevation ladder. Rows read as the same value as the canvas: `InlineToolItem` used `bg-card/40` (near-transparent) and RunGroup's OpenUI/running blocks used `bg-[var(--bg-root)]` (literally the page background). Both now use `--md3-surface-container` — one clear step above the `--bg-root` canvas — with hover/open stepping to `--md3-surface-container-high`. Rows now separate from the background instead of blending into it.
|
|
8
|
+
|
|
9
|
+
## 9.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- deb065a: AgentTimeline now accepts `renderToolActions` (and carries the source `ToolPart` on its tool items) so consumers can render actions beside a tool call — e.g. "open in artifacts". Previously these hooks reached only the run-grouped `MessageList`, not the timeline presentation. ChatContainer exposes this through a new `renderTimelineToolActions?(part)` prop; the existing `renderToolActions(part, options)` contract for the `runs` presentation is unchanged.
|
|
14
|
+
|
|
15
|
+
The timeline tool-call summary now shows a human-readable detail (file path / command via `getToolDisplayMetadata`) instead of the raw input JSON, and drops the redundant `title: description` label. The source `ToolPart` is threaded into `ToolCallStep`, so the expanded detail renders the full input + output via `ExpandedToolDetail` (previously the timeline's expanded view showed only the output).
|
|
16
|
+
|
|
3
17
|
## 9.0.0
|
|
4
18
|
|
|
5
19
|
### Major Changes
|
package/dist/chat.d.ts
CHANGED
|
@@ -32,18 +32,22 @@ interface ChatContainerProps {
|
|
|
32
32
|
renderRunActions?: (run: Run) => ReactNode;
|
|
33
33
|
/** Optional actions rendered below each user message bubble. */
|
|
34
34
|
renderUserMessageActions?: (message: SessionMessage, parts: SessionPart[]) => ReactNode;
|
|
35
|
-
/** Optional actions rendered beside individual tool items
|
|
35
|
+
/** Optional actions rendered beside individual tool items in the run-grouped
|
|
36
|
+
* (`runs`) presentation. */
|
|
36
37
|
renderToolActions?: (part: ToolPart, options: {
|
|
37
38
|
run: Run;
|
|
38
39
|
messageId: string;
|
|
39
40
|
partIndex: number;
|
|
40
41
|
}) => ReactNode;
|
|
42
|
+
/** Optional actions rendered beside individual tool items in the `timeline`
|
|
43
|
+
* presentation, which has no run/message grouping (hence part-only). */
|
|
44
|
+
renderTimelineToolActions?: (part: ToolPart) => ReactNode;
|
|
41
45
|
}
|
|
42
46
|
/**
|
|
43
47
|
* Chat transcript container: message list + auto-scroll.
|
|
44
48
|
* Orchestrates useRunGroups, useRunCollapseState, and useAutoScroll.
|
|
45
49
|
*/
|
|
46
|
-
declare const ChatContainer: React.MemoExoticComponent<({ messages, partMap, isStreaming, branding, className, renderToolDetail, presentation, onOpenUIAction, enableOpenUI, renderRunActions, renderUserMessageActions, renderToolActions, }: ChatContainerProps) => react_jsx_runtime.JSX.Element>;
|
|
50
|
+
declare const ChatContainer: React.MemoExoticComponent<({ messages, partMap, isStreaming, branding, className, renderToolDetail, presentation, onOpenUIAction, enableOpenUI, renderRunActions, renderUserMessageActions, renderToolActions, renderTimelineToolActions, }: ChatContainerProps) => react_jsx_runtime.JSX.Element>;
|
|
47
51
|
|
|
48
52
|
interface MessageListProps {
|
|
49
53
|
groups: GroupedMessage[];
|
|
@@ -121,12 +125,17 @@ interface AgentTimelineToolItem {
|
|
|
121
125
|
id: string;
|
|
122
126
|
kind: "tool";
|
|
123
127
|
call: ToolCallData;
|
|
128
|
+
/** Source tool part, so a consumer's `renderToolActions` gets the real
|
|
129
|
+
* input/output (the flat `call` is display-only). */
|
|
130
|
+
part?: ToolPart;
|
|
124
131
|
}
|
|
125
132
|
interface AgentTimelineToolGroupItem {
|
|
126
133
|
id: string;
|
|
127
134
|
kind: "tool_group";
|
|
128
135
|
title?: string;
|
|
129
136
|
calls: ToolCallData[];
|
|
137
|
+
/** Source tool parts, parallel to `calls`. */
|
|
138
|
+
parts?: ToolPart[];
|
|
130
139
|
}
|
|
131
140
|
interface AgentTimelineStatusItem {
|
|
132
141
|
id: string;
|
|
@@ -157,12 +166,15 @@ interface AgentTimelineProps {
|
|
|
157
166
|
isThinking?: boolean;
|
|
158
167
|
emptyState?: ReactNode;
|
|
159
168
|
className?: string;
|
|
169
|
+
/** Optional actions rendered beside each tool item (e.g. "open in artifacts").
|
|
170
|
+
* Receives the source tool part carried on the item. */
|
|
171
|
+
renderToolActions?: (part: ToolPart) => ReactNode;
|
|
160
172
|
}
|
|
161
173
|
/**
|
|
162
174
|
* AgentTimeline — unified mixed-content timeline for agent-backed sandbox
|
|
163
175
|
* sessions. Renders messages, tool steps, status cards, and artifact handoffs in
|
|
164
176
|
* a single execution narrative.
|
|
165
177
|
*/
|
|
166
|
-
declare function AgentTimeline({ items, isThinking, emptyState, className, }: AgentTimelineProps): react_jsx_runtime.JSX.Element | null;
|
|
178
|
+
declare function AgentTimeline({ items, isThinking, emptyState, className, renderToolActions, }: AgentTimelineProps): react_jsx_runtime.JSX.Element | null;
|
|
167
179
|
|
|
168
180
|
export { AgentTimeline, type AgentTimelineArtifactItem, type AgentTimelineCustomItem, type AgentTimelineItem, type AgentTimelineMessageItem, type AgentTimelineProps, type AgentTimelineStatusItem, type AgentTimelineTone, type AgentTimelineToolGroupItem, type AgentTimelineToolItem, ChatContainer, type ChatContainerProps, ChatMessage, type ChatMessageProps, MessageList, type MessageListProps, type MessageRole, ThinkingIndicator, type ThinkingIndicatorProps, UserMessage, type UserMessageProps };
|
package/dist/chat.js
CHANGED
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
MessageList,
|
|
6
6
|
ThinkingIndicator,
|
|
7
7
|
UserMessage
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YUY6D62P.js";
|
|
9
9
|
import "./chunk-AZWDI2JG.js";
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-KWRLQ3MR.js";
|
|
11
|
+
import "./chunk-WVW4KHEH.js";
|
|
12
12
|
import "./chunk-ULDNFLIM.js";
|
|
13
13
|
import "./chunk-AAUNOHVL.js";
|
|
14
14
|
import "./chunk-52Y3FMFI.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
InlineToolItem,
|
|
3
3
|
LiveDuration
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WVW4KHEH.js";
|
|
5
5
|
import {
|
|
6
6
|
formatDuration,
|
|
7
7
|
truncateText
|
|
@@ -347,7 +347,7 @@ var RunGroup = memo2(
|
|
|
347
347
|
return /* @__PURE__ */ jsxs2(
|
|
348
348
|
"div",
|
|
349
349
|
{
|
|
350
|
-
className: "overflow-hidden rounded-[
|
|
350
|
+
className: "overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)]",
|
|
351
351
|
children: [
|
|
352
352
|
summary ? /* @__PURE__ */ jsx2("div", { className: "border-b border-[var(--border-subtle)] px-4 py-3 text-sm leading-6 text-foreground", children: summary }) : null,
|
|
353
353
|
/* @__PURE__ */ jsx2("div", { className: "p-4", children: /* @__PURE__ */ jsx2(OpenUIArtifactRenderer, { schema }) })
|
|
@@ -360,7 +360,7 @@ var RunGroup = memo2(
|
|
|
360
360
|
return /* @__PURE__ */ jsxs2(
|
|
361
361
|
"div",
|
|
362
362
|
{
|
|
363
|
-
className: "flex items-center gap-2 rounded-[
|
|
363
|
+
className: "flex items-center gap-2 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)] px-4 py-3 text-sm text-muted-foreground",
|
|
364
364
|
children: [
|
|
365
365
|
/* @__PURE__ */ jsx2(Loader2, { className: "h-4 w-4 animate-spin text-primary" }),
|
|
366
366
|
"Building view\u2026"
|
|
@@ -423,12 +423,12 @@ var RunGroup = memo2(
|
|
|
423
423
|
const schema = extractOpenUISchema(toolPart.state.output);
|
|
424
424
|
const summary = getOpenUISummary(toolPart.state.output);
|
|
425
425
|
if (toolPart.state.status === "completed" && schema) {
|
|
426
|
-
node = /* @__PURE__ */ jsxs2("div", { className: "overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--
|
|
426
|
+
node = /* @__PURE__ */ jsxs2("div", { className: "overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)]", children: [
|
|
427
427
|
summary ? /* @__PURE__ */ jsx2("div", { className: "border-b border-[var(--border-subtle)] px-4 py-3 text-sm leading-6 text-foreground", children: summary }) : null,
|
|
428
428
|
/* @__PURE__ */ jsx2("div", { className: "p-4", children: /* @__PURE__ */ jsx2(OpenUIArtifactRenderer, { schema }) })
|
|
429
429
|
] });
|
|
430
430
|
} else if (toolPart.state.status === "running") {
|
|
431
|
-
node = /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--
|
|
431
|
+
node = /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)] px-4 py-3 text-sm text-muted-foreground", children: [
|
|
432
432
|
/* @__PURE__ */ jsx2(Loader2, { className: "h-4 w-4 animate-spin text-primary" }),
|
|
433
433
|
"Building view\u2026"
|
|
434
434
|
] });
|
|
@@ -227,8 +227,8 @@ var InlineToolItem = memo2(
|
|
|
227
227
|
{
|
|
228
228
|
className: cn(
|
|
229
229
|
"w-full border text-left transition-colors",
|
|
230
|
-
"border-[var(--border-subtle)] bg-
|
|
231
|
-
open && "border-border bg-
|
|
230
|
+
"border-[var(--border-subtle)] bg-[var(--md3-surface-container)] hover:border-border hover:bg-[var(--md3-surface-container-high)]",
|
|
231
|
+
open && "border-border bg-[var(--md3-surface-container-high)]",
|
|
232
232
|
shapeClass,
|
|
233
233
|
className
|
|
234
234
|
),
|
|
@@ -318,9 +318,11 @@ function ToolCallStep({
|
|
|
318
318
|
output,
|
|
319
319
|
language,
|
|
320
320
|
duration,
|
|
321
|
-
className
|
|
321
|
+
className,
|
|
322
|
+
actions,
|
|
323
|
+
part
|
|
322
324
|
}) {
|
|
323
|
-
const
|
|
325
|
+
const resolvedPart = part ?? {
|
|
324
326
|
type: "tool",
|
|
325
327
|
id: `${type}:${label}`,
|
|
326
328
|
tool: type,
|
|
@@ -335,11 +337,12 @@ function ToolCallStep({
|
|
|
335
337
|
return /* @__PURE__ */ jsx4(
|
|
336
338
|
InlineToolItem,
|
|
337
339
|
{
|
|
338
|
-
part,
|
|
340
|
+
part: resolvedPart,
|
|
339
341
|
title: label,
|
|
340
342
|
description: detail,
|
|
341
343
|
className,
|
|
342
|
-
|
|
344
|
+
actions,
|
|
345
|
+
renderToolDetail: part ? void 0 : output ? () => /* @__PURE__ */ jsx4(
|
|
343
346
|
CodeBlock,
|
|
344
347
|
{
|
|
345
348
|
code: output,
|
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
InlineThinkingItem,
|
|
8
8
|
RunGroup
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-KWRLQ3MR.js";
|
|
10
10
|
import {
|
|
11
11
|
ToolCallGroup,
|
|
12
12
|
ToolCallStep
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-WVW4KHEH.js";
|
|
14
14
|
import {
|
|
15
15
|
getToolDisplayMetadata
|
|
16
16
|
} from "./chunk-ULDNFLIM.js";
|
|
@@ -233,7 +233,8 @@ function AgentTimeline({
|
|
|
233
233
|
items,
|
|
234
234
|
isThinking,
|
|
235
235
|
emptyState,
|
|
236
|
-
className
|
|
236
|
+
className,
|
|
237
|
+
renderToolActions
|
|
237
238
|
}) {
|
|
238
239
|
if (items.length === 0 && !isThinking) {
|
|
239
240
|
return emptyState ? /* @__PURE__ */ jsx4("div", { className: cn("flex h-full items-center justify-center p-4", className), children: emptyState }) : null;
|
|
@@ -258,23 +259,30 @@ function AgentTimeline({
|
|
|
258
259
|
status: item.call.status,
|
|
259
260
|
detail: item.call.detail,
|
|
260
261
|
output: item.call.output,
|
|
261
|
-
duration: item.call.duration
|
|
262
|
+
duration: item.call.duration,
|
|
263
|
+
part: item.part,
|
|
264
|
+
actions: item.part ? renderToolActions?.(item.part) : void 0
|
|
262
265
|
}
|
|
263
266
|
) }, item.id);
|
|
264
267
|
}
|
|
265
268
|
if (item.kind === "tool_group") {
|
|
266
|
-
return /* @__PURE__ */ jsx4(AgentTimelineRow, { isLast, accentClassName: "bg-[var(--border-hover)]", children: /* @__PURE__ */ jsx4(ToolCallGroup, { title: item.title, children: item.calls.map((call) =>
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
269
|
+
return /* @__PURE__ */ jsx4(AgentTimelineRow, { isLast, accentClassName: "bg-[var(--border-hover)]", children: /* @__PURE__ */ jsx4(ToolCallGroup, { title: item.title, children: item.calls.map((call, callIndex) => {
|
|
270
|
+
const part = item.parts?.[callIndex];
|
|
271
|
+
return /* @__PURE__ */ jsx4(
|
|
272
|
+
ToolCallStep,
|
|
273
|
+
{
|
|
274
|
+
type: call.type,
|
|
275
|
+
label: call.label,
|
|
276
|
+
status: call.status,
|
|
277
|
+
detail: call.detail,
|
|
278
|
+
output: call.output,
|
|
279
|
+
duration: call.duration,
|
|
280
|
+
part,
|
|
281
|
+
actions: part ? renderToolActions?.(part) : void 0
|
|
282
|
+
},
|
|
283
|
+
call.id
|
|
284
|
+
);
|
|
285
|
+
}) }) }, item.id);
|
|
278
286
|
}
|
|
279
287
|
if (item.kind === "status") {
|
|
280
288
|
return /* @__PURE__ */ jsx4(
|
|
@@ -407,9 +415,11 @@ function buildTimelineItems(messages, partMap, isStreaming, onOpenUIAction, enab
|
|
|
407
415
|
return {
|
|
408
416
|
id: part.id,
|
|
409
417
|
type: mapToolPartToTimelineType(part),
|
|
410
|
-
label: meta.
|
|
418
|
+
label: meta.title,
|
|
411
419
|
status: part.state.status === "completed" ? "success" : part.state.status === "error" ? "error" : "running",
|
|
412
|
-
|
|
420
|
+
// A human-readable summary (file path / command), not the raw input JSON —
|
|
421
|
+
// the full input still renders in the expanded ExpandedToolDetail.
|
|
422
|
+
detail: meta.commandSnippet ?? meta.targetPath ?? meta.description,
|
|
413
423
|
output: formatUnknown(part.state.output),
|
|
414
424
|
duration: start && end ? end - start : void 0
|
|
415
425
|
};
|
|
@@ -435,13 +445,15 @@ function buildTimelineItems(messages, partMap, isStreaming, onOpenUIAction, enab
|
|
|
435
445
|
items.push({
|
|
436
446
|
id: `${message.id}-tool-${toolBuffer[0].id}`,
|
|
437
447
|
kind: "tool",
|
|
438
|
-
call: toToolCall(toolBuffer[0])
|
|
448
|
+
call: toToolCall(toolBuffer[0]),
|
|
449
|
+
part: toolBuffer[0]
|
|
439
450
|
});
|
|
440
451
|
} else {
|
|
441
452
|
items.push({
|
|
442
453
|
id: `${message.id}-tool-group-${index}`,
|
|
443
454
|
kind: "tool_group",
|
|
444
455
|
title: "Tool activity",
|
|
456
|
+
parts: [...toolBuffer],
|
|
445
457
|
calls: toolBuffer.map((part) => toToolCall(part))
|
|
446
458
|
});
|
|
447
459
|
}
|
|
@@ -540,7 +552,8 @@ var ChatContainer = memo3(
|
|
|
540
552
|
enableOpenUI = true,
|
|
541
553
|
renderRunActions,
|
|
542
554
|
renderUserMessageActions,
|
|
543
|
-
renderToolActions
|
|
555
|
+
renderToolActions,
|
|
556
|
+
renderTimelineToolActions
|
|
544
557
|
}) => {
|
|
545
558
|
const scrollRef = useRef(null);
|
|
546
559
|
const groups = useRunGroups({ messages, partMap, isStreaming });
|
|
@@ -561,7 +574,14 @@ var ChatContainer = memo3(
|
|
|
561
574
|
{
|
|
562
575
|
ref: scrollRef,
|
|
563
576
|
className: "flex-1 overflow-y-auto [scrollbar-gutter:stable]",
|
|
564
|
-
children: messages.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx5("div", { className: "max-w-md text-center", children: /* @__PURE__ */ jsx5("div", { className: "text-sm font-medium text-muted-foreground", children: "Start a conversation." }) }) }) : presentation === "timeline" ? /* @__PURE__ */ jsx5("div", { className: "mx-auto flex min-h-full w-full max-w-3xl flex-col justify-end", children: /* @__PURE__ */ jsx5(
|
|
577
|
+
children: messages.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx5("div", { className: "max-w-md text-center", children: /* @__PURE__ */ jsx5("div", { className: "text-sm font-medium text-muted-foreground", children: "Start a conversation." }) }) }) : presentation === "timeline" ? /* @__PURE__ */ jsx5("div", { className: "mx-auto flex min-h-full w-full max-w-3xl flex-col justify-end", children: /* @__PURE__ */ jsx5(
|
|
578
|
+
AgentTimeline,
|
|
579
|
+
{
|
|
580
|
+
items: timeline.items,
|
|
581
|
+
isThinking: timeline.showThinking,
|
|
582
|
+
renderToolActions: renderTimelineToolActions
|
|
583
|
+
}
|
|
584
|
+
) }) : /* @__PURE__ */ jsx5("div", { className: "mx-auto flex min-h-full w-full max-w-3xl flex-col justify-end", children: /* @__PURE__ */ jsx5(
|
|
565
585
|
MessageList,
|
|
566
586
|
{
|
|
567
587
|
groups,
|
package/dist/hooks.js
CHANGED
|
@@ -11,15 +11,15 @@ import {
|
|
|
11
11
|
useSSEStream,
|
|
12
12
|
useSdkSession,
|
|
13
13
|
useToolCallStream
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-IVQYFG5D.js";
|
|
15
15
|
import "./chunk-OEX7NZE3.js";
|
|
16
16
|
import {
|
|
17
17
|
useAutoScroll,
|
|
18
18
|
useRunCollapseState,
|
|
19
19
|
useRunGroups
|
|
20
20
|
} from "./chunk-AZWDI2JG.js";
|
|
21
|
-
import "./chunk-
|
|
22
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-V6BF4AZ7.js";
|
|
22
|
+
import "./chunk-WVW4KHEH.js";
|
|
23
23
|
import "./chunk-ULDNFLIM.js";
|
|
24
24
|
import "./chunk-AAUNOHVL.js";
|
|
25
25
|
import "./chunk-ZRVH3WCA.js";
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
useSSEStream,
|
|
18
18
|
useSdkSession,
|
|
19
19
|
useToolCallStream
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-IVQYFG5D.js";
|
|
21
21
|
import {
|
|
22
22
|
addMessage,
|
|
23
23
|
addParts,
|
|
@@ -137,7 +137,7 @@ import {
|
|
|
137
137
|
MessageList,
|
|
138
138
|
ThinkingIndicator,
|
|
139
139
|
UserMessage
|
|
140
|
-
} from "./chunk-
|
|
140
|
+
} from "./chunk-YUY6D62P.js";
|
|
141
141
|
import {
|
|
142
142
|
useAutoScroll,
|
|
143
143
|
useRunCollapseState,
|
|
@@ -147,16 +147,16 @@ import "./chunk-47XH56SV.js";
|
|
|
147
147
|
import {
|
|
148
148
|
ToolCallFeed,
|
|
149
149
|
parseToolEvent
|
|
150
|
-
} from "./chunk-
|
|
150
|
+
} from "./chunk-V6BF4AZ7.js";
|
|
151
151
|
import {
|
|
152
152
|
InlineThinkingItem,
|
|
153
153
|
RunGroup
|
|
154
|
-
} from "./chunk-
|
|
154
|
+
} from "./chunk-KWRLQ3MR.js";
|
|
155
155
|
import {
|
|
156
156
|
ExpandedToolDetail,
|
|
157
157
|
InlineToolItem,
|
|
158
158
|
LiveDuration
|
|
159
|
-
} from "./chunk-
|
|
159
|
+
} from "./chunk-WVW4KHEH.js";
|
|
160
160
|
import {
|
|
161
161
|
TOOL_CATEGORY_ICONS,
|
|
162
162
|
formatBytes,
|
package/dist/run.js
CHANGED
|
@@ -2,16 +2,16 @@ import "./chunk-47XH56SV.js";
|
|
|
2
2
|
import {
|
|
3
3
|
ToolCallFeed,
|
|
4
4
|
parseToolEvent
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-V6BF4AZ7.js";
|
|
6
6
|
import {
|
|
7
7
|
InlineThinkingItem,
|
|
8
8
|
RunGroup
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-KWRLQ3MR.js";
|
|
10
10
|
import {
|
|
11
11
|
ExpandedToolDetail,
|
|
12
12
|
InlineToolItem,
|
|
13
13
|
LiveDuration
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-WVW4KHEH.js";
|
|
15
15
|
import "./chunk-ULDNFLIM.js";
|
|
16
16
|
import "./chunk-AAUNOHVL.js";
|
|
17
17
|
import "./chunk-52Y3FMFI.js";
|
package/dist/sdk-hooks.js
CHANGED
|
@@ -5,15 +5,15 @@ import {
|
|
|
5
5
|
useSSEStream,
|
|
6
6
|
useSdkSession,
|
|
7
7
|
useToolCallStream
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IVQYFG5D.js";
|
|
9
9
|
import "./chunk-OEX7NZE3.js";
|
|
10
10
|
import {
|
|
11
11
|
useAutoScroll,
|
|
12
12
|
useRunCollapseState,
|
|
13
13
|
useRunGroups
|
|
14
14
|
} from "./chunk-AZWDI2JG.js";
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-V6BF4AZ7.js";
|
|
16
|
+
import "./chunk-WVW4KHEH.js";
|
|
17
17
|
import "./chunk-ULDNFLIM.js";
|
|
18
18
|
import "./chunk-AAUNOHVL.js";
|
|
19
19
|
import "./chunk-ZRVH3WCA.js";
|
package/package.json
CHANGED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react"
|
|
2
|
+
import userEvent from "@testing-library/user-event"
|
|
3
|
+
import { describe, expect, it, vi } from "vitest"
|
|
4
|
+
import type { ToolPart } from "../types/parts"
|
|
5
|
+
import { AgentTimeline, type AgentTimelineItem } from "./agent-timeline"
|
|
6
|
+
|
|
7
|
+
const editPart: ToolPart = {
|
|
8
|
+
type: "tool",
|
|
9
|
+
id: "tool-1",
|
|
10
|
+
tool: "edit",
|
|
11
|
+
state: {
|
|
12
|
+
status: "completed",
|
|
13
|
+
input: { path: "src/batch-writer.ts" },
|
|
14
|
+
output: "Applied 1 edit",
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const readPart: ToolPart = {
|
|
19
|
+
type: "tool",
|
|
20
|
+
id: "tool-2",
|
|
21
|
+
tool: "read",
|
|
22
|
+
state: {
|
|
23
|
+
status: "completed",
|
|
24
|
+
input: { path: "src/lib/jitter.ts" },
|
|
25
|
+
output: "export function jitter() {}",
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function toolItem(part: ToolPart): AgentTimelineItem {
|
|
30
|
+
return {
|
|
31
|
+
id: "item-1",
|
|
32
|
+
kind: "tool",
|
|
33
|
+
call: {
|
|
34
|
+
id: part.id,
|
|
35
|
+
type: "edit",
|
|
36
|
+
label: "Edit src/batch-writer.ts",
|
|
37
|
+
status: "success",
|
|
38
|
+
detail: "src/batch-writer.ts",
|
|
39
|
+
output: "Applied 1 edit",
|
|
40
|
+
},
|
|
41
|
+
part,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("AgentTimeline tool actions", () => {
|
|
46
|
+
it("renders a tool call's label and clean detail", () => {
|
|
47
|
+
render(<AgentTimeline items={[toolItem(editPart)]} />)
|
|
48
|
+
expect(screen.getByText("Edit src/batch-writer.ts")).toBeInTheDocument()
|
|
49
|
+
expect(screen.getByText("src/batch-writer.ts")).toBeInTheDocument()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("renders renderToolActions beside the tool item, called with the source part", () => {
|
|
53
|
+
const renderToolActions = vi.fn((part: ToolPart) => (
|
|
54
|
+
<button type="button">Open {part.id}</button>
|
|
55
|
+
))
|
|
56
|
+
|
|
57
|
+
render(
|
|
58
|
+
<AgentTimeline
|
|
59
|
+
items={[toolItem(editPart)]}
|
|
60
|
+
renderToolActions={renderToolActions}
|
|
61
|
+
/>,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
expect(renderToolActions).toHaveBeenCalledWith(editPart)
|
|
65
|
+
expect(
|
|
66
|
+
screen.getByRole("button", { name: /open tool-1/i }),
|
|
67
|
+
).toBeInTheDocument()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it("renders no action when the item carries no source part", () => {
|
|
71
|
+
const renderToolActions = vi.fn(() => <button type="button">Open</button>)
|
|
72
|
+
const item = { ...toolItem(editPart), part: undefined }
|
|
73
|
+
|
|
74
|
+
render(
|
|
75
|
+
<AgentTimeline items={[item]} renderToolActions={renderToolActions} />,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
expect(renderToolActions).not.toHaveBeenCalled()
|
|
79
|
+
expect(screen.queryByRole("button", { name: /open/i })).not.toBeInTheDocument()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it("renders tool items unchanged when no renderToolActions is provided", () => {
|
|
83
|
+
render(<AgentTimeline items={[toolItem(editPart)]} />)
|
|
84
|
+
expect(screen.getByText("Edit src/batch-writer.ts")).toBeInTheDocument()
|
|
85
|
+
expect(screen.queryByRole("button", { name: /open/i })).not.toBeInTheDocument()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it("renders renderToolActions for each call in a tool group, with its part", () => {
|
|
89
|
+
const renderToolActions = vi.fn((part: ToolPart) => (
|
|
90
|
+
<button type="button">Open {part.id}</button>
|
|
91
|
+
))
|
|
92
|
+
const groupItem: AgentTimelineItem = {
|
|
93
|
+
id: "group-1",
|
|
94
|
+
kind: "tool_group",
|
|
95
|
+
title: "Tool activity",
|
|
96
|
+
calls: [
|
|
97
|
+
{
|
|
98
|
+
id: "tool-1",
|
|
99
|
+
type: "edit",
|
|
100
|
+
label: "Edit src/batch-writer.ts",
|
|
101
|
+
status: "success",
|
|
102
|
+
detail: "src/batch-writer.ts",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "tool-2",
|
|
106
|
+
type: "read",
|
|
107
|
+
label: "Read src/lib/jitter.ts",
|
|
108
|
+
status: "success",
|
|
109
|
+
detail: "src/lib/jitter.ts",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
parts: [editPart, readPart],
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
render(
|
|
116
|
+
<AgentTimeline items={[groupItem]} renderToolActions={renderToolActions} />,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
expect(renderToolActions).toHaveBeenCalledWith(editPart)
|
|
120
|
+
expect(renderToolActions).toHaveBeenCalledWith(readPart)
|
|
121
|
+
expect(
|
|
122
|
+
screen.getByRole("button", { name: /open tool-1/i }),
|
|
123
|
+
).toBeInTheDocument()
|
|
124
|
+
expect(
|
|
125
|
+
screen.getByRole("button", { name: /open tool-2/i }),
|
|
126
|
+
).toBeInTheDocument()
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it("renders the source part's real input in the expanded detail", async () => {
|
|
130
|
+
const user = userEvent.setup()
|
|
131
|
+
const probePart: ToolPart = {
|
|
132
|
+
type: "tool",
|
|
133
|
+
id: "tool-3",
|
|
134
|
+
tool: "custom_probe",
|
|
135
|
+
state: {
|
|
136
|
+
status: "completed",
|
|
137
|
+
input: { marker: "deep-input-value" },
|
|
138
|
+
output: "done",
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
const item: AgentTimelineItem = {
|
|
142
|
+
id: "item-3",
|
|
143
|
+
kind: "tool",
|
|
144
|
+
call: {
|
|
145
|
+
id: "tool-3",
|
|
146
|
+
type: "unknown",
|
|
147
|
+
label: "custom_probe",
|
|
148
|
+
status: "success",
|
|
149
|
+
detail: "probe",
|
|
150
|
+
},
|
|
151
|
+
part: probePart,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
render(<AgentTimeline items={[item]} />)
|
|
155
|
+
await user.click(screen.getByRole("button", { name: /custom_probe/i }))
|
|
156
|
+
|
|
157
|
+
// ExpandedToolDetail (fed the real part) renders labelled Input/Output
|
|
158
|
+
// sections — the synthesized-part path showed only a bare output block.
|
|
159
|
+
expect(screen.getByText("Input")).toBeInTheDocument()
|
|
160
|
+
expect(screen.getByText("Output")).toBeInTheDocument()
|
|
161
|
+
})
|
|
162
|
+
})
|
|
@@ -12,6 +12,7 @@ import { Markdown } from "../markdown/markdown";
|
|
|
12
12
|
import { ThinkingIndicator } from "./thinking-indicator";
|
|
13
13
|
import { type ToolCallData } from "../run/tool-call-feed";
|
|
14
14
|
import { ToolCallGroup, ToolCallStep } from "../run/tool-call-step";
|
|
15
|
+
import type { ToolPart } from "../types/parts";
|
|
15
16
|
|
|
16
17
|
export type AgentTimelineTone = "default" | "info" | "success" | "warning" | "error";
|
|
17
18
|
|
|
@@ -30,6 +31,9 @@ export interface AgentTimelineToolItem {
|
|
|
30
31
|
id: string;
|
|
31
32
|
kind: "tool";
|
|
32
33
|
call: ToolCallData;
|
|
34
|
+
/** Source tool part, so a consumer's `renderToolActions` gets the real
|
|
35
|
+
* input/output (the flat `call` is display-only). */
|
|
36
|
+
part?: ToolPart;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
export interface AgentTimelineToolGroupItem {
|
|
@@ -37,6 +41,8 @@ export interface AgentTimelineToolGroupItem {
|
|
|
37
41
|
kind: "tool_group";
|
|
38
42
|
title?: string;
|
|
39
43
|
calls: ToolCallData[];
|
|
44
|
+
/** Source tool parts, parallel to `calls`. */
|
|
45
|
+
parts?: ToolPart[];
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
export interface AgentTimelineStatusItem {
|
|
@@ -78,6 +84,9 @@ export interface AgentTimelineProps {
|
|
|
78
84
|
isThinking?: boolean;
|
|
79
85
|
emptyState?: ReactNode;
|
|
80
86
|
className?: string;
|
|
87
|
+
/** Optional actions rendered beside each tool item (e.g. "open in artifacts").
|
|
88
|
+
* Receives the source tool part carried on the item. */
|
|
89
|
+
renderToolActions?: (part: ToolPart) => ReactNode;
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
const TONE_STYLES: Record<AgentTimelineTone, { dot: string; card: string; text: string; icon: typeof Info }> = {
|
|
@@ -253,6 +262,7 @@ export function AgentTimeline({
|
|
|
253
262
|
isThinking,
|
|
254
263
|
emptyState,
|
|
255
264
|
className,
|
|
265
|
+
renderToolActions,
|
|
256
266
|
}: AgentTimelineProps) {
|
|
257
267
|
if (items.length === 0 && !isThinking) {
|
|
258
268
|
return emptyState ? (
|
|
@@ -299,6 +309,8 @@ export function AgentTimeline({
|
|
|
299
309
|
detail={item.call.detail}
|
|
300
310
|
output={item.call.output}
|
|
301
311
|
duration={item.call.duration}
|
|
312
|
+
part={item.part}
|
|
313
|
+
actions={item.part ? renderToolActions?.(item.part) : undefined}
|
|
302
314
|
/>
|
|
303
315
|
</AgentTimelineRow>
|
|
304
316
|
);
|
|
@@ -308,17 +320,22 @@ export function AgentTimeline({
|
|
|
308
320
|
return (
|
|
309
321
|
<AgentTimelineRow key={item.id} isLast={isLast} accentClassName="bg-[var(--border-hover)]">
|
|
310
322
|
<ToolCallGroup title={item.title}>
|
|
311
|
-
{item.calls.map((call) =>
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
323
|
+
{item.calls.map((call, callIndex) => {
|
|
324
|
+
const part = item.parts?.[callIndex];
|
|
325
|
+
return (
|
|
326
|
+
<ToolCallStep
|
|
327
|
+
key={call.id}
|
|
328
|
+
type={call.type}
|
|
329
|
+
label={call.label}
|
|
330
|
+
status={call.status}
|
|
331
|
+
detail={call.detail}
|
|
332
|
+
output={call.output}
|
|
333
|
+
duration={call.duration}
|
|
334
|
+
part={part}
|
|
335
|
+
actions={part ? renderToolActions?.(part) : undefined}
|
|
336
|
+
/>
|
|
337
|
+
);
|
|
338
|
+
})}
|
|
322
339
|
</ToolCallGroup>
|
|
323
340
|
</AgentTimelineRow>
|
|
324
341
|
);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react"
|
|
2
|
+
import { beforeAll, describe, expect, it, vi } from "vitest"
|
|
3
|
+
import type { SessionMessage } from "../types/message"
|
|
4
|
+
import type { SessionPart, ToolPart } from "../types/parts"
|
|
5
|
+
import { ChatContainer } from "./chat-container"
|
|
6
|
+
|
|
7
|
+
// jsdom has no layout engine; the auto-scroll hook calls scrollIntoView.
|
|
8
|
+
beforeAll(() => {
|
|
9
|
+
Element.prototype.scrollIntoView = vi.fn()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const messages: SessionMessage[] = [
|
|
13
|
+
{ id: "m1", role: "assistant" },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
const editPart: ToolPart = {
|
|
17
|
+
type: "tool",
|
|
18
|
+
id: "tool-1",
|
|
19
|
+
tool: "edit",
|
|
20
|
+
state: {
|
|
21
|
+
status: "completed",
|
|
22
|
+
input: { path: "src/batch-writer.ts" },
|
|
23
|
+
output: "Applied 1 edit to src/batch-writer.ts",
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const partMap: Record<string, SessionPart[]> = { m1: [editPart] }
|
|
28
|
+
|
|
29
|
+
describe("ChatContainer timeline tool rendering", () => {
|
|
30
|
+
it("shows a clean tool detail (the file path), not the raw input JSON", () => {
|
|
31
|
+
render(
|
|
32
|
+
<ChatContainer
|
|
33
|
+
messages={messages}
|
|
34
|
+
partMap={partMap}
|
|
35
|
+
isStreaming={false}
|
|
36
|
+
presentation="timeline"
|
|
37
|
+
/>,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
expect(screen.getByText("src/batch-writer.ts")).toBeInTheDocument()
|
|
41
|
+
// The raw `{ "path": ... }` input JSON must not leak into the summary row.
|
|
42
|
+
expect(screen.queryByText(/"path"/)).not.toBeInTheDocument()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("threads renderTimelineToolActions to the timeline tool item with its source part", () => {
|
|
46
|
+
const renderTimelineToolActions = vi.fn((part: ToolPart) => (
|
|
47
|
+
<button type="button">Open {part.id}</button>
|
|
48
|
+
))
|
|
49
|
+
|
|
50
|
+
render(
|
|
51
|
+
<ChatContainer
|
|
52
|
+
messages={messages}
|
|
53
|
+
partMap={partMap}
|
|
54
|
+
isStreaming={false}
|
|
55
|
+
presentation="timeline"
|
|
56
|
+
renderTimelineToolActions={renderTimelineToolActions}
|
|
57
|
+
/>,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
expect(renderTimelineToolActions).toHaveBeenCalled()
|
|
61
|
+
expect(renderTimelineToolActions.mock.calls[0][0]).toMatchObject({
|
|
62
|
+
id: "tool-1",
|
|
63
|
+
})
|
|
64
|
+
expect(
|
|
65
|
+
screen.getByRole("button", { name: /open tool-1/i }),
|
|
66
|
+
).toBeInTheDocument()
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -50,7 +50,8 @@ export interface ChatContainerProps {
|
|
|
50
50
|
renderRunActions?: (run: Run) => ReactNode;
|
|
51
51
|
/** Optional actions rendered below each user message bubble. */
|
|
52
52
|
renderUserMessageActions?: (message: SessionMessage, parts: SessionPart[]) => ReactNode;
|
|
53
|
-
/** Optional actions rendered beside individual tool items
|
|
53
|
+
/** Optional actions rendered beside individual tool items in the run-grouped
|
|
54
|
+
* (`runs`) presentation. */
|
|
54
55
|
renderToolActions?: (
|
|
55
56
|
part: ToolPart,
|
|
56
57
|
options: {
|
|
@@ -59,6 +60,9 @@ export interface ChatContainerProps {
|
|
|
59
60
|
partIndex: number;
|
|
60
61
|
},
|
|
61
62
|
) => ReactNode;
|
|
63
|
+
/** Optional actions rendered beside individual tool items in the `timeline`
|
|
64
|
+
* presentation, which has no run/message grouping (hence part-only). */
|
|
65
|
+
renderTimelineToolActions?: (part: ToolPart) => ReactNode;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
const OPENUI_NODE_TYPES = new Set([
|
|
@@ -181,14 +185,16 @@ function buildTimelineItems(
|
|
|
181
185
|
return {
|
|
182
186
|
id: part.id,
|
|
183
187
|
type: mapToolPartToTimelineType(part),
|
|
184
|
-
label: meta.
|
|
188
|
+
label: meta.title,
|
|
185
189
|
status:
|
|
186
190
|
part.state.status === "completed"
|
|
187
191
|
? "success"
|
|
188
192
|
: part.state.status === "error"
|
|
189
193
|
? "error"
|
|
190
194
|
: "running",
|
|
191
|
-
|
|
195
|
+
// A human-readable summary (file path / command), not the raw input JSON —
|
|
196
|
+
// the full input still renders in the expanded ExpandedToolDetail.
|
|
197
|
+
detail: meta.commandSnippet ?? meta.targetPath ?? meta.description,
|
|
192
198
|
output: formatUnknown(part.state.output),
|
|
193
199
|
duration: start && end ? end - start : undefined,
|
|
194
200
|
} as const;
|
|
@@ -225,12 +231,14 @@ function buildTimelineItems(
|
|
|
225
231
|
id: `${message.id}-tool-${toolBuffer[0].id}`,
|
|
226
232
|
kind: "tool",
|
|
227
233
|
call: toToolCall(toolBuffer[0]),
|
|
234
|
+
part: toolBuffer[0],
|
|
228
235
|
});
|
|
229
236
|
} else {
|
|
230
237
|
items.push({
|
|
231
238
|
id: `${message.id}-tool-group-${index}`,
|
|
232
239
|
kind: "tool_group",
|
|
233
240
|
title: "Tool activity",
|
|
241
|
+
parts: [...toolBuffer],
|
|
234
242
|
calls: toolBuffer.map((part) => toToolCall(part)),
|
|
235
243
|
});
|
|
236
244
|
}
|
|
@@ -364,6 +372,7 @@ export const ChatContainer = memo(
|
|
|
364
372
|
renderRunActions,
|
|
365
373
|
renderUserMessageActions,
|
|
366
374
|
renderToolActions,
|
|
375
|
+
renderTimelineToolActions,
|
|
367
376
|
}: ChatContainerProps) => {
|
|
368
377
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
369
378
|
|
|
@@ -401,7 +410,11 @@ export const ChatContainer = memo(
|
|
|
401
410
|
</div>
|
|
402
411
|
) : presentation === "timeline" ? (
|
|
403
412
|
<div className="mx-auto flex min-h-full w-full max-w-3xl flex-col justify-end">
|
|
404
|
-
<AgentTimeline
|
|
413
|
+
<AgentTimeline
|
|
414
|
+
items={timeline.items}
|
|
415
|
+
isThinking={timeline.showThinking}
|
|
416
|
+
renderToolActions={renderTimelineToolActions}
|
|
417
|
+
/>
|
|
405
418
|
</div>
|
|
406
419
|
) : (
|
|
407
420
|
<div className="mx-auto flex min-h-full w-full max-w-3xl flex-col justify-end">
|
|
@@ -109,8 +109,8 @@ export const InlineToolItem = memo(
|
|
|
109
109
|
<button
|
|
110
110
|
className={cn(
|
|
111
111
|
"w-full border text-left transition-colors",
|
|
112
|
-
"border-[var(--border-subtle)] bg-
|
|
113
|
-
open && "border-border bg-
|
|
112
|
+
"border-[var(--border-subtle)] bg-[var(--md3-surface-container)] hover:border-border hover:bg-[var(--md3-surface-container-high)]",
|
|
113
|
+
open && "border-border bg-[var(--md3-surface-container-high)]",
|
|
114
114
|
shapeClass,
|
|
115
115
|
className,
|
|
116
116
|
)}
|
package/src/run/run-group.tsx
CHANGED
|
@@ -366,7 +366,7 @@ export const RunGroup = memo(
|
|
|
366
366
|
return (
|
|
367
367
|
<div
|
|
368
368
|
key={key}
|
|
369
|
-
className="overflow-hidden rounded-[
|
|
369
|
+
className="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)]"
|
|
370
370
|
>
|
|
371
371
|
{summary ? (
|
|
372
372
|
<div className="border-b border-[var(--border-subtle)] px-4 py-3 text-sm leading-6 text-foreground">
|
|
@@ -384,7 +384,7 @@ export const RunGroup = memo(
|
|
|
384
384
|
return (
|
|
385
385
|
<div
|
|
386
386
|
key={key}
|
|
387
|
-
className="flex items-center gap-2 rounded-[
|
|
387
|
+
className="flex items-center gap-2 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)] px-4 py-3 text-sm text-muted-foreground"
|
|
388
388
|
>
|
|
389
389
|
<Loader2 className="h-4 w-4 animate-spin text-primary" />
|
|
390
390
|
Building view…
|
|
@@ -493,7 +493,7 @@ export const RunGroup = memo(
|
|
|
493
493
|
|
|
494
494
|
if (toolPart.state.status === "completed" && schema) {
|
|
495
495
|
node = (
|
|
496
|
-
<div className="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--
|
|
496
|
+
<div className="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)]">
|
|
497
497
|
{summary ? (
|
|
498
498
|
<div className="border-b border-[var(--border-subtle)] px-4 py-3 text-sm leading-6 text-foreground">
|
|
499
499
|
{summary}
|
|
@@ -506,7 +506,7 @@ export const RunGroup = memo(
|
|
|
506
506
|
);
|
|
507
507
|
} else if (toolPart.state.status === "running") {
|
|
508
508
|
node = (
|
|
509
|
-
<div className="flex items-center gap-3 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--
|
|
509
|
+
<div className="flex items-center gap-3 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] bg-[var(--md3-surface-container)] px-4 py-3 text-sm text-muted-foreground">
|
|
510
510
|
<Loader2 className="h-4 w-4 animate-spin text-primary" />
|
|
511
511
|
Building view…
|
|
512
512
|
</div>
|
|
@@ -36,6 +36,11 @@ export interface ToolCallStepProps {
|
|
|
36
36
|
language?: string;
|
|
37
37
|
duration?: number;
|
|
38
38
|
className?: string;
|
|
39
|
+
/** Actions rendered beside the row (e.g. "open in artifacts"). */
|
|
40
|
+
actions?: ReactNode;
|
|
41
|
+
/** Source tool part. When provided, the expanded detail renders from the real
|
|
42
|
+
* input/output (via ExpandedToolDetail) rather than the flat summary props. */
|
|
43
|
+
part?: ToolPart;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
const EXT_LANGUAGE: Record<string, string> = {
|
|
@@ -77,8 +82,12 @@ export function ToolCallStep({
|
|
|
77
82
|
language,
|
|
78
83
|
duration,
|
|
79
84
|
className,
|
|
85
|
+
actions,
|
|
86
|
+
part,
|
|
80
87
|
}: ToolCallStepProps) {
|
|
81
|
-
|
|
88
|
+
// Fall back to a synthesized part for callers that only have flat props
|
|
89
|
+
// (e.g. ToolCallFeed); the real part, when supplied, drives the expanded view.
|
|
90
|
+
const resolvedPart: ToolPart = part ?? {
|
|
82
91
|
type: "tool",
|
|
83
92
|
id: `${type}:${label}`,
|
|
84
93
|
tool: type,
|
|
@@ -94,20 +103,26 @@ export function ToolCallStep({
|
|
|
94
103
|
|
|
95
104
|
return (
|
|
96
105
|
<InlineToolItem
|
|
97
|
-
part={
|
|
106
|
+
part={resolvedPart}
|
|
98
107
|
title={label}
|
|
99
108
|
description={detail}
|
|
100
109
|
className={className}
|
|
110
|
+
actions={actions}
|
|
111
|
+
// With a real part, InlineToolItem's default ExpandedToolDetail renders
|
|
112
|
+
// the full input + output. The synthesized-part path keeps the output-only
|
|
113
|
+
// fallback (it has no real input to show).
|
|
101
114
|
renderToolDetail={
|
|
102
|
-
|
|
103
|
-
?
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
part
|
|
116
|
+
? undefined
|
|
117
|
+
: output
|
|
118
|
+
? () => (
|
|
119
|
+
<CodeBlock
|
|
120
|
+
code={output}
|
|
121
|
+
language={lang}
|
|
122
|
+
className="max-h-72 overflow-auto text-xs"
|
|
123
|
+
/>
|
|
124
|
+
)
|
|
125
|
+
: () => null
|
|
111
126
|
}
|
|
112
127
|
/>
|
|
113
128
|
);
|