feat(core): allow user feedback when cancelling a tool call#25681
feat(core): allow user feedback when cancelling a tool call#25681luckyrandom wants to merge 1 commit intogoogle-gemini:mainfrom
Conversation
Extends ToolConfirmationPayload with a ToolCancelWithFeedbackPayload
variant ({ feedback: string }). When a confirmation response carries
this payload with outcome=Cancel, the scheduler interpolates the user's
reason into the cancellation message delivered to the model, so the
model sees *why* the call was denied without waiting for the next user
turn.
Before:
functionResponse.response.error
= "[Operation Cancelled] Reason: User denied execution."
After (with feedback):
functionResponse.response.error
= "[Operation Cancelled] Reason: User denied execution. Feedback: <text>"
Behavior without a feedback payload is unchanged. The existing
"[Operation Cancelled] Reason:" prefix is preserved for backward
compatibility with any consumer pattern-matching on it.
This mirrors the precedent already set by ToolExitPlanModeConfirmation
Payload.feedback, extending the same channel to generic tool denials
so non-TUI hosts (IDE, web UI) can surface per-call denial feedback.
- tools/tools.ts: add ToolCancelWithFeedbackPayload to the payload union
- scheduler/confirmation.ts: propagate the final payload through
ResolutionResult so the scheduler can inspect it
- scheduler/scheduler.ts: on Cancel, use payload.feedback to build the
cancel reason
- scheduler/scheduler.test.ts: cover feedback present + empty-string
fallback
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a mechanism to pass user-authored feedback when cancelling a tool call. By embedding this feedback directly into the function response, the model can immediately understand the reason for the cancellation and potentially adjust its next action without requiring a separate follow-up message from the user. This change is backward-compatible and opt-in for hosts. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
🛑 Action Required: Evaluation ApprovalSteering changes have been detected in this PR. To prevent regressions, a maintainer must approve the evaluation run before this PR can be merged. Maintainers:
Once approved, the evaluation results will be posted here automatically. |
There was a problem hiding this comment.
Code Review
This pull request introduces the ability to capture and return user feedback when a tool call is cancelled. It updates the ResolutionResult and ToolConfirmationPayload types, modifies the scheduler to extract feedback from the confirmation response, and updates the cancellation status message to include this feedback. Unit tests were added to verify the correct formatting of the cancellation reason. A security concern was raised regarding the potential for prompt injection when interpolating untrusted user feedback into the message sent back to the LLM, suggesting that the input should be sanitized by removing newlines and context-breaking characters.
| const reason = cancelFeedback | ||
| ? `User denied execution. Feedback: ${cancelFeedback}` | ||
| : 'User denied execution.'; |
There was a problem hiding this comment.
The cancelFeedback variable contains untrusted user input interpolated into the reason string sent to the LLM, posing a prompt injection risk. To mitigate this, avoid including user-provided input in content passed to the LLM (llmContent); use returnDisplay if the input is for display purposes. If the input must be passed to the LLM, ensure it is sanitized at the tool or subagent level (not in the executor) by trimming and removing newlines and context-breaking characters like ].
References
- To prevent prompt injection, avoid including user-provided input in content passed to the LLM (
llmContent). If the input is needed for display purposes, usereturnDisplayinstead. - Prompt injection sanitization should be handled at the tool or subagent level, not on a per-tool basis within the agent executor.
- Sanitize data from LLM-driven tools before injecting it into a system prompt to prevent prompt injection. At a minimum, remove newlines and context-breaking characters (e.g., ']').
- When validating string parameters from tools, trim the string first and then check for emptiness to prevent whitespace-only values from being accepted.
Summary
Extends
ToolConfirmationPayloadwith aToolCancelWithFeedbackPayloadvariant ({ feedback: string }). When aTOOL_CONFIRMATION_RESPONSEcarries this payload withoutcome=Cancel, the scheduler interpolates the user's reason into the cancellation message delivered back to the model.Before:
After (with feedback):
Behavior without a feedback payload is unchanged. The existing
[Operation Cancelled] Reason:prefix is preserved for backward compatibility.Motivation
When the SDK asks the user to confirm a destructive tool call, the user can Allow or Cancel. On Cancel, the model receives only a generic
"User denied execution."— not enough signal to correct the call. In practice the user often knows exactly what's wrong ("do a smoke run first, not the real run", "write tonotes.mdinstead of overwritingREADME.md") and wants the model to retry with that fix applied.What's possible today: the user cancels, then types the reason as a chat message. The problem is ordering — the model sees the cancellation result before the user's follow-up message arrives, so it can freely decide to retry (often with a slightly different command that the user would still deny) without ever seeing the feedback. The user's reason ends up scolding the model after it's already wasted a step.
This change lets hosts optionally thread a short user-authored reason through the same
payloadchannel already used byToolExitPlanModeConfirmationPayload.feedback, so the feedback is embedded in the cancelled call'sfunctionResponse— the model cannot retry without first reading it. The result: a revised tool call in the next step of the same turn, no follow-up user message, no blind retry.The addition is backward-compatible (absent payload → existing behavior unchanged) and opt-in per host.
Changes
tools/tools.ts: addToolCancelWithFeedbackPayloadto theToolConfirmationPayloadunionscheduler/confirmation.ts: propagate the finalpayloadthroughResolutionResultso the scheduler can inspect itscheduler/scheduler.ts: onCancel, usepayload.feedbackto build the cancel reasonscheduler/scheduler.test.ts: cover feedback-present and empty-string-fallback casesTest plan
npm test -- src/scheduler— all 146 scheduler tests pass (144 existing + 2 new)npm run typecheck— clean