Fetch OpenCode Go's catalog as the settings window opens

Codex already refreshes its boxes from the source at open, so the
built-in list is never the whole truth for longer than a window takes to
build. OpenCode Go now gets the same courtesy: one request to /models on
a background thread, both of its boxes refilled with the picked model
kept, skipped entirely when there is no key to send.
This commit is contained in:
2026-08-27 16:04:30 +03:00
parent 1ff18e46ab
commit fa3d72f5a7
2 changed files with 55 additions and 1 deletions
+34
View File
@@ -569,6 +569,7 @@ class SettingsWindow(QDialog):
_models_loaded = pyqtSignal(list, str, str)
_transcribe_models_loaded = pyqtSignal(list, str)
_codex_models_loaded = pyqtSignal(list)
_opencode_models_loaded = pyqtSignal(list)
# Which key was tested, whether it worked, and what to write under it.
_test_done = pyqtSignal(str, bool, str)
# The release that was found, or None, and what went wrong instead.
@@ -628,6 +629,7 @@ class SettingsWindow(QDialog):
self._models_loaded.connect(self._on_models_loaded)
self._transcribe_models_loaded.connect(self._on_transcribe_models_loaded)
self._codex_models_loaded.connect(self._on_codex_models_loaded)
self._opencode_models_loaded.connect(self._on_opencode_models_loaded)
self._test_done.connect(self._on_test_done)
self._update_checked.connect(self._on_update_checked)
self.transcriber.progress.connect(self._on_file_progress)
@@ -639,6 +641,7 @@ class SettingsWindow(QDialog):
self.meetings.failed.connect(self._on_minutes_failed)
self._load()
self._load_codex_models()
self._load_opencode_models()
# Connected after the load, so that filling the boxes in is not taken
# for the user ticking them.
self.file_timestamps.toggled.connect(self._remember_file_choices)
@@ -2080,6 +2083,37 @@ class SettingsWindow(QDialog):
combo.addItem(name, name)
combo.setCurrentText(current)
def _load_opencode_models(self):
"""Ask OpenCode Go for its catalog of the day, off the interface thread.
The same courtesy Codex gets: the built-in list is only a starting
point, so the boxes are refreshed from the source as the window opens.
Skipped without a key, so a machine that never touched OpenCode Go
sends it nothing; the Fetch button stays for a key typed in just now.
"""
key = self.conf.opencode_key()
if not key:
return
def work():
try:
found = api.openai_models(key, self.conf["opencode_base_url"],
"OpenCode Go")
except api.ApiError:
# The window is only opening; the Test button says what failed.
return
if found:
self._opencode_models_loaded.emit(found)
threading.Thread(target=work, daemon=True).start()
def _on_opencode_models_loaded(self, models):
for combo in (self.cleanup_opencode_model, self.assistant_opencode_model):
current = combo.currentText()
combo.clear()
combo.addItems(models)
combo.setCurrentText(current)
def _test_openai(self):
key, base = self._typed_key("openai")
self._test_key("openai", lambda: t(
+21 -1
View File
@@ -135,6 +135,8 @@ class Settings(DikteTest):
"_load_transcribe_models"))
self.enterContext(mock.patch.object(settings_ui.SettingsWindow,
"_load_codex_models"))
self.enterContext(mock.patch.object(settings_ui.SettingsWindow,
"_load_opencode_models"))
# The local model boxes fetch their own list the moment they are shown,
# from a thread, which is nobody's test failing but a real request.
self.enterContext(mock.patch.object(settings_ui.LocalModelBox,
@@ -285,6 +287,20 @@ class Settings(DikteTest):
self.assertEqual(window.cleanup_codex_model.currentText(),
"my-own-model")
def test_opencode_answering_refills_both_of_its_boxes(self):
"""The catalog fetched at open replaces the built-in list in the
cleanup and agent boxes alike, and neither loses what was picked."""
conf = self.config(cleanup_opencode_model="my-own-model")
window = self.window(conf)
window._on_opencode_models_loaded(["glm-9", "kimi-k9"])
for combo in (window.cleanup_opencode_model,
window.assistant_opencode_model):
with self.subTest(combo=combo.objectName() or "combo"):
offered = [combo.itemText(i) for i in range(combo.count())]
self.assertEqual(offered, ["glm-9", "kimi-k9"])
self.assertEqual(window.cleanup_opencode_model.currentText(),
"my-own-model")
def test_a_fetched_opencode_list_lands_in_opencode_s_own_box(self):
"""The OpenRouter and meeting boxes are not refilled by another
provider's catalog, and the picked model survives the refill."""
@@ -1026,7 +1042,9 @@ class MeetingSources(DikteTest):
mock.patch.object(settings_ui.SettingsWindow,
"_load_transcribe_models"), \
mock.patch.object(settings_ui.SettingsWindow,
"_load_codex_models"):
"_load_codex_models"), \
mock.patch.object(settings_ui.SettingsWindow,
"_load_opencode_models"):
window = settings_ui.SettingsWindow(cfg.Config())
self.addCleanup(window.deleteLater)
self.addCleanup(window.close)
@@ -1058,6 +1076,8 @@ class LocalModels(DikteTest):
# And one with Codex on it would ask it for its model list.
self.enterContext(mock.patch.object(settings_ui.SettingsWindow,
"_load_codex_models"))
self.enterContext(mock.patch.object(settings_ui.SettingsWindow,
"_load_opencode_models"))
def window(self, conf):
window = settings_ui.SettingsWindow(conf)