Skip to content

fix(acp): let the model continue when a question can't be shown - #52740

Merged
nexxeln merged 2 commits into
v2from
acp-question-fallback
Oct 2, 2026
Merged

nexxeln merged 2 commits into
v2from
acp-question-fallback

Conversation

@nexxeln

@nexxeln nexxeln commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Why the change

When an ACP client can't show a question form, the model now gets a reason and keeps going instead of the turn silently ending.

Special things to note

  • The credential check now also looks at a field's description and option labels, because the question text goes into description. The ACP spec says form mode "MUST NOT be used to request secrets or credentials". Questions caught by it go through the same fallback.
  • Hiding the tool with a session deny rule was ruled out because session rules are saved, so the deny would carry over into the TUI, the app, and child sessions.
  • No core changes. The server's form cancel already takes an optional message, and the question tool already returns it to the model.

Change outline

Before, ACP cancelled the form with no message. Core treats that as the user dismissing the question, so the tool was marked aborted and the turn stopped with no reason given:

model:  question({ header: "Runtime", question: "Which runtime?" })
ACP:    DELETE /form/frm_1                      # client has no form elicitation
core:   tool aborted, step interrupted
client: stopReason "cancelled"                  # nothing shown, no answer

Now a question form that ACP won't show is cancelled with a message. The question tool returns it as a normal tool result and the model continues:

model:  question({ header: "Runtime", question: "Which runtime?" })
ACP:    DELETE /form/frm_1?message=…
model:  tool result: "The question couldn't be shown to the user in this client. Continue without an answer:
        make reasonable assumptions and state them, or ask the user in your reply if you can't proceed."
model:  "I'll assume Bun…"
client: stopReason "end_turn"

The decision lives in elicitation.ts. Other form kinds, such as websearch.provider, and a user's decline or dismissal in the client are still cancelled without a message.

 FormAsk (turn.ts)
   requestedSchema(form, capabilities)
-  undefined → cancel(form)
+  undefined → cancelUnshown(form)
+                kind "question" → cancel(form, UnshownQuestionMessage)
+                otherwise       → cancel(form)
   schema    → elicit → answer | cancel(form)      # user decline stays silent

requestedSchema returns undefined when the client lacks form elicitation, the form kind isn't allowed, a field looks like a credential, or ACP can't represent the form. The message doesn't name a reason, so it's accurate in every case.

-credential = key or title matches Credential
+credential = key, title, description, or an option label matches Credential
-Credential = /…|api[_-]?key|…|private[_-]?key/i
+Credential = /…|api[_ -]?key|…|private[_ -]?key/i     # so "Paste your API key" matches

Tests:

packages/cli/test/acp/
├── turn.test.ts                    # no elicitation + question → cancel carries the message
├── elicitation.test.ts             # credential in description or option label is not shown;
│                                   # description-only credential question → message;
│                                   # websearch.provider → silent; user decline → silent
├── question.subprocess.test.ts     # real server: model calls question, gets the message
│                                   # as the call_question tool result, turn ends end_turn
├── subprocess.ts                   # shared toolCallStream(id, name, input) helper
└── wire-fixture.ts                 # records the cancel message

@nexxeln
nexxeln merged commit e2a700d into v2 Oct 2, 2026
10 checks passed
@nexxeln
nexxeln deleted the acp-question-fallback branch October 2, 2026 12:09
Ichinose-Kazuki pushed a commit to Ichinose-Kazuki/opencode that referenced this pull request Oct 7, 2026
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