Skip to content

feat(ui): chat holds a tool call that changes something until the reader allows it - #378

Merged
Michael McQuade (giraffesyo) merged 4 commits into
canaryfrom
feat/ui-chat-tool-approvals
Oct 20, 2026
Merged

Michael McQuade (giraffesyo) merged 4 commits into
canaryfrom
feat/ui-chat-tool-approvals

Conversation

@giraffesyo

@giraffesyo Michael McQuade (giraffesyo) commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Summary

Lets a chat gateway offer tools that change something (stop a cluster, cancel a run) without running them on the model's say-so. The gateway holds such a call until the reader allows it.

The flow

  1. The gateway streams the call, then a new toolApproval chunk (toolCallId, optional expiresAt) in place of a result, and ends the reply.
  2. Chat shows the call as waiting and puts the existing confirm card under it: Allow, Deny, or Deny with feedback. Past expiresAt the card says the request expired.
  3. The answer goes back through useChat().answerToolApproval as a request with no new user turn. StreamRequest.resume (messageId, toolCallId, allowed, optional feedback) is sent as resume in the body.
  4. The gateway's toolResult for the call lands on the reply that paused, and the model's answer streams as a new reply under it. History replays as usual, because the paused reply now holds a finished call.
  5. The answered call keeps who answered and when, as the same decision line agent sessions show ("Allowed by Alice Example · 3 seconds ago"). Answering records a tool_decision on the call, and the answered card gives way to that line.

A held call waiting for the reader

Allowing the call, then the answer:

answer-flow.mp4
After Allow, desktop After Allow, phone
After Allow: the call's result, who allowed it, then the answer After Allow at phone width

Shared conversations. A tool runs with the access of whoever sent the message, so:

  • Only that person may answer a held call. Everyone else sees whom it waits on.
  • A turn someone else started says so: "Ran 1 tool call as Bob".
  • The composer notes that tool results are visible to the people the conversation is shared with. It shows beside the share button for the owner of a shared conversation, and always for a collaborator.

The same call in a conversation Bob started, at phone width

A held call at phone width

Also

  • ChatToolOption.asksFirst marks such a tool in the + menu and the tools modal ("Asks first").
  • ToolCallStatus gains 'waiting', with a tool_wait part delta; ApprovalPart gains expiresAt and waitingOn.
  • A resolved approval whose call records a decision no longer renders, in any host: the decision line says the same thing.
  • The expired card's string is approvalRequestExpired, because approvalExpired is now the decision line's label.
  • sendMessage's streaming now lives in one helper that resuming shares, and the history mapping is one function.
  • A reply with nothing finished in it yet is left out of the replayed history instead of being sent as an empty assistant message.
  • Stories: Chat/Thread › Held Tool Call (Allow and Deny work) and Held Tool Call Someone Else Asked.

Testing

  • make check passes (566 tests in src/ai, including new ones for the toolApproval chunk, the resume body, tool_wait, resuming onto the paused reply, deny feedback, expiry, who may answer, the "as Bob" label, and the decision recorded on answering).
  • Checked both stories in Storybook at 1000px and 390px.

Checklist

  • make check passes
  • A new or changed shared message is in every language in problem/messages/ (none changed)
  • No code, rule or param was renamed (they are API contract)

…pprovals

# Conflicts:
#	packages/ui/src/ai/components/ChatMessage.test.tsx
#	packages/ui/src/ai/components/ChatMessage.tsx
#	packages/ui/src/ai/components/ChatThread.tsx
@giraffesyo
Michael McQuade (giraffesyo) merged commit e40f4c4 into canary Oct 20, 2026
10 checks passed
@giraffesyo
Michael McQuade (giraffesyo) deleted the feat/ui-chat-tool-approvals branch October 20, 2026 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant