Skip to content

add tool approval flow for chat-panel#9507

Open
Light2Dark wants to merge 5 commits into
mainfrom
sham/add-tool-approval-ui
Open

add tool approval flow for chat-panel#9507
Light2Dark wants to merge 5 commits into
mainfrom
sham/add-tool-approval-ui

Conversation

@Light2Dark
Copy link
Copy Markdown
Collaborator

@Light2Dark Light2Dark commented May 11, 2026

📝 Summary

  • Paves the way for more complex tools in AI panel.
  • Currently no tools need approval, so I tested this manually with a custom tool. Effectively, this PR doesn't change existing behaviour too much (apart from UI fixes)
  • Cleans up some UI stuff (empty reasoning chunks)
  • Backend work to sanitize the messages from tools. There are some pydantic errors raised after approving / denying tools.

The AI SDK's addToolApprovalResponse transitions tool parts via object spread, which carries over fields the AI SDK's own
Zod schema marks z.never().optional() for the new state. pydantic-ai's extra='forbid' mirrors that intent and rightly rejects them. So, we sanitize these values on the backend by reflecting the Pydantic schema, which gives us future compatability.

Tested a few tools to make sure no regression

Screenshot 2026-05-11 at 12 58 00 PM Screenshot 2026-05-11 at 1 11 03 PM Screenshot 2026-05-11 at 12 59 27 PM

📋 Pre-Review Checklist

  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
  • Video or media evidence is provided for any visual changes (optional).

✅ Merge Checklist

  • I have read the contributor guidelines.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Tests have been added for the changes made.

Copilot AI review requested due to automatic review settings May 11, 2026 20:56
@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment May 11, 2026 9:30pm

Request Review

@Light2Dark Light2Dark marked this pull request as draft May 11, 2026 20:56
@Light2Dark Light2Dark added the enhancement New feature or request label May 11, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Bundle Report

Changes will increase total bundle size by 16.22kB (0.06%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
marimo-esm 25.15MB 16.22kB (0.06%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: marimo-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/terminal-*.js 105 bytes 458.83kB 0.02%
assets/index-*.css -231 bytes 365.79kB -0.06%
assets/JsonOutput-*.js 41 bytes 360.21kB 0.01%
assets/dist-*.js -32 bytes 137 bytes -18.93%
assets/dist-*.js 234 bytes 403 bytes 138.46% ⚠️
assets/dist-*.js 20 bytes 276 bytes 7.81% ⚠️
assets/dist-*.js 92 bytes 256 bytes 56.1% ⚠️
assets/dist-*.js -39 bytes 137 bytes -22.16%
assets/dist-*.js 74 bytes 176 bytes 72.55% ⚠️
assets/dist-*.js 6 bytes 183 bytes 3.39%
assets/dist-*.js -234 bytes 169 bytes -58.06%
assets/dist-*.js 32 bytes 169 bytes 23.36% ⚠️
assets/dist-*.js 204 bytes 387 bytes 111.48% ⚠️
assets/dist-*.js -35 bytes 102 bytes -25.55%
assets/dist-*.js 17 bytes 177 bytes 10.62% ⚠️
assets/dist-*.js 56 bytes 160 bytes 53.85% ⚠️
assets/dist-*.js -223 bytes 164 bytes -57.62%
assets/dist-*.js -172 bytes 104 bytes -62.32%
assets/edit-*.js 9 bytes 324.78kB 0.0%
assets/add-*.js 9.34kB 202.09kB 4.84%
assets/file-*.js 9 bytes 46.82kB 0.02%
assets/chat-*.js 5 bytes 14.66kB 0.03%
assets/chat-*.js 6.53kB 15.22kB 75.18% ⚠️
assets/chat-*.js 240 bytes 32.63kB 0.74%
assets/useNotebookActions-*.js 9 bytes 27.33kB 0.03%
assets/react-*.browser.esm-BdtIs0E-.js (New) 25.64kB 25.64kB 100.0% 🚀
assets/vega-*.browser-C8wT63Va.js (New) 24.8kB 24.8kB 100.0% 🚀
assets/home-*.js 41 bytes 24.03kB 0.17%
assets/CellStatus-*.js -133 bytes 11.22kB -1.17%
assets/react-*.esm-BNzu6e7h.js (New) 8.37kB 8.37kB 100.0% 🚀
assets/config-*.js 24 bytes 6.09kB 0.4%
assets/emotion-*.esm-C59xfSYt.js (New) 4.37kB 4.37kB 100.0% 🚀
assets/mermaid-*.core-B73Gp-Wo.js (New) 2.38kB 2.38kB 100.0% 🚀
assets/maps-*.js -198 bytes 595 bytes -24.97%
assets/external-*.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/ban-*.js (New) 181 bytes 181 bytes 100.0% 🚀
assets/__vite-*.js 5 bytes 98 bytes 5.38% ⚠️
assets/__vite-*.js -5 bytes 93 bytes -5.1%
assets/react-*.browser.esm-BUNcfKXO.js (Deleted) -25.64kB 0 bytes -100.0% 🗑️
assets/vega-*.browser-BegSZk0G.js (Deleted) -24.8kB 0 bytes -100.0% 🗑️
assets/react-*.esm-D9xfKaPZ.js (Deleted) -8.37kB 0 bytes -100.0% 🗑️
assets/emotion-*.esm-DD4AwVTU.js (Deleted) -4.37kB 0 bytes -100.0% 🗑️
assets/mermaid-*.core-BQULBKwL.js (Deleted) -2.38kB 0 bytes -100.0% 🗑️

Files in assets/add-*.js:

  • ./src/components/chat/chat-components.tsx → Total Size: 8.94kB

  • ./src/core/ai/tools/registry.ts → Total Size: 3.1kB

  • ./src/components/chat/chat-utils.ts → Total Size: 5.4kB

Files in assets/chat-*.js:

  • ./src/components/chat/tool-call/tool-call-view.tsx → Total Size: 2.5kB

  • ./src/components/chat/reasoning-accordion.tsx → Total Size: 2.63kB

  • ./src/components/chat/tool-call/tool-approval-card.tsx → Total Size: 3.06kB

  • ./src/components/chat/tool-call/tool-history-row.tsx → Total Size: 6.45kB

  • ./src/components/chat/tool-call/tool-error-card.tsx → Total Size: 3.79kB

  • ./src/components/chat/tool-call/tool-result.tsx → Total Size: 4.31kB

  • ./src/components/chat/tool-call/tool-args.tsx → Total Size: 1.2kB

  • ./src/components/chat/tool-call/shared.ts → Total Size: 140 bytes

  • ./src/components/chat/chat-display.tsx → Total Size: 2.67kB

Files in assets/chat-*.js:

  • ./src/components/chat/chat-panel.tsx → Total Size: 28.94kB

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an approval UX for tool invocations in the chat panel and hardens backend message parsing against AI SDK tool-part state transitions that can leak stale fields (causing strict pydantic-ai validation failures).

Changes:

  • Introduces a tool-call UI that supports approval-requested, denied, error, and history states (replacing the prior single accordion).
  • Sanitizes tool parts server-side by stripping fields not allowed by the pydantic-ai model for the part’s current state; adds regression tests.
  • Updates AI SDK dependencies and adjusts frontend tool registry invocation API accordingly.

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/_ai/test_pydantic_utils.py Adds unit/regression tests for backend tool-part sanitization.
marimo/_ai/_pydantic_ai_utils.py Implements _sanitize_part and schema-reflection to drop stale tool-part fields before pydantic-ai validation.
frontend/package.json Bumps ai / @ai-sdk/react versions used by the chat UI.
pnpm-lock.yaml Lockfile updates for the AI SDK upgrade and transitive deps.
frontend/src/core/ai/tools/registry.ts Changes invoke to accept a single named-args object.
frontend/src/core/ai/tools/tests/registry.test.ts Updates tests for the new invoke call shape.
frontend/src/components/chat/chat-panel.tsx Wires addToolApprovalResponse through useChat, adds outgoing schema validation canary logging, removes dynamic-tool early-return.
frontend/src/components/chat/chat-display.tsx Replaces ToolCallAccordion with ToolCallView, adds rendering for source-* parts and tool approval props wiring.
frontend/src/components/chat/chat-utils.ts Updates tool invocation to use new registry API; updates auto-send logic to include approval-responded/output-* states.
frontend/src/components/chat/reasoning-accordion.tsx Skips empty non-streaming reasoning parts and improves open/close behavior.
frontend/src/components/chat/chat-components.tsx Adds reusable SourceChip for source-url/source-document parts.
frontend/src/components/chat/tool-call/tool-call-view.tsx New state-based wrapper choosing approval/error/history presentations.
frontend/src/components/chat/tool-call/tool-approval-card.tsx New approval UI for tool calls requiring user approval.
frontend/src/components/chat/tool-call/tool-error-card.tsx New collapsible error presentation for failed tool runs.
frontend/src/components/chat/tool-call/tool-history-row.tsx New accordion row for non-blocking tool-call states/history.
frontend/src/components/chat/tool-call/tool-args.tsx New renderer for tool input arguments.
frontend/src/components/chat/tool-call/tool-result.tsx New renderer for tool outputs, with “pretty” success formatting when schema matches.
frontend/src/components/chat/tool-call/shared.ts Shared types/helpers for tool-call UI.
frontend/src/components/chat/tool-call-accordion.tsx Removes the old monolithic tool-call accordion component.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread frontend/src/components/chat/chat-utils.ts Outdated
Comment thread frontend/src/components/chat/tool-call/tool-args.tsx Outdated
Comment thread frontend/src/components/chat/tool-call/tool-result.tsx
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 19 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="frontend/src/components/chat/tool-call/tool-result.tsx">

<violation number="1" location="frontend/src/components/chat/tool-call/tool-result.tsx:59">
P2: `isEmpty(value)` incorrectly filters out valid primitive values like `false` and `0`, causing tool result fields to disappear.</violation>
</file>

<file name="frontend/src/components/chat/chat-display.tsx">

<violation number="1" location="frontend/src/components/chat/chat-display.tsx:123">
P2: Validate `source-url` before passing it to `href`; rendering untrusted schemes directly can create unsafe clickable links (e.g. `javascript:`).</violation>
</file>

<file name="frontend/src/components/chat/chat-components.tsx">

<violation number="1" location="frontend/src/components/chat/chat-components.tsx:208">
P1: Validate `href` before rendering the anchor; currently any scheme is allowed, which can enable `javascript:` URL injection when source URLs are untrusted.</violation>
</file>

<file name="frontend/src/components/chat/tool-call/tool-args.tsx">

<violation number="1" location="frontend/src/components/chat/tool-call/tool-args.tsx:19">
P2: `lodash.isEmpty` returns `true` for all primitives (including `0`, `false`), so a primitive `input` value will render as `"{}"` instead of its actual value. Guard the emptiness check so it only applies to objects/arrays/strings.</violation>

<violation number="2" location="frontend/src/components/chat/tool-call/tool-args.tsx:33">
P2: Render array tool arguments with `JSON.stringify` instead of `String(input)` to avoid lossy output like `[object Object]`.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client as Browser (Chat Panel)
    participant ChatHook as useChat (AI SDK)
    participant ToolView as ToolCallView
    participant ToolRegistry as FrontendToolRegistry
    participant Backend as Backend API (/api/ai/chat)
    participant Sanitizer as _sanitize_part (Python)
    participant PydanticAI as pydantic-ai Vercel Adapter

    Note over Client,PydanticAI: Tool Approval Flow

    Client->>ChatHook: useChat()
    ChatHook->>Client: addToolApprovalResponse, addToolOutput
    
    Note over Client,ToolView: Tool Call Execution

    ChatHook->>Client: onToolCall (toolCall event)
    Client->>ToolRegistry: invoke({toolName, rawArgs, toolContext})
    ToolRegistry->>ToolRegistry: Validate args against schema
    alt Valid args
        ToolRegistry->>Client: InvokeResult (tool output)
        Client->>ChatHook: addToolOutput({tool, toolCallId, result})
    else Invalid args
        ToolRegistry->>Client: InvokeResult with error
        Client->>ChatHook: addToolOutput({tool, toolCallId, error})
    end

    Note over Client,ToolView: Approval Required Path

    ChatHook->>Client: UIMessage with approval state
    Client->>ToolView: render (state="approval-requested", approval)
    ToolView->>Client: ToolApprovalCard (ShieldQuestionIcon, approve/deny buttons)
    
    alt User Approves
        Client->>ChatHook: addToolApprovalResponse({id, approved: true})
        ChatHook->>Client: Tool state → "approval-responded"
        Client->>Client: hasPendingToolCalls() → true (ready to send)
        Client->>Backend: POST /api/ai/chat (with approval-responded parts)
    else User Denies
        Client->>ChatHook: addToolApprovalResponse({id, approved: false})
        ChatHook->>Client: Tool state → "output-denied"
        Client->>Client: hasPendingToolCalls() → true (ready to send)
        Client->>Backend: POST /api/ai/chat (with output-denied + denial info)
    end

    Note over Client,Backend: Request Prep

    Client->>ChatHook: prepareSendMessagesRequest
    ChatHook->>ChatHook: NEW: safeValidateUIMessages (canary validation)
    alt Validation fails
        ChatHook->>ChatHook: Logger.debug (warning only, no block)
    end
    ChatHook->>Client: completionBody

    Note over Backend,PydanticAI: Backend Sanitization

    Client->>Backend: POST /api/ai/chat (messages with tool parts)
    Backend->>Sanitizer: _sanitize_part(part) for each part
    alt Tool part with state transition
        Sanitizer->>Sanitizer: Look up allowed fields from pydantic-ai schema
        Sanitizer->>Sanitizer: Drop stale fields (output, errorText from prior state)
        Sanitizer->>Backend: Cleaned part dict
    else Non-tool part
        Sanitizer->>Backend: Pass through unchanged
    end

    Backend->>PydanticAI: convert_to_pydantic_messages(sanitized messages)
    PydanticAI->>PydanticAI: Validate against strict schema (extra='forbid')
    alt Valid
        PydanticAI->>Backend: Parsed UIMessage objects
        Backend->>Client: AI response
    else Invalid
        PydanticAI->>Backend: ValidationError (blocked by sanitizer)
        Note over Backend: Would have failed without sanitization
    end

    Note over Client,ToolView: Tool State Display

    alt Tool completes successfully
        ChatHook->>Client: Tool state → "output-available"
        Client->>ToolView: render (state="output-available")
        ToolView->>Client: ToolHistoryRow (accordion, green checkmark)
        Client->>Client: hasPendingToolCalls() → true
    else Tool errors
        ChatHook->>Client: Tool state → "output-error"
        Client->>ToolView: render (state="output-error", isLive=true)
        ToolView->>Client: ToolErrorCard (red, auto-expanded while live)
        Note over ToolView: Auto-collapses when message is no longer last
    else Tool denied
        ChatHook->>Client: Tool state → "output-denied"
        Client->>ToolView: render (state="output-denied")
        ToolView->>Client: ToolHistoryRow (accordion, BanIcon, denial reason)
    end

    Note over Client,PydanticAI: Auto-send Logic

    Client->>Client: hasPendingToolCalls() iterates last message parts
    alt All tools ready (output-available, output-error, output-denied, approval-responded)
        Client->>Client: Auto-trigger next send (no manual submit needed)
    else Still pending (input-streaming, input-available, approval-requested)
        Client->>Client: Wait for completion or user action
    end
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread frontend/src/components/chat/chat-components.tsx Outdated
Comment thread frontend/src/components/chat/tool-call/tool-result.tsx Outdated
Comment thread frontend/src/components/chat/chat-display.tsx
Comment thread frontend/src/components/chat/tool-call/tool-args.tsx Outdated
Comment thread frontend/src/components/chat/tool-call/tool-args.tsx Outdated
@Light2Dark Light2Dark marked this pull request as ready for review May 11, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants