feat: publish the predicted follow-up prompt as _meta.promptSuggestions (#1356)

## What

The Claude Code CLI can predict the prompt the user is likely to send
next. This adapter asked for nothing and dropped the `prompt_suggestion`
message. It now asks for the prediction (`promptSuggestions: true`) and
forwards the one prediction of a turn.

ACP has no field for a composer prefill, so the value travels as

```json
{
  "sessionUpdate": "session_info_update",
  "_meta": { "promptSuggestions": { "suggestions": [{ "text": "Run the tests" }] } }
}
```

The key names no vendor, so another client can read it. AIR already
renders the first non-blank text as gray text in an empty composer,
where Tab fills the composer and sends nothing.

## Behavior

- `suggestions: []` clears the prediction the client holds: at the next
`session/prompt`, because that turn's prediction is spent, and on a
`conversation_reset`, because the transcript it was drawn from is gone.
- A prediction the next prompt outran — the user sent the next prompt
before the adapter read the prediction off the SDK stream — is dropped
instead of published mid-turn.
- A blank prediction never goes on the wire.
- The update is sent only to an AIR client, like the other extension
keys, so non-AIR traffic is unchanged (the key is added to
`AIR_ONLY_META_KEYS`; the `origin/main` scenario baselines stay
byte-identical).
- The CLI predicts nothing on the first turn, after an API error, in
plan mode, and at the usage limit, so a turn without a prediction is
normal. A caller can opt out with
`_meta.claudeCode.options.promptSuggestions: false`.

Documented in `docs/air-extensions.md`.

## Tests

`src/tests/prompt-suggestions.test.ts` covers the wire shape, the
publish after the turn's result, the clear at the next prompt, the
dropped stale prediction, the blank prediction, and a non-AIR client.
`src/tests/create-session-options.test.ts` covers the SDK option and the
caller opt-out.

`npm run check`, `npm run build` and the touched suites are green; the
full `test:run` has pre-existing flakiness on this machine
(session-index watchers, providers) unrelated to this change.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
This commit is contained in:
nikita-ashihmin
2026-10-15 17:55:44 +04:00
committed by GitHub
parent 910d83ceea
commit 78a29f58ba
6 changed files with 315 additions and 1 deletions

View File

@@ -20,6 +20,7 @@ It describes only this adapter.
- [Session hooks](#session-hooks)
- [Permission presentation](#permission-presentation)
- [Goal](#goal)
- [Predicted follow-up prompt](#predicted-follow-up-prompt)
- [Recommended config values](#recommended-config-values)
- [Async tasks](#async-tasks)
- [Agent file-change report](#agent-file-change-report)
@@ -206,6 +207,7 @@ The adapter keeps them where they are.
| `steering` | `initialize` response `_meta.steering = {supported: true}` | The agent accepts `_session/steering` for a running turn. |
| `quota` | `PromptResponse._meta.quota` | Token usage of the turn, also split by model. |
| `authStatus` | `initialize` response `agentCapabilities._meta.authStatus` | The agent pushes `_auth/status_update`. The object carries no payload. |
| `promptSuggestions` | `session_info_update._meta.promptSuggestions = {suggestions: [{text}]}`, AIR only | The prompt the agent predicts for the next turn. An empty list clears it. |
## Auth source display
@@ -816,6 +818,37 @@ While that command is pending, the adapter ignores late updates for the previous
A matching runtime `active_goal` stays authoritative and adds the iteration, reason, and timestamp.
A failed command restores the previous state.
## Predicted follow-up prompt
After a turn, the Claude Code CLI can predict the prompt the user is likely to send next.
The adapter asks for it with the SDK option `promptSuggestions: true` and forwards the one prediction of the turn.
ACP has no field for a composer prefill, so the prediction travels outside the AIR namespace:
```json
{
"sessionUpdate": "session_info_update",
"_meta": { "promptSuggestions": { "suggestions": [{ "text": "Run the tests" }] } }
}
```
The key names no vendor, so another client can read it.
The adapter sends it only to an AIR client; see the [compatibility rule](#compatibility-rule).
AIR shows the first non-blank `suggestions[].text` as gray text in an empty composer.
Tab fills the composer with it and sends nothing.
`suggestions: []` clears the prediction the client holds. The adapter clears it:
- when the next `session/prompt` arrives, because that turn's prediction is spent;
- on a `conversation_reset`, because the prediction was drawn from the transcript the reset dropped.
A prediction the next prompt outran — the user sent it before the adapter read the prediction off the SDK stream — is dropped instead of published.
The CLI predicts nothing on the first turn of a session, after an API error, in plan mode, and at the account's usage limit, so many turns end with no prediction.
A client must treat a turn without one as normal.
A caller can turn the whole feature off with `_meta.claudeCode.options.promptSuggestions: false` on `session/new`; a user can turn it off in Claude Code settings or with `CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false`.
## Recommended config values
The `recommendedValue` extension lets a client show concrete model and effort choices without an ambiguous `default` row.