Skip to content

Comments

feat(ai-chat): support addToolOutput for approval states and custom denial messages#967

Open
threepointone wants to merge 5 commits intomainfrom
approval-followups
Open

feat(ai-chat): support addToolOutput for approval states and custom denial messages#967
threepointone wants to merge 5 commits intomainfrom
approval-followups

Conversation

@threepointone
Copy link
Contributor

Summary

Follow-up to #956 (which fixed #955). This PR addresses the remaining two issues from #955:

  1. addToolOutput now works for tools in approval states_applyToolResult accepts approval-requested and approval-responded states, not just input-available. Users can provide custom tool results for needsApproval tools.

  2. Custom denial messages via output-erroraddToolOutput now accepts state: "output-error" and errorText, enabling the Vercel AI SDK recommended pattern for client-side tool denial with a custom message (instead of the generic "Tool execution denied.").

  3. Rejections auto-continue — Tool approval rejections now auto-continue the conversation (when autoContinueAfterToolResult is enabled), so the LLM sees the tool_result and responds naturally.

Example

addToolOutput({
  toolCallId: invocation.toolCallId,
  state: "output-error",
  errorText: "User declined: insufficient budget for this quarter"
});

What changed

SDK (packages/ai-chat/)

  • src/index.ts
    • _applyToolResult() — expanded matchStates to include approval-requested and approval-responded; added overrideState and errorText params for output-error
    • CF_AGENT_TOOL_RESULT handler — extracts and forwards state/errorText from the wire message
    • CF_AGENT_TOOL_APPROVAL handler — removed approved guard from auto-continue; rejections now continue the conversation
    • _resolveMessageForToolMerge() — added output-error to dedup state check
  • src/types.ts — added optional state and errorText fields to CF_AGENT_TOOL_RESULT wire message
  • src/react.tsx
    • AddToolOutputOptions — moved before OnToolCallCallback as single source of truth; added state and errorText fields; output made optional
    • OnToolCallCallback — references AddToolOutputOptions via Omit<..., "toolName">
    • sendToolOutputToServer — forwards state/errorText; suppresses autoContinue for output-error
    • Both addToolOutput implementations updated to use shared type and pass through new fields

Tests (packages/ai-chat/src/tests/)

  • 8 new tests covering the full state transition matrix, terminal-state guards, default errorText fallback, and e2e convertToModelMessages integration
  • Updated rejection auto-continue test to assert continuation now happens

Docs

  • docs/human-in-the-loop.md — added output-denied UI handling in client example; new "Custom denial messages with addToolOutput" section; documented auto-continue behavior for rejections
  • docs/chat-agents.md — fixed 4 pre-existing bugs: approval-requiredapproval-requested, part.type === "tool"isToolUIPart(), part.toolNamegetToolName(), id: part.toolCallIdid: part.approval?.id
  • docs/client-tools-continuation.md — added output-error denial snippet

Examples

  • examples/playground/src/demos/ai/ToolsDemo.tsx — "Denied" and "Error" badges for output-denied/output-error states
  • examples/ai-chat/src/client.tsxoutput-denied rendering with "Denied" badge

Notes for reviewers

  1. Start with _applyToolResult in src/index.ts — the core change is widening matchStates and adding the output-error code path.

  2. Auto-continue for rejections (src/index.ts line ~634) — the guard changed from applied && approved && autoContinue to applied && autoContinue. This is intentional: now that output-denied produces a proper tool_result (from fix(ai-chat): emit output-denied state for rejected tool approvals #956), the LLM should see the denial and respond. Without this, rejections silently dead-end the conversation.

  3. sendToolOutputToServer suppresses autoContinue for output-error (react.tsx line ~805) — when the client explicitly sends output-error, we set autoContinue: false so the server does not auto-continue. This is the opposite of the approval flow auto-continue change above. The difference: output-error via addToolOutput is a deliberate client action where the client controls the flow, while addToolApprovalResponse is the standard approve/reject path where auto-continue is expected.

  4. Not a breaking change — all new fields are optional; existing callers get identical behavior. The auto-continue change for rejections is a behavior change but was previously a bug (rejections dead-ended).

  5. Doc fix in chat-agents.md is unrelated to this PR but fixes real bugs (wrong state name approval-required, wrong field for approval ID) that would confuse users copy-pasting the example.

Test plan

  • All 182 ai-chat workers tests pass (22 test files)
  • Manual test: playground ToolsDemo — reject calculate tool → LLM responds to denial
  • Manual test: playground ToolsDemo — rejected tool shows "Denied" badge

Made with Cursor

Allow client-side tool outputs to signal denials/errors and work for approval flows. addToolOutput now supports a state override ("output-error") and errorText, and can be used for tools in approval-requested/approval-responded states (not just input-available). The agent core applies output-error state and propagates errorText, and tool approval rejections (approved: false) now auto-continue when autoContinue is set so the LLM can react to the denial. Updated hooks/types/react logic to send and handle the new fields, updated UI examples/docs to render denied/error states, and added tests covering the new behaviors and edge cases.
@changeset-bot
Copy link

changeset-bot bot commented Feb 21, 2026

🦋 Changeset detected

Latest commit: 7575a4f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/ai-chat Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@threepointone
Copy link
Contributor Author

/bonk review this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 21, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/agents@967
npm i https://pkg.pr.new/cloudflare/agents/@cloudflare/ai-chat@967
npm i https://pkg.pr.new/cloudflare/agents/@cloudflare/codemode@967
npm i https://pkg.pr.new/cloudflare/agents/hono-agents@967

commit: 7575a4f

Include an explicit approval.id ("approval-denied-guard") in the approval object within client-tool-duplicate-message.test.ts. This aligns the test data with the expected shape (approval id required) and prevents schema/guard-related failures during test assertions.
@ask-bonk

This comment was marked as resolved.

@ask-bonk

This comment was marked as resolved.

@threepointone
Copy link
Contributor Author

/bonk try reviewing again

ask-bonk[bot]

This comment was marked as resolved.

Co-authored-by: threepointone <threepointone@users.noreply.github.com>
@ask-bonk

This comment was marked as resolved.

Treat tool results with state "output-error" as deliberate client denials: set autoContinue to false for these cases and avoid storing them in clientToolResults. A comment clarifies that addToolApprovalResponse still performs the standard approve/reject auto-continue flow, and users can call sendMessage() if they want the LLM to respond to an output-error denial. This prevents unintended automatic continuation after explicit rejections.
@threepointone
Copy link
Contributor Author

/bonk please review again

@threepointone
Copy link
Contributor Author

/bonk hey buddy, please review my PR

@ask-bonk

This comment was marked as resolved.

@ask-bonk

This comment was marked as resolved.

Update documentation and code comments to make auto-continue semantics explicit: addToolApprovalResponse (including rejections) auto-continues when autoContinueAfterToolResult is enabled, while addToolOutput with state:"output-error" does not auto-continue and requires calling sendMessage() to proceed. Aligns react.tsx comments with the docs for clearer developer guidance.
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.

@cloudflare/ai-chat: Tool approval denial does not produce a tool_result

1 participant