Recommended config values extension

September 9, 2026 ยท View on GitHub

This document defines the experimental AIR recommendedValue session configuration extension implemented by claude-agent-acp. It lets a client show concrete model and effort choices without an ambiguous default row while preserving legacy config options for every client that does not opt in.

Permission modes and all other session config options are outside this extension and remain unchanged.

Capability negotiation

A client opts in through the common AIR capability list in initialize:

{
  "clientCapabilities": {
    "_meta": {
      "jetbrains": {
        "air": {
          "version": 1,
          "capabilities": ["recommendedValue"]
        }
      }
    }
  }
}

The adapter enables the extension only when the AIR version is a finite integer greater than or equal to 1 and capabilities contains the singular recommendedValue capability. Otherwise it preserves the existing default rows, current values, and metadata exactly. The adapter advertises the same capability in its top-level initialize-response _meta.jetbrains.air.capabilities list.

Config option metadata

For each selector transformed by the extension, the adapter writes the concrete recommendation through the common AIR metadata envelope at _meta.jetbrains.air.recommendedValue:

{
  "id": "model",
  "type": "select",
  "currentValue": "sonnet",
  "options": [
    { "value": "opus", "name": "Claude Opus" },
    { "value": "sonnet", "name": "Claude Sonnet" },
    { "value": "haiku", "name": "Claude Haiku" }
  ],
  "_meta": {
    "jetbrains": {
      "air": {
        "version": 1,
        "recommendedValue": "sonnet"
      }
    }
  }
}

recommendedValue always names one of that selector's advertised option values. It is independent from currentValue: a user's explicit selection remains current even when the SDK recommends a different value.

Model behavior

The SDK's default model entry may carry a resolvedModel identifying the model currently recommended by Claude. The adapter matches that identifier exactly to a selectable named model (accepting equivalent -1m and [1m] suffix spellings), removes the default row, and advertises the named value in _meta.jetbrains.air.recommendedValue. When the session itself is still using the SDK default, the same concrete value is presented as currentValue.

If the SDK recommendation cannot be mapped to an advertised named model, the adapter retains the legacy default row for that selector and omits recommendedValue. This prevents a client from receiving a recommendation or current value it cannot select.

Terse SDK labels that match their underlying model family include the concrete version derived from model metadata: for example, Sonnet becomes Sonnet 5, Fable becomes Fable 5.1, and Claude Haiku becomes Claude Haiku 4.5. Context suffixes such as Opus (1M context) are omitted from the label because the context remains in the description. Custom labels are preserved, and normalization falls back to the original labels if two selectable entries would otherwise collide. This presentation also applies to legacy clients.

Effort behavior

When the current model supports effort selection, the adapter removes the default effort row and advertises medium as the recommendation. An existing explicit or settings-derived effort remains the currentValue; an absent or legacy default current effort is presented as medium.

The adapter applies the displayed effort to the SDK on session creation and model switches, so the concrete selection reflects the effort actually used. Explicit SDK options.effort and ACP picker choices remain pinned across model switches while supported. Otherwise each switch re-reads the new model's settings before falling back to the recommendation. Switching to a model without effort support clears the flag override. Legacy clients continue to let the SDK resolve automatic effort.

If effort synchronization fails after the model has already switched, the adapter still reports the new model but never presents the unapplied effort as current. It retains the last successfully applied value when that value is selectable for the new model; otherwise it temporarily omits the effort selector until a later successful switch can rebuild truthful state.

If a future model exposes effort choices without medium, the first SDK-advertised effort level is used so both recommendedValue and currentValue remain valid option values.

SDK upgrade check

The ordinary test suite pins the SDK version whose Opus label was verified. When updating the SDK, first run the live contract test in an authenticated environment:

RUN_INTEGRATION_TESTS=true npx vitest run src/tests/model-presentation.test.ts

Review any changes to the available Opus entries and their labels before updating the version guard and, if necessary, the normalization. The live test only initializes the SDK; it sends no model prompt.