@marimo-team/islands 0.25.3-dev266 → 0.25.3-dev268
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/dist/{ConnectedDataExplorerComponent-C_hj7Poi.js → ConnectedDataExplorerComponent-eLHPh6uu.js} +9 -9
- package/dist/{ErrorBoundary-CvxlC3F1.js → ErrorBoundary-CQYAoD7X.js} +64 -69
- package/dist/{abnfDiagram-VCTEODGH-DzrZZXRD.js → abnfDiagram-VCTEODGH-hqd0IBtX.js} +2 -2
- package/dist/{any-language-editor-CFeWfC3o.js → any-language-editor-DnG26QXh.js} +1 -1
- package/dist/{architectureDiagram-5GKGNRK7-iF_snyFL.js → architectureDiagram-5GKGNRK7-BzLAFcc0.js} +1 -1
- package/dist/{strings-BCKpLFNU.js → assertNever-DIU3JJbt.js} +14 -14
- package/dist/{chat-ui-DFxNgzXF.js → chat-ui-BAnLgpTe.js} +1964 -1740
- package/dist/{chunk-IMKFNOWR-B7xytD4B.js → chunk-IMKFNOWR-DOeVvuyE.js} +1 -1
- package/dist/{chunk-SHT3W25Y-I8ZFP5L1.js → chunk-SHT3W25Y-DZoFI8wR.js} +1 -1
- package/dist/{chunk-SVP7TREG-Di-uAPdT.js → chunk-SVP7TREG-CWu86PSW.js} +1 -1
- package/dist/{chunk-TICWLB2K-BFpUy0ud.js → chunk-TICWLB2K-BrM0GoP3.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-Cqm4JGpx.js → classDiagram-ZZMXUADV-DYi4V2bB.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-DFHRJpN3.js → classDiagram-v2-VYDZK3BY-tN7FIk-m.js} +2 -2
- package/dist/{code-visibility-SRM1JiD_.js → code-visibility-DcZ_RwaH.js} +44 -47
- package/dist/{common-yiRhZ6ZZ.js → common-BGeQilvh.js} +5846 -5519
- package/dist/{config-CmeG_Pb6.js → config-BsPruWMR.js} +2 -2
- package/dist/{createReducer-C_syF8Td.js → createReducer-DdtT_7ji.js} +1 -1
- package/dist/{cynefinDiagram-5FMLGOSQ-cuGOeTBC.js → cynefinDiagram-5FMLGOSQ-D5nCnDc-.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-DJ76aC4k.js → dagre-GXQ25YYZ-BHkd7f_i.js} +1 -1
- package/dist/{data-grid-overlay-editor-DLTzcQ4O.js → data-grid-overlay-editor-Dc4lW4A3.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-DLLc0sw7.js → diagram-S7CK7UJ4-D1ALppT2.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-Tz_d6qmk.js → diagram-UQ7AKVKN-DLv0b83L.js} +1 -1
- package/dist/{diagram-VX7I27RA-D7SzPrDN.js → diagram-VX7I27RA-7XJ7J6qg.js} +1 -1
- package/dist/{diagram-Z3DM3KII-qr37XV58.js → diagram-Z3DM3KII-7M7zbHp_.js} +1 -1
- package/dist/{dialog-BtprQCUw.js → dialog-DzCj1qQv.js} +1 -1
- package/dist/{dropdown-menu-CkJ7ErY6.js → dropdown-menu-CdXE15an.js} +1214 -1208
- package/dist/{ebnfDiagram-PWID7BFC-BKOQb4J0.js → ebnfDiagram-PWID7BFC-DHH1qlmV.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-CM15Vq68.js → erDiagram-RLTQ6QDP-fI_0dGef.js} +1 -1
- package/dist/{error-banner-DbRFOd2O.js → error-banner-DPc9eIw5.js} +3 -3
- package/dist/{error-entries-CMoaUX9V.js → error-entries-CxN-Nq3V.js} +524 -456
- package/dist/{feedback-button-DBUrE4OL.js → feedback-button-BqaeQcvX.js} +77 -77
- package/dist/{flowDiagram-HODETNUW-Dt3N5yFT.js → flowDiagram-HODETNUW-Brg9MA6i.js} +2 -2
- package/dist/{formats-gGospKWJ.js → formats-DbSvAPY-.js} +1 -1
- package/dist/{glide-data-editor-Cfv4HeOh.js → glide-data-editor-CiijPXA7.js} +2042 -2041
- package/dist/{glide-utils-Ba9WD2IN.js → glide-utils-BSw3SSHF.js} +29 -35
- package/dist/{globals-dIbBIR5-.js → globals-CpmSWKI_.js} +2 -2
- package/dist/{html-to-image-D_5CMreP.js → html-to-image-8sywF4my.js} +5452 -5751
- package/dist/{infoDiagram-27XIBGKW-C7axJMCl.js → infoDiagram-27XIBGKW-E9e403YT.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-CdC9Eur7.js → ishikawaDiagram-5VMMS53U-C-RlYDRo.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-BHnMIgD7.js → kanban-definition-UXKFOSKX-CLFl_uqK.js} +1 -1
- package/dist/main.js +6294 -6324
- package/dist/{mermaid-DFYN_PXd.js → mermaid-rd09e1JL.js} +35 -35
- package/dist/{mindmap-definition-YA3MSWOX-BagHoCGU.js → mindmap-definition-YA3MSWOX-BQ8TWtyB.js} +1 -1
- package/dist/{number-overlay-editor-CTiJp5Mj.js → number-overlay-editor-Ft4NiAEN.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-8IIZczip.js → pegDiagram-XKGWAZYB-CN8UHFPi.js} +2 -2
- package/dist/pencil-DcT8AE0h.js +10 -0
- package/dist/{pieDiagram-E7YTZNPT-CE6dKBIr.js → pieDiagram-E7YTZNPT-DGuJ0EBd.js} +1 -1
- package/dist/{process-output-CGFNVC60.js → process-output-Dku5POvm.js} +3 -3
- package/dist/{railroadDiagram-O6MQD6OU-CkexPq3b.js → railroadDiagram-O6MQD6OU-BPDA8wy8.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-B0-OkuAe.js → requirementDiagram-BXWQKSXE-ChzLGbGW.js} +1 -1
- package/dist/{reveal-component-DH09ygHo.js → reveal-component-D0cfwbbl.js} +1094 -1094
- package/dist/{select-C33FVPPN.js → select-OxgozOC8.js} +1 -1
- package/dist/{sequenceDiagram-WJ2MYXX4-Dxysg8DX.js → sequenceDiagram-WJ2MYXX4-DFX-bE6O.js} +1 -1
- package/dist/{spec-DH1lmF7Q.js → spec-oJn0RhpQ.js} +2 -2
- package/dist/{stateDiagram-D77RDMKH-DTEN3-OJ.js → stateDiagram-D77RDMKH-BR7cI3wK.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-vktk3Xc5.js → stateDiagram-v2-MP3YSRHH-oGqB5UDI.js} +2 -2
- package/dist/{static-state-BldGd99T.js → static-state-CuV6Pc3a.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-Cjveh9OM.js → swimlanes-42K2YHIH-DwSZl1yC.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-Bdr7Ev_2.js → swimlanesDiagram-VR7AAH4N-BHlcd-Ri.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-KIuDcefn.js → timeline-definition-24CTP7MA-DMmP-G2O.js} +1 -1
- package/dist/{toast-CY1_Qv41.js → toast-DbBHmxmB.js} +2 -2
- package/dist/{useAsyncData-DsZMvNXn.js → useAsyncData-BYIAoMqn.js} +1 -1
- package/dist/{useDateFormatter-Dk_xFci4.js → useDateFormatter-ButWkdcK.js} +1 -1
- package/dist/{useDeepCompareMemoize-KMBBux8C.js → useDeepCompareMemoize-Bz0c5GN6.js} +1 -1
- package/dist/{useTheme-BZ2GGLgm.js → useTheme-DPLobSFs.js} +7 -7
- package/dist/{utils-DXkj82sK.js → utils-EBklsC5R.js} +31 -31
- package/dist/{vega-component-C-lqjV0q.js → vega-component-rxIxAAh9.js} +7 -7
- package/dist/{vennDiagram-4TSXK5OY-DrIOUHfb.js → vennDiagram-4TSXK5OY-Db8GtH1G.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-CJNjf4OH.js → wardleyDiagram-VM6X3IG4-DmIiC3FU.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-DEshFho-.js → xychartDiagram-S5SC5T6Z-DS9OPrNq.js} +1 -1
- package/package.json +1 -1
- package/src/__mocks__/executions.ts +195 -0
- package/src/__mocks__/notebook.ts +0 -1
- package/src/__tests__/CellStatus.test.tsx +19 -20
- package/src/components/chat/__tests__/chat-display.test.tsx +157 -86
- package/src/components/chat/__tests__/chat-utils.test.ts +42 -0
- package/src/components/chat/__tests__/turn-timing.test.ts +84 -0
- package/src/components/chat/__tests__/use-assistant-turn.test.ts +95 -0
- package/src/components/chat/__tests__/user-message.test.tsx +27 -0
- package/src/components/chat/acp/agent-panel.tsx +2 -0
- package/src/components/chat/agent-activity.tsx +132 -0
- package/src/components/chat/chat-display.tsx +125 -96
- package/src/components/chat/chat-panel.tsx +123 -106
- package/src/components/chat/chat-utils.ts +12 -24
- package/src/components/chat/tool-call/__tests__/tool-labels.test.ts +76 -0
- package/src/components/chat/tool-call/__tests__/tool-step.test.tsx +57 -0
- package/src/components/chat/tool-call/shared.ts +6 -0
- package/src/components/chat/tool-call/tool-call-view.tsx +22 -76
- package/src/components/chat/tool-call/tool-labels.ts +172 -0
- package/src/components/chat/tool-call/tool-step.tsx +145 -0
- package/src/components/chat/turn-timing.ts +73 -0
- package/src/components/chat/use-assistant-turn.ts +65 -0
- package/src/components/chat/use-chat-generation.ts +5 -4
- package/src/components/chat/user-message.tsx +145 -0
- package/src/components/dependency-graph/minimap-content.tsx +3 -2
- package/src/components/editor/__tests__/data-attributes.test.tsx +0 -7
- package/src/components/editor/__tests__/output-persistence.test.tsx +2 -2
- package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +19 -1
- package/src/components/editor/actions/useCellActionButton.tsx +10 -3
- package/src/components/editor/ai/completion-handlers.tsx +0 -37
- package/src/components/editor/alerts/stdin-blocking-alert.tsx +3 -1
- package/src/components/editor/cell/CellStatus.tsx +23 -19
- package/src/components/editor/cell/RunButton.tsx +16 -6
- package/src/components/editor/cell/StagedAICell.tsx +51 -144
- package/src/components/editor/cell/__tests__/useRunCells.test.ts +45 -0
- package/src/components/editor/cell/cell-context-menu.tsx +4 -1
- package/src/components/editor/cell/code/cell-editor.tsx +7 -5
- package/src/components/editor/cell/collapse.tsx +4 -0
- package/src/components/editor/cell/useRunCells.ts +18 -3
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +193 -32
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +2 -2
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +145 -81
- package/src/components/editor/chrome/wrapper/pending-ai-changes-badge.tsx +25 -0
- package/src/components/editor/chrome/wrapper/sidebar.tsx +8 -4
- package/src/components/editor/header/status.tsx +4 -2
- package/src/components/editor/links/cell-link.tsx +4 -4
- package/src/components/editor/navigation/navigation.ts +3 -2
- package/src/components/editor/notebook-cell.tsx +85 -94
- package/src/components/editor/renderers/grid-layout/__tests__/grid-layout.test.tsx +75 -0
- package/src/components/editor/renderers/grid-layout/grid-layout.tsx +22 -16
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -1
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +12 -33
- package/src/components/editor/stdin-blocking-alert.tsx +3 -1
- package/src/components/scratchpad/scratchpad.tsx +4 -1
- package/src/components/slides/minimap.tsx +4 -2
- package/src/components/slides/reveal-component.tsx +14 -10
- package/src/components/slides/slide-cell-view.tsx +28 -24
- package/src/components/slides/slide.tsx +3 -4
- package/src/components/ui/toast.tsx +1 -1
- package/src/core/ai/__tests__/staged-cells.test.ts +74 -126
- package/src/core/ai/staged-cells.ts +48 -38
- package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -389
- package/src/core/cells/__tests__/cell.test.ts +49 -154
- package/src/core/cells/__tests__/cells.test.ts +18 -127
- package/src/core/cells/__tests__/pending-delete-service.test.tsx +11 -20
- package/src/core/cells/__tests__/utils.test.ts +75 -247
- package/src/core/cells/actions.ts +5 -2
- package/src/core/cells/cell.ts +31 -133
- package/src/core/cells/cells.ts +14 -29
- package/src/core/cells/document.ts +3 -3
- package/src/core/cells/focus.ts +27 -6
- package/src/core/cells/layout.ts +1 -1
- package/src/core/cells/pending-delete-service.ts +5 -7
- package/src/core/cells/scrollCellIntoView.ts +6 -0
- package/src/core/cells/types.ts +13 -36
- package/src/core/cells/utils.ts +37 -68
- package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +59 -0
- package/src/core/codemirror/placeholder/extensions.ts +23 -0
- package/src/core/edit-app.tsx +2 -1
- package/src/core/executions/__tests__/atoms.test.ts +99 -0
- package/src/core/executions/__tests__/select.test.ts +268 -0
- package/src/core/executions/atoms.ts +206 -0
- package/src/core/executions/hooks.ts +31 -0
- package/src/core/executions/index.ts +31 -0
- package/src/core/executions/select.ts +175 -0
- package/src/core/export/__tests__/hooks.test.ts +0 -5
- package/src/core/islands/bootstrap.ts +4 -1
- package/src/core/islands/components/output-wrapper.tsx +5 -5
- package/src/core/kernel/handlers.ts +2 -11
- package/src/core/kernel/state.ts +0 -15
- package/src/core/lifecycle/render-policy.ts +12 -26
- package/src/core/network/__tests__/requests-lazy.test.ts +5 -5
- package/src/core/network/requests-lazy.ts +3 -3
- package/src/core/notebook/operations.ts +1 -1
- package/src/core/run-app.tsx +2 -1
- package/src/core/runtime/__tests__/adapter.test.ts +3 -3
- package/src/core/runtime/adapter.ts +4 -4
- package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +34 -0
- package/src/core/session/useNotebookSession.tsx +10 -5
- package/src/core/wasm/bridge.ts +1 -1
- package/src/css/app/Cell.css +7 -36
- package/src/plugins/core/registerReactComponent.tsx +4 -13
- package/src/plugins/impl/chat/chat-ui.tsx +2 -3
- package/src/plugins/impl/common/error-banner.tsx +5 -3
- package/src/stories/cell-status.stories.tsx +17 -10
- package/src/stories/cell.stories.tsx +0 -31
- package/src/stories/layout/vertical/one-column.stories.tsx +0 -9
- package/src/utils/time.ts +10 -0
- package/src/components/chat/reasoning-accordion.tsx +0 -61
- package/src/components/chat/tool-call/tool-activity-group.tsx +0 -49
- package/src/components/chat/tool-call/tool-error-card.tsx +0 -81
- package/src/components/chat/tool-call/tool-history-row.tsx +0 -147
- package/src/components/icons/loading-ellipsis.tsx +0 -46
|
@@ -57,30 +57,6 @@ export async function convertToFileUIPart(
|
|
|
57
57
|
return fileUIParts;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export function isLastMessageReasoning(messages: UIMessage[]): boolean {
|
|
61
|
-
if (messages.length === 0) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const lastMessage = messages.at(-1);
|
|
66
|
-
if (!lastMessage) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (lastMessage.role !== "assistant" || !lastMessage.parts) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const parts = lastMessage.parts;
|
|
75
|
-
if (parts.length === 0) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Check if the last part is reasoning
|
|
80
|
-
const lastPart = parts[parts.length - 1];
|
|
81
|
-
return lastPart.type === "reasoning";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
60
|
function stringifyTextParts(parts: UIMessage["parts"]): string {
|
|
85
61
|
return parts
|
|
86
62
|
.map((part) => (part.type === "text" ? part.text : ""))
|
|
@@ -198,6 +174,18 @@ export function hasPendingToolCalls(messages: UIMessage[]): boolean {
|
|
|
198
174
|
);
|
|
199
175
|
}
|
|
200
176
|
|
|
177
|
+
export function willAutoContinue(messages: UIMessage[]): boolean {
|
|
178
|
+
const lastMessage = messages.at(-1);
|
|
179
|
+
const lastPart =
|
|
180
|
+
lastMessage?.role === "assistant" ? lastMessage.parts.at(-1) : undefined;
|
|
181
|
+
return (
|
|
182
|
+
hasPendingToolCalls(messages) ||
|
|
183
|
+
(lastPart != null &&
|
|
184
|
+
isToolUIPart(lastPart) &&
|
|
185
|
+
lastPart.state === "input-available")
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
201
189
|
/**
|
|
202
190
|
* True when the assistant is still waiting on tool execution or user approval.
|
|
203
191
|
* Unlike `hasPendingToolCalls` (ready to auto-resume), these states must block
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { describeToolCall } from "../tool-labels";
|
|
5
|
+
|
|
6
|
+
describe("describeToolCall", () => {
|
|
7
|
+
it("names notebook edits by operation and shows the first line of code", () => {
|
|
8
|
+
const input = {
|
|
9
|
+
edit: {
|
|
10
|
+
type: "add_cell",
|
|
11
|
+
code: "\n def fib(n):\n return n",
|
|
12
|
+
position: {},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
expect(
|
|
16
|
+
describeToolCall("tool-edit_notebook_tool", input, "done"),
|
|
17
|
+
).toMatchObject({
|
|
18
|
+
label: "Added cell",
|
|
19
|
+
detail: "def fib(n):",
|
|
20
|
+
});
|
|
21
|
+
expect(
|
|
22
|
+
describeToolCall("tool-edit_notebook_tool", input, "running").label,
|
|
23
|
+
).toBe("Adding cell");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("falls back to a generic edit label while the input is still streaming", () => {
|
|
27
|
+
expect(
|
|
28
|
+
describeToolCall("tool-edit_notebook_tool", { edit: {} }, "running"),
|
|
29
|
+
).toEqual(expect.objectContaining({ label: "Editing notebook" }));
|
|
30
|
+
expect(
|
|
31
|
+
describeToolCall("tool-edit_notebook_tool", { edit: {} }, "running"),
|
|
32
|
+
).not.toHaveProperty("detail");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("ignores inherited object keys in tool names and edit types", () => {
|
|
36
|
+
expect(describeToolCall("tool-constructor", {}, "done")).toMatchObject({
|
|
37
|
+
label: "Called",
|
|
38
|
+
detail: "constructor",
|
|
39
|
+
});
|
|
40
|
+
expect(
|
|
41
|
+
describeToolCall(
|
|
42
|
+
"edit_notebook_tool",
|
|
43
|
+
{ edit: { type: "toString" } },
|
|
44
|
+
"done",
|
|
45
|
+
).label,
|
|
46
|
+
).toBe("Edited notebook");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("summarizes code-mode runs by their first meaningful line", () => {
|
|
50
|
+
const code =
|
|
51
|
+
"import marimo._code_mode as cm\n# add cells\ncm.add_cell('x = 1')";
|
|
52
|
+
expect(
|
|
53
|
+
describeToolCall("tool-execute_code", { code }, "done"),
|
|
54
|
+
).toMatchObject({
|
|
55
|
+
label: "Ran code",
|
|
56
|
+
detail: "cm.add_cell('x = 1')",
|
|
57
|
+
});
|
|
58
|
+
expect(
|
|
59
|
+
describeToolCall("execute_code", { code: "import numpy as np" }, "done")
|
|
60
|
+
.detail,
|
|
61
|
+
).toBe("import numpy as np");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("uses plain language for known tools", () => {
|
|
65
|
+
expect(describeToolCall("lint_notebook", {}, "done").label).toBe(
|
|
66
|
+
"Linted notebook",
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("shows the raw name for unknown tools such as MCP tools", () => {
|
|
71
|
+
expect(describeToolCall("tool-search_docs", {}, "running")).toMatchObject({
|
|
72
|
+
label: "Calling",
|
|
73
|
+
detail: "search_docs",
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { ToolStep } from "../tool-step";
|
|
6
|
+
|
|
7
|
+
const edit = { edit: { type: "add_cell", code: "x = 1", position: {} } };
|
|
8
|
+
|
|
9
|
+
describe("ToolStep", () => {
|
|
10
|
+
it("describes the call and reveals its input and output on demand", () => {
|
|
11
|
+
render(
|
|
12
|
+
<ToolStep
|
|
13
|
+
toolName="tool-edit_notebook_tool"
|
|
14
|
+
state="output-available"
|
|
15
|
+
input={edit}
|
|
16
|
+
result={{ status: "success" }}
|
|
17
|
+
/>,
|
|
18
|
+
);
|
|
19
|
+
const row = screen.getByRole("button", { name: /Added cell/ });
|
|
20
|
+
expect(row.getAttribute("aria-expanded")).toBe("false");
|
|
21
|
+
expect(screen.queryByText("Tool Request")).toBeNull();
|
|
22
|
+
|
|
23
|
+
fireEvent.click(row);
|
|
24
|
+
expect(row.getAttribute("aria-expanded")).toBe("true");
|
|
25
|
+
expect(screen.getByText("Tool Request")).toBeTruthy();
|
|
26
|
+
expect(screen.getByText("Tool Result")).toBeTruthy();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("shows the error text of a failed call", () => {
|
|
30
|
+
render(
|
|
31
|
+
<ToolStep
|
|
32
|
+
toolName="lint_notebook"
|
|
33
|
+
state="output-error"
|
|
34
|
+
input={{}}
|
|
35
|
+
errorText="Linter crashed"
|
|
36
|
+
/>,
|
|
37
|
+
);
|
|
38
|
+
fireEvent.click(
|
|
39
|
+
screen.getByRole("button", { name: /Linted notebook.*Failed/ }),
|
|
40
|
+
);
|
|
41
|
+
expect(screen.getByText("Linter crashed")).toBeTruthy();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("marks calls cut off by the end of the turn as stopped", () => {
|
|
45
|
+
render(
|
|
46
|
+
<ToolStep
|
|
47
|
+
toolName="tool-edit_notebook_tool"
|
|
48
|
+
state="input-available"
|
|
49
|
+
input={edit}
|
|
50
|
+
isActive={false}
|
|
51
|
+
/>,
|
|
52
|
+
);
|
|
53
|
+
expect(
|
|
54
|
+
screen.getByRole("button", { name: /Adding cell.*Stopped/ }),
|
|
55
|
+
).toBeTruthy();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -8,6 +8,12 @@ export type ToolState = ToolUIPart["state"];
|
|
|
8
8
|
// rather than exporting a named type, so we derive ours from there.
|
|
9
9
|
export type ToolApproval = NonNullable<ToolUIPart["approval"]>;
|
|
10
10
|
|
|
11
|
+
export const PENDING_TOOL_STATES: ReadonlySet<ToolState> = new Set([
|
|
12
|
+
"input-streaming",
|
|
13
|
+
"input-available",
|
|
14
|
+
"approval-responded",
|
|
15
|
+
]);
|
|
16
|
+
|
|
11
17
|
export function formatToolName(toolName: string): string {
|
|
12
18
|
return toolName.replace("tool-", "");
|
|
13
19
|
}
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ChatAddToolApproveResponseFunction } from "ai";
|
|
4
4
|
import React from "react";
|
|
5
|
-
import { logNever } from "@/utils/assertNever";
|
|
6
5
|
import type { ToolApproval, ToolState } from "./shared";
|
|
7
6
|
import { ToolApprovalCard } from "./tool-approval-card";
|
|
8
|
-
import {
|
|
9
|
-
import { ToolHistoryRow } from "./tool-history-row";
|
|
7
|
+
import { ToolStep } from "./tool-step";
|
|
10
8
|
|
|
11
9
|
interface ToolCallViewProps {
|
|
12
10
|
toolName: string;
|
|
@@ -16,88 +14,36 @@ interface ToolCallViewProps {
|
|
|
16
14
|
input?: unknown;
|
|
17
15
|
approval?: ToolApproval;
|
|
18
16
|
onApprove?: ChatAddToolApproveResponseFunction;
|
|
19
|
-
index?: number;
|
|
20
17
|
className?: string;
|
|
21
|
-
/**
|
|
22
|
-
* Whether this tool call belongs to the latest message in the conversation.
|
|
23
|
-
* Used by error cards to decide whether to stay expanded (live) or collapse
|
|
24
|
-
* (the user has moved on).
|
|
25
|
-
*/
|
|
26
|
-
isLive?: boolean;
|
|
27
18
|
isActive?: boolean;
|
|
28
19
|
}
|
|
29
20
|
|
|
30
21
|
export const ToolCallView: React.FC<ToolCallViewProps> = ({
|
|
31
22
|
toolName,
|
|
32
23
|
state,
|
|
33
|
-
result,
|
|
34
|
-
errorText,
|
|
35
|
-
input,
|
|
36
24
|
approval,
|
|
37
25
|
onApprove,
|
|
38
|
-
|
|
39
|
-
className,
|
|
40
|
-
isLive = true,
|
|
41
|
-
isActive = true,
|
|
26
|
+
...props
|
|
42
27
|
}) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
className={className}
|
|
56
|
-
/>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
return (
|
|
60
|
-
<ToolHistoryRow
|
|
61
|
-
toolName={toolName}
|
|
62
|
-
state="input-available"
|
|
63
|
-
input={input}
|
|
64
|
-
index={index}
|
|
65
|
-
className={className}
|
|
66
|
-
isActive={isActive}
|
|
67
|
-
/>
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
case "output-error":
|
|
71
|
-
return (
|
|
72
|
-
<ToolErrorCard
|
|
73
|
-
toolName={toolName}
|
|
74
|
-
input={input}
|
|
75
|
-
errorText={errorText}
|
|
76
|
-
isLive={isLive}
|
|
77
|
-
className={className}
|
|
78
|
-
/>
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
case "input-streaming":
|
|
82
|
-
case "input-available":
|
|
83
|
-
case "approval-responded":
|
|
84
|
-
case "output-available":
|
|
85
|
-
case "output-denied":
|
|
86
|
-
return (
|
|
87
|
-
<ToolHistoryRow
|
|
88
|
-
toolName={toolName}
|
|
89
|
-
state={state}
|
|
90
|
-
input={input}
|
|
91
|
-
result={result}
|
|
92
|
-
approval={approval}
|
|
93
|
-
index={index}
|
|
94
|
-
className={className}
|
|
95
|
-
isActive={isActive}
|
|
96
|
-
/>
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
default:
|
|
100
|
-
logNever(state);
|
|
101
|
-
return null;
|
|
28
|
+
if (state === "approval-requested") {
|
|
29
|
+
return approval != null && onApprove != null ? (
|
|
30
|
+
<ToolApprovalCard
|
|
31
|
+
toolName={toolName}
|
|
32
|
+
input={props.input}
|
|
33
|
+
approval={approval}
|
|
34
|
+
onApprove={onApprove}
|
|
35
|
+
className={props.className}
|
|
36
|
+
/>
|
|
37
|
+
) : (
|
|
38
|
+
<ToolStep {...props} toolName={toolName} state="input-available" />
|
|
39
|
+
);
|
|
102
40
|
}
|
|
41
|
+
return (
|
|
42
|
+
<ToolStep
|
|
43
|
+
{...props}
|
|
44
|
+
toolName={toolName}
|
|
45
|
+
state={state}
|
|
46
|
+
approval={approval}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
103
49
|
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BookOpenIcon,
|
|
5
|
+
BugIcon,
|
|
6
|
+
DatabaseIcon,
|
|
7
|
+
FileSearchIcon,
|
|
8
|
+
ListChecksIcon,
|
|
9
|
+
type LucideIcon,
|
|
10
|
+
NetworkIcon,
|
|
11
|
+
NotebookIcon,
|
|
12
|
+
PencilIcon,
|
|
13
|
+
PlusIcon,
|
|
14
|
+
ScanSearchIcon,
|
|
15
|
+
SquareTerminalIcon,
|
|
16
|
+
Trash2Icon,
|
|
17
|
+
VariableIcon,
|
|
18
|
+
WrenchIcon,
|
|
19
|
+
} from "lucide-react";
|
|
20
|
+
import { formatToolName } from "./shared";
|
|
21
|
+
|
|
22
|
+
interface ToolDescription {
|
|
23
|
+
label: string;
|
|
24
|
+
detail?: string;
|
|
25
|
+
icon: LucideIcon;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type Phase = "running" | "done";
|
|
29
|
+
|
|
30
|
+
const KNOWN_TOOLS: Record<
|
|
31
|
+
string,
|
|
32
|
+
{ running: string; done: string; icon: LucideIcon }
|
|
33
|
+
> = {
|
|
34
|
+
get_marimo_rules: {
|
|
35
|
+
running: "Reading marimo guidelines",
|
|
36
|
+
done: "Read marimo guidelines",
|
|
37
|
+
icon: BookOpenIcon,
|
|
38
|
+
},
|
|
39
|
+
get_active_notebooks: {
|
|
40
|
+
running: "Listing notebooks",
|
|
41
|
+
done: "Listed notebooks",
|
|
42
|
+
icon: NotebookIcon,
|
|
43
|
+
},
|
|
44
|
+
get_lightweight_cell_map: {
|
|
45
|
+
running: "Reading notebook",
|
|
46
|
+
done: "Read notebook",
|
|
47
|
+
icon: FileSearchIcon,
|
|
48
|
+
},
|
|
49
|
+
get_cell_runtime_data: {
|
|
50
|
+
running: "Inspecting cells",
|
|
51
|
+
done: "Inspected cells",
|
|
52
|
+
icon: ScanSearchIcon,
|
|
53
|
+
},
|
|
54
|
+
get_cell_outputs: {
|
|
55
|
+
running: "Reading cell outputs",
|
|
56
|
+
done: "Read cell outputs",
|
|
57
|
+
icon: ScanSearchIcon,
|
|
58
|
+
},
|
|
59
|
+
get_tables_and_variables: {
|
|
60
|
+
running: "Reading variables",
|
|
61
|
+
done: "Read variables",
|
|
62
|
+
icon: VariableIcon,
|
|
63
|
+
},
|
|
64
|
+
get_database_tables: {
|
|
65
|
+
running: "Reading database tables",
|
|
66
|
+
done: "Read database tables",
|
|
67
|
+
icon: DatabaseIcon,
|
|
68
|
+
},
|
|
69
|
+
get_notebook_errors: {
|
|
70
|
+
running: "Checking for errors",
|
|
71
|
+
done: "Checked for errors",
|
|
72
|
+
icon: BugIcon,
|
|
73
|
+
},
|
|
74
|
+
lint_notebook: {
|
|
75
|
+
running: "Linting notebook",
|
|
76
|
+
done: "Linted notebook",
|
|
77
|
+
icon: ListChecksIcon,
|
|
78
|
+
},
|
|
79
|
+
get_cell_dependency_graph: {
|
|
80
|
+
running: "Reading dependency graph",
|
|
81
|
+
done: "Read dependency graph",
|
|
82
|
+
icon: NetworkIcon,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const EDIT_LABELS = {
|
|
87
|
+
add_cell: { running: "Adding cell", done: "Added cell", icon: PlusIcon },
|
|
88
|
+
update_cell: {
|
|
89
|
+
running: "Editing cell",
|
|
90
|
+
done: "Edited cell",
|
|
91
|
+
icon: PencilIcon,
|
|
92
|
+
},
|
|
93
|
+
delete_cell: {
|
|
94
|
+
running: "Deleting cell",
|
|
95
|
+
done: "Deleted cell",
|
|
96
|
+
icon: Trash2Icon,
|
|
97
|
+
},
|
|
98
|
+
} as const;
|
|
99
|
+
|
|
100
|
+
export function describeToolCall(
|
|
101
|
+
toolName: string,
|
|
102
|
+
input: unknown,
|
|
103
|
+
phase: Phase,
|
|
104
|
+
): ToolDescription {
|
|
105
|
+
const name = formatToolName(toolName);
|
|
106
|
+
|
|
107
|
+
if (name === "execute_code") {
|
|
108
|
+
return {
|
|
109
|
+
label: phase === "running" ? "Running code" : "Ran code",
|
|
110
|
+
detail: isRecord(input) ? summarizeCode(input.code) : undefined,
|
|
111
|
+
icon: SquareTerminalIcon,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (name === "edit_notebook_tool") {
|
|
116
|
+
const edit = getEdit(input);
|
|
117
|
+
if (edit) {
|
|
118
|
+
const labels = EDIT_LABELS[edit.type];
|
|
119
|
+
return {
|
|
120
|
+
label: labels[phase],
|
|
121
|
+
detail: summarizeCode(edit.code),
|
|
122
|
+
icon: labels.icon,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
label: phase === "running" ? "Editing notebook" : "Edited notebook",
|
|
127
|
+
icon: PencilIcon,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (Object.hasOwn(KNOWN_TOOLS, name)) {
|
|
132
|
+
const known = KNOWN_TOOLS[name];
|
|
133
|
+
return { label: known[phase], icon: known.icon };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
label: phase === "running" ? "Calling" : "Called",
|
|
138
|
+
detail: name,
|
|
139
|
+
icon: WrenchIcon,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getEdit(
|
|
144
|
+
input: unknown,
|
|
145
|
+
): { type: keyof typeof EDIT_LABELS; code?: string } | undefined {
|
|
146
|
+
if (!isRecord(input) || !isRecord(input.edit)) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const { type, code } = input.edit;
|
|
150
|
+
if (typeof type !== "string" || !Object.hasOwn(EDIT_LABELS, type)) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
type: type as keyof typeof EDIT_LABELS,
|
|
155
|
+
code: typeof code === "string" ? code : undefined,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function summarizeCode(code: unknown): string | undefined {
|
|
160
|
+
if (typeof code !== "string") {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
const lines = code
|
|
164
|
+
.split("\n")
|
|
165
|
+
.map((line) => line.trim())
|
|
166
|
+
.filter((line) => line.length > 0);
|
|
167
|
+
return lines.find((line) => !/^(import\s|from\s|#)/.test(line)) ?? lines[0];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
171
|
+
return typeof value === "object" && value !== null;
|
|
172
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BanIcon,
|
|
5
|
+
ChevronRightIcon,
|
|
6
|
+
CircleSlashIcon,
|
|
7
|
+
Loader2Icon,
|
|
8
|
+
type LucideIcon,
|
|
9
|
+
XCircleIcon,
|
|
10
|
+
} from "lucide-react";
|
|
11
|
+
import React from "react";
|
|
12
|
+
import { cn } from "@/utils/cn";
|
|
13
|
+
import {
|
|
14
|
+
PENDING_TOOL_STATES,
|
|
15
|
+
type ToolApproval,
|
|
16
|
+
type ToolState,
|
|
17
|
+
} from "./shared";
|
|
18
|
+
import { ToolArgsRenderer } from "./tool-args";
|
|
19
|
+
import { describeToolCall } from "./tool-labels";
|
|
20
|
+
import { ResultRenderer } from "./tool-result";
|
|
21
|
+
|
|
22
|
+
type ToolStepState = Exclude<ToolState, "approval-requested">;
|
|
23
|
+
|
|
24
|
+
interface ToolStepProps {
|
|
25
|
+
toolName: string;
|
|
26
|
+
state: ToolStepState;
|
|
27
|
+
input?: unknown;
|
|
28
|
+
result?: unknown;
|
|
29
|
+
errorText?: string;
|
|
30
|
+
approval?: ToolApproval;
|
|
31
|
+
isActive?: boolean;
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const ToolStep: React.FC<ToolStepProps> = ({
|
|
36
|
+
toolName,
|
|
37
|
+
state,
|
|
38
|
+
input,
|
|
39
|
+
result,
|
|
40
|
+
errorText,
|
|
41
|
+
approval,
|
|
42
|
+
isActive = true,
|
|
43
|
+
className,
|
|
44
|
+
}) => {
|
|
45
|
+
const [open, setOpen] = React.useState(false);
|
|
46
|
+
const pending = PENDING_TOOL_STATES.has(state);
|
|
47
|
+
const { label, detail, icon } = describeToolCall(
|
|
48
|
+
toolName,
|
|
49
|
+
input,
|
|
50
|
+
pending ? "running" : "done",
|
|
51
|
+
);
|
|
52
|
+
const status = getStatus(state, pending && !isActive, icon);
|
|
53
|
+
const failed = state === "output-error";
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className={cn("w-full", className)}>
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
onClick={() => setOpen((prev) => !prev)}
|
|
60
|
+
aria-expanded={open}
|
|
61
|
+
className={cn(
|
|
62
|
+
"group/step -mx-1.5 flex w-fit max-w-full min-w-0 items-center gap-1.5 rounded-md px-1.5 py-0.5 text-left text-xs leading-4 text-muted-foreground transition-colors hover:bg-foreground/6 hover:text-foreground focus-visible:outline-1 focus-visible:outline-(--slate-8)",
|
|
63
|
+
open && "text-foreground",
|
|
64
|
+
failed && "text-(--red-11) hover:bg-(--red-3) hover:text-(--red-11)",
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
<status.icon
|
|
68
|
+
aria-hidden={true}
|
|
69
|
+
className={cn(
|
|
70
|
+
// Offsets lucide's built-in padding.
|
|
71
|
+
"-ml-px size-3 shrink-0",
|
|
72
|
+
status.spinning && "animate-spin",
|
|
73
|
+
)}
|
|
74
|
+
/>
|
|
75
|
+
<span className="shrink-0">{label}</span>
|
|
76
|
+
{detail && (
|
|
77
|
+
<code className="min-w-0 truncate font-mono text-[0.6875rem] opacity-80">
|
|
78
|
+
{detail}
|
|
79
|
+
</code>
|
|
80
|
+
)}
|
|
81
|
+
{status.suffix && (
|
|
82
|
+
<span className="shrink-0 opacity-80">· {status.suffix}</span>
|
|
83
|
+
)}
|
|
84
|
+
<ChevronRightIcon
|
|
85
|
+
aria-hidden={true}
|
|
86
|
+
className={cn(
|
|
87
|
+
"size-3 shrink-0 opacity-40 transition-transform group-hover/step:opacity-100",
|
|
88
|
+
open && "rotate-90 opacity-100",
|
|
89
|
+
)}
|
|
90
|
+
/>
|
|
91
|
+
</button>
|
|
92
|
+
{open && (
|
|
93
|
+
<ToolStepDetails
|
|
94
|
+
state={state}
|
|
95
|
+
input={input}
|
|
96
|
+
result={result}
|
|
97
|
+
errorText={errorText}
|
|
98
|
+
approval={approval}
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
function getStatus(
|
|
106
|
+
state: ToolStepState,
|
|
107
|
+
interrupted: boolean,
|
|
108
|
+
doneIcon: LucideIcon,
|
|
109
|
+
): { icon: LucideIcon; suffix?: string; spinning?: boolean } {
|
|
110
|
+
if (interrupted) {
|
|
111
|
+
return { icon: CircleSlashIcon, suffix: "Stopped" };
|
|
112
|
+
}
|
|
113
|
+
switch (state) {
|
|
114
|
+
case "output-error":
|
|
115
|
+
return { icon: XCircleIcon, suffix: "Failed" };
|
|
116
|
+
case "output-denied":
|
|
117
|
+
return { icon: BanIcon, suffix: "Denied" };
|
|
118
|
+
case "output-available":
|
|
119
|
+
return { icon: doneIcon };
|
|
120
|
+
default:
|
|
121
|
+
return { icon: Loader2Icon, spinning: true };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const ToolStepDetails: React.FC<
|
|
126
|
+
Pick<ToolStepProps, "state" | "input" | "result" | "errorText" | "approval">
|
|
127
|
+
> = ({ state, input, result, errorText, approval }) => (
|
|
128
|
+
<div className="mt-1 mb-2 ml-5.5 space-y-3">
|
|
129
|
+
<ToolArgsRenderer input={input} />
|
|
130
|
+
{state === "output-available" && result != null && (
|
|
131
|
+
<ResultRenderer result={result} />
|
|
132
|
+
)}
|
|
133
|
+
{state === "output-error" && errorText && (
|
|
134
|
+
<pre className="max-h-64 overflow-auto rounded border border-(--red-6) bg-(--red-2) p-2 text-xs leading-relaxed whitespace-pre-wrap text-(--red-11) scrollbar-thin">
|
|
135
|
+
{errorText}
|
|
136
|
+
</pre>
|
|
137
|
+
)}
|
|
138
|
+
{state === "output-denied" && (
|
|
139
|
+
<p className="text-xs text-muted-foreground">
|
|
140
|
+
Tool execution was denied
|
|
141
|
+
{approval?.reason ? `: ${approval.reason}` : "."}
|
|
142
|
+
</p>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
);
|