Skip to content

Commit d5f5618

Browse files
feat(acp): support config-option-backed model picker
Some agents (e.g. Claude Code) expose model selection via configOptions rather than the top-level models array. Without this path, the model combo stayed hidden and model preferences were never saved or restored for those agents. - Fall back to configOptions when availableModels is empty, scanning for an option whose id or category matches "model" - Track the option id in m_modelConfigOptionId so all three sites (populate, change, restore) route through the right protocol: session/set_config vs session/set_model - Persist and restore preferences under the actual option id rather than the hard-coded "model" key, so the registry key matches what the agent expects
1 parent ba3b7ed commit d5f5618

2 files changed

Lines changed: 71 additions & 17 deletions

File tree

src/widgets/AcpSessionView.cpp

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,17 +1090,41 @@ void AcpSessionView::onMetadataChanged()
10901090
if (!m_model) return;
10911091
m_updatingSelectors = true;
10921092

1093-
// Models combo.
1093+
// Models combo. Prefer the top-level models catalog; if the agent
1094+
// doesn't surface one, fall back to a `model` config option (Claude Code
1095+
// exposes the picker only via configOptions, never the models array).
10941096
m_modelCombo->clear();
1097+
m_modelConfigOptionId.clear();
10951098
const auto &models = m_model->availableModels();
1096-
for (const auto &m : models) {
1097-
m_modelCombo->addItem(m.name.isEmpty() ? m.id : m.name, m.id);
1098-
}
1099-
if (!m_model->currentModelId().isEmpty()) {
1100-
const int idx = m_modelCombo->findData(m_model->currentModelId());
1101-
if (idx >= 0) m_modelCombo->setCurrentIndex(idx);
1099+
if (!models.isEmpty()) {
1100+
for (const auto &m : models) {
1101+
m_modelCombo->addItem(m.name.isEmpty() ? m.id : m.name, m.id);
1102+
}
1103+
if (!m_model->currentModelId().isEmpty()) {
1104+
const int idx = m_modelCombo->findData(m_model->currentModelId());
1105+
if (idx >= 0) m_modelCombo->setCurrentIndex(idx);
1106+
}
1107+
} else {
1108+
for (const auto &opt : m_model->configOptions()) {
1109+
const QString idLower = opt.id.toLower();
1110+
const QString catLower = opt.category.toLower();
1111+
const bool matches = idLower == QLatin1String("model")
1112+
|| catLower == QLatin1String("model");
1113+
if (!matches) continue;
1114+
m_modelConfigOptionId = opt.id;
1115+
for (const auto &ch : opt.options) {
1116+
const QString label = ch.name.isEmpty() ? ch.value : ch.name;
1117+
if (!label.isEmpty()) m_modelCombo->addItem(label, ch.value);
1118+
}
1119+
const QString currentVal = opt.currentValue.toString();
1120+
if (!currentVal.isEmpty()) {
1121+
const int idx = m_modelCombo->findData(currentVal);
1122+
if (idx >= 0) m_modelCombo->setCurrentIndex(idx);
1123+
}
1124+
break;
1125+
}
11021126
}
1103-
m_modelCombo->setVisible(!models.isEmpty());
1127+
m_modelCombo->setVisible(m_modelCombo->count() > 0);
11041128

11051129
// Modes combo.
11061130
m_modeCombo->clear();
@@ -1160,8 +1184,11 @@ void AcpSessionView::onMetadataChanged()
11601184
const QString agentId = m_connection->definition().id;
11611185
if (!agentId.isEmpty()) {
11621186
const QString curModel = m_model->currentModelId();
1163-
if (!curModel.isEmpty())
1164-
m_registry->setAgentPreference(agentId, QStringLiteral("model"), curModel);
1187+
if (!curModel.isEmpty()) {
1188+
const QString modelKey = m_modelConfigOptionId.isEmpty()
1189+
? QStringLiteral("model") : m_modelConfigOptionId;
1190+
m_registry->setAgentPreference(agentId, modelKey, curModel);
1191+
}
11651192

11661193
const QString curMode = m_model->currentModeId();
11671194
if (!curMode.isEmpty())
@@ -1471,10 +1498,17 @@ void AcpSessionView::onModelComboChanged(int index)
14711498
if (m_updatingSelectors || !m_connection || index < 0) return;
14721499
const QString id = m_modelCombo->itemData(index).toString();
14731500
if (id.isEmpty()) return;
1474-
m_connection->setModel(id);
1501+
// Config-option-backed picker (Claude Code) → session/set_config; the
1502+
// dedicated session/set_model channel only applies to the models array.
1503+
const QString prefKey = m_modelConfigOptionId.isEmpty()
1504+
? QStringLiteral("model") : m_modelConfigOptionId;
1505+
if (!m_modelConfigOptionId.isEmpty()) {
1506+
m_connection->setConfigOption(m_modelConfigOptionId, id);
1507+
} else {
1508+
m_connection->setModel(id);
1509+
}
14751510
if (m_registry) {
1476-
m_registry->setAgentPreference(m_connection->definition().id,
1477-
QStringLiteral("model"), id);
1511+
m_registry->setAgentPreference(m_connection->definition().id, prefKey, id);
14781512
}
14791513
}
14801514

@@ -1518,13 +1552,29 @@ void AcpSessionView::applySavedPreferences()
15181552
if (agentId.isEmpty()) return;
15191553

15201554
// Model: send only if the saved id exists in the catalog and differs.
1521-
const QString savedModel = m_registry->agentPreference(agentId, QStringLiteral("model"));
1555+
// Config-option-backed pickers (Claude Code) restore via session/set_config.
1556+
const QString modelPrefKey = m_modelConfigOptionId.isEmpty()
1557+
? QStringLiteral("model") : m_modelConfigOptionId;
1558+
const QString savedModel = m_registry->agentPreference(agentId, modelPrefKey);
15221559
if (!savedModel.isEmpty() && savedModel != m_model->currentModelId()) {
1523-
for (const auto &m : m_model->availableModels()) {
1524-
if (m.id == savedModel) {
1525-
m_connection->setModel(savedModel);
1560+
if (!m_modelConfigOptionId.isEmpty()) {
1561+
for (const auto &opt : m_model->configOptions()) {
1562+
if (opt.id != m_modelConfigOptionId) continue;
1563+
for (const auto &ch : opt.options) {
1564+
if (ch.value == savedModel) {
1565+
m_connection->setConfigOption(m_modelConfigOptionId, savedModel);
1566+
break;
1567+
}
1568+
}
15261569
break;
15271570
}
1571+
} else {
1572+
for (const auto &m : m_model->availableModels()) {
1573+
if (m.id == savedModel) {
1574+
m_connection->setModel(savedModel);
1575+
break;
1576+
}
1577+
}
15281578
}
15291579
}
15301580

src/widgets/AcpSessionView.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,10 @@ private slots:
238238
QComboBox *m_modeCombo = nullptr;
239239
QComboBox *m_effortCombo = nullptr;
240240
QString m_effortConfigOptionId;
241+
// Non-empty when the agent exposes the model picker as a config option
242+
// (e.g. Claude Code's `model` select) rather than the top-level models
243+
// array. Drives whether model changes dispatch via session/set_config.
244+
QString m_modelConfigOptionId;
241245

242246
// Auto-approve
243247
QCheckBox *m_autoApproveCheck = nullptr;

0 commit comments

Comments
 (0)