Offer OpenCode Go in the settings window

A key row under Keys, a model box in the cleanup tab and a box in the
agent tab, each with its own model list of the models OpenCode Go
serves over /chat/completions. Fetch model list reads whichever cleanup
provider is on screen, and the Turkish strings cover the new rows.
This commit is contained in:
sudoeren
2026-08-25 21:05:21 +03:00
parent 0a6c6128a2
commit 3c336e7178
3 changed files with 141 additions and 19 deletions
+27 -2
View File
@@ -40,8 +40,10 @@ def t(text, /, **kwargs):
# by the sentence, so it arrives already inflected. English takes the name as it # by the sentence, so it arrives already inflected. English takes the name as it
# is and puts the preposition in the sentence, where it belongs. # is and puts the preposition in the sentence, where it belongs.
_TR_CASES = { _TR_CASES = {
"dative": {"Claude": "Claude'a", "Codex": "Codex'e", "OpenRouter": "OpenRouter'a"}, "dative": {"Claude": "Claude'a", "Codex": "Codex'e", "OpenRouter": "OpenRouter'a",
"accusative": {"Claude": "Claude'u", "Codex": "Codex'i", "OpenRouter": "OpenRouter'ı"}, "OpenCode Go": "OpenCode Go'ya"},
"accusative": {"Claude": "Claude'u", "Codex": "Codex'i",
"OpenRouter": "OpenRouter'ı", "OpenCode Go": "OpenCode Go'yu"},
} }
@@ -221,11 +223,15 @@ TR = {
"sk-… (falls back to OPENAI_API_KEY)": "sk-… (boşsa OPENAI_API_KEY kullanılır)", "sk-… (falls back to OPENAI_API_KEY)": "sk-… (boşsa OPENAI_API_KEY kullanılır)",
"gsk_… (falls back to GROQ_API_KEY)": "gsk_… (boşsa GROQ_API_KEY kullanılır)", "gsk_… (falls back to GROQ_API_KEY)": "gsk_… (boşsa GROQ_API_KEY kullanılır)",
"sk-or-… (falls back to OPENROUTER_API_KEY)": "sk-or-… (boşsa OPENROUTER_API_KEY kullanılır)", "sk-or-… (falls back to OPENROUTER_API_KEY)": "sk-or-… (boşsa OPENROUTER_API_KEY kullanılır)",
"(falls back to OPENCODE_API_KEY)": "(boşsa OPENCODE_API_KEY kullanılır)",
"Test": "Test et", "Test": "Test et",
"Trying…": "Deneniyor…", "Trying…": "Deneniyor…",
"Runs on OpenRouter.": "OpenRouter üzerinde çalışır.", "Runs on OpenRouter.": "OpenRouter üzerinde çalışır.",
"Runs on OpenCode Go.": "OpenCode Go üzerinde çalışır.",
"Connection works. {count} audio models visible.": "Connection works. {count} audio models visible.":
"Bağlantı tamam. {count} ses modeli görünüyor.", "Bağlantı tamam. {count} ses modeli görünüyor.",
"Connection works. {count} models visible.":
"Bağlantı tamam. {count} model görünüyor.",
"Clean the transcript with a model": "Transkripti bir modelle temizle", "Clean the transcript with a model": "Transkripti bir modelle temizle",
"OpenRouter is the quickest and the only one that needs nothing installed. " "OpenRouter is the quickest and the only one that needs nothing installed. "
"Claude Code and Codex clean up on the subscription you already have, " "Claude Code and Codex clean up on the subscription you already have, "
@@ -235,6 +241,14 @@ TR = {
"Claude Code ile Codex, temizliği hâlihazırda ödediğin abonelik üzerinden " "Claude Code ile Codex, temizliği hâlihazırda ödediğin abonelik üzerinden "
"yapar, ikinci bir anahtar istemez; her biri bunun için bir oturum açtığından " "yapar, ikinci bir anahtar istemez; her biri bunun için bir oturum açtığından "
"birkaç saniye daha uzun sürer.", "birkaç saniye daha uzun sürer.",
"OpenRouter and OpenCode Go are the quickest and need nothing installed. "
"Claude Code and Codex clean up on the subscription you already have, "
"without a second key, and take a few seconds longer because each one opens "
"a session to do it.":
"En hızlıları OpenRouter ve OpenCode Go'dur; kurulu bir program "
"istemezler. Claude Code ile Codex, temizliği hâlihazırda ödediğin "
"abonelik üzerinden yapar, ikinci bir anahtar istemez; her biri bunun "
"için bir oturum açtığından birkaç saniye daha uzun sürer.",
"{binary} is not on your PATH, so cleanup would fail and the raw transcript " "{binary} is not on your PATH, so cleanup would fail and the raw transcript "
"would be pasted. Install it, or pick another one above.": "would be pasted. Install it, or pick another one above.":
"{binary} PATH'te değil; temizleme başarısız olur ve ham transkript " "{binary} PATH'te değil; temizleme başarısız olur ve ham transkript "
@@ -541,6 +555,17 @@ TR = {
"Yukarıdaki çalışma dizini ve izinler burada bir şey ifade etmez.", "Yukarıdaki çalışma dizini ve izinler burada bir şey ifade etmez.",
"Needs no program installed, only the OpenRouter key.": "Needs no program installed, only the OpenRouter key.":
"Kurulu bir programa değil, yalnızca OpenRouter anahtarına ihtiyaç duyar.", "Kurulu bir programa değil, yalnızca OpenRouter anahtarına ihtiyaç duyar.",
"A plain question and a plain answer, over the OpenCode Go key you already "
"have. It runs no commands, opens no files and reaches none of your "
"services, so it can tell you what the capital of Peru is but not what is "
"in your calendar. Working directory and permissions above mean nothing "
"here.":
"Elindeki OpenCode Go anahtarı üzerinden düz bir soru ve düz bir cevap. "
"Komut çalıştırmaz, dosya açmaz, servislerinin hiçbirine erişmez; yani "
"Peru'nun başkentini söyler ama takviminde ne olduğunu söyleyemez. "
"Yukarıdaki çalışma dizini ve izinler burada bir şey ifade etmez.",
"Needs no program installed, only an OpenCode Go key.":
"Kurulu bir programa değil, yalnızca bir OpenCode Go anahtarına ihtiyaç duyar.",
"{binary} is not on your PATH, so this cannot run yet. Install it, or pick " "{binary} is not on your PATH, so this cannot run yet. Install it, or pick "
"another one above.": "another one above.":
"{binary} PATH'te değil, dolayısıyla bu henüz çalışamaz. Kur ya da " "{binary} PATH'te değil, dolayısıyla bu henüz çalışamaz. Kur ya da "
+113 -17
View File
@@ -61,7 +61,8 @@ CLEANUP_MODELS = [
# model here takes a little longer and costs nothing, and the two CLIs the agent # model here takes a little longer and costs nothing, and the two CLIs the agent
# can run on open a whole session to do the smaller job. # can run on open a whole session to do the smaller job.
CLEANUP_PROVIDERS = [ CLEANUP_PROVIDERS = [
("OpenRouter", "openrouter"), ("This machine (llama.cpp)", "local"), ("OpenRouter", "openrouter"), ("OpenCode Go", "opencode"),
("This machine (llama.cpp)", "local"),
("Claude Code", "claude"), ("Codex", "codex"), ("Claude Code", "claude"), ("Codex", "codex"),
] ]
# Cleaning up a sentence is the lightest thing either of them will ever be # Cleaning up a sentence is the lightest thing either of them will ever be
@@ -74,7 +75,8 @@ MEETING_MODELS = [
"anthropic/claude-sonnet-5", "openai/gpt-5.4", "x-ai/grok-4.5", "anthropic/claude-sonnet-5", "openai/gpt-5.4", "x-ai/grok-4.5",
] ]
ASSISTANT_PROVIDERS = [ ASSISTANT_PROVIDERS = [
("Claude Code", "claude"), ("Codex", "codex"), ("OpenRouter", "openrouter"), ("Claude Code", "claude"), ("Codex", "codex"),
("OpenRouter", "openrouter"), ("OpenCode Go", "opencode"),
] ]
# Aliases resolve to the newest model of that name, so they age better than an # Aliases resolve to the newest model of that name, so they age better than an
# id does; a full id can be typed in when a particular one is wanted. # id does; a full id can be typed in when a particular one is wanted.
@@ -85,6 +87,14 @@ ASSISTANT_OR_MODELS = [
"google/gemini-3.5-flash", "anthropic/claude-sonnet-5", "openai/gpt-5.4", "google/gemini-3.5-flash", "anthropic/claude-sonnet-5", "openai/gpt-5.4",
"x-ai/grok-4.5", "google/gemini-3.1-pro-preview", "x-ai/grok-4.5", "google/gemini-3.1-pro-preview",
] ]
# The models OpenCode Go serves over /chat/completions. The ones its catalog
# lists under /responses or /messages (Grok, GPT-5.6 Luna, MiniMax, Qwen) are
# not offered here, because Dikte speaks only the chat endpoint.
OPENCODE_MODELS = [
"deepseek-v4-flash", "deepseek-v4-pro", "glm-5.3", "glm-5.2", "glm-5.1",
"kimi-k3", "kimi-k2.7-code", "kimi-k2.6", "longcat-2.0",
"mimo-v2.5", "mimo-v2.5-pro", "hy3", "ox-alpha-free",
]
# What Claude Code may do without being able to ask. It cannot ask: there is no # What Claude Code may do without being able to ask. It cannot ask: there is no
# window to answer in, so a mode that would have prompted denies instead. # window to answer in, so a mode that would have prompted denies instead.
PERMISSION_MODES = [ PERMISSION_MODES = [
@@ -519,7 +529,7 @@ class SettingsWindow(QDialog):
# hears about it from here rather than waiting for its own next check. # hears about it from here rather than waiting for its own next check.
update_found = pyqtSignal(object) update_found = pyqtSignal(object)
_models_loaded = pyqtSignal(list, str) _models_loaded = pyqtSignal(list, str, str)
_transcribe_models_loaded = pyqtSignal(list, str) _transcribe_models_loaded = pyqtSignal(list, str)
# Which key was tested, whether it worked, and what to write under it. # Which key was tested, whether it worked, and what to write under it.
_test_done = pyqtSignal(str, bool, str) _test_done = pyqtSignal(str, bool, str)
@@ -746,6 +756,9 @@ class SettingsWindow(QDialog):
self.openrouter_key = self._key_row( self.openrouter_key = self._key_row(
keys_form, "openrouter", t("sk-or-… (falls back to OPENROUTER_API_KEY)"), keys_form, "openrouter", t("sk-or-… (falls back to OPENROUTER_API_KEY)"),
self._test_openrouter) self._test_openrouter)
self.opencode_key = self._key_row(
keys_form, "opencode", t("(falls back to OPENCODE_API_KEY)"),
self._test_opencode, service="OpenCode Go")
outer.addWidget(keys) outer.addWidget(keys)
stt = QGroupBox(t("Speech to text")) stt = QGroupBox(t("Speech to text"))
@@ -818,7 +831,7 @@ class SettingsWindow(QDialog):
for label, value in CLEANUP_PROVIDERS: for label, value in CLEANUP_PROVIDERS:
self.cleanup_provider.addItem(t(label), value) self.cleanup_provider.addItem(t(label), value)
self.cleanup_provider.setToolTip(t( self.cleanup_provider.setToolTip(t(
"OpenRouter is the quickest and the only one that needs nothing " "OpenRouter and OpenCode Go are the quickest and need nothing "
"installed. llama.cpp runs here, on a model downloaded below. Claude " "installed. llama.cpp runs here, on a model downloaded below. Claude "
"Code and Codex clean up on the subscription you already have, " "Code and Codex clean up on the subscription you already have, "
"without a second key, and take a few seconds longer because each " "without a second key, and take a few seconds longer because each "
@@ -848,6 +861,11 @@ class SettingsWindow(QDialog):
self.cleanup_codex_model.addItems([t("Codex's own default")] + CODEX_MODELS) self.cleanup_codex_model.addItems([t("Codex's own default")] + CODEX_MODELS)
orr_form.addRow(t("Model"), self.cleanup_codex_model) orr_form.addRow(t("Model"), self.cleanup_codex_model)
self.cleanup_opencode_model = QComboBox()
self.cleanup_opencode_model.setEditable(True)
self.cleanup_opencode_model.addItems(OPENCODE_MODELS)
orr_form.addRow(t("Model"), self.cleanup_opencode_model)
self.cleanup_reasoning = QComboBox() self.cleanup_reasoning = QComboBox()
for label, value in REASONING_LEVELS: for label, value in REASONING_LEVELS:
self.cleanup_reasoning.addItem(t(label), value) self.cleanup_reasoning.addItem(t(label), value)
@@ -1044,6 +1062,23 @@ class SettingsWindow(QDialog):
or_form.addRow(or_note) or_form.addRow(or_note)
layout.addWidget(self.openrouter_box) layout.addWidget(self.openrouter_box)
self.opencode_box = QGroupBox("OpenCode Go")
og_form = QFormLayout(self.opencode_box)
self.assistant_opencode_model = QComboBox()
self.assistant_opencode_model.setEditable(True)
self.assistant_opencode_model.addItems(OPENCODE_MODELS)
og_form.addRow(t("Model"), self.assistant_opencode_model)
og_note = QLabel(t(
"A plain question and a plain answer, over the OpenCode Go key you "
"already have. It runs no commands, opens no files and reaches none "
"of your services, so it can tell you what the capital of Peru is "
"but not what is in your calendar. Working directory and permissions "
"above mean nothing here."
))
og_note.setWordWrap(True)
og_form.addRow(og_note)
layout.addWidget(self.opencode_box)
thread = QGroupBox(t("The conversation")) thread = QGroupBox(t("The conversation"))
thread_form = QFormLayout(thread) thread_form = QFormLayout(thread)
self.assistant_session_minutes = QSpinBox() self.assistant_session_minutes = QSpinBox()
@@ -1515,7 +1550,7 @@ class SettingsWindow(QDialog):
box.lineEdit().setPlaceholderText(placeholder) box.lineEdit().setPlaceholderText(placeholder)
return box return box
def _key_row(self, form, provider, placeholder, tester): def _key_row(self, form, provider, placeholder, tester, service=""):
"""A key field, its Test button and the line the answer lands on. """A key field, its Test button and the line the answer lands on.
The field and the pair the answer needs are filed under the provider's The field and the pair the answer needs are filed under the provider's
@@ -1529,7 +1564,8 @@ class SettingsWindow(QDialog):
button.clicked.connect(tester) button.clicked.connect(tester)
answer = QLabel("") answer = QLabel("")
answer.setWordWrap(True) answer.setWordWrap(True)
form.addRow(cfg.TRANSCRIBERS[provider].service, self._row(field, button)) form.addRow(service or cfg.TRANSCRIBERS[provider].service,
self._row(field, button))
form.addRow("", answer) form.addRow("", answer)
self._key_fields[provider] = field self._key_fields[provider] = field
self._testers[provider] = (button, answer) self._testers[provider] = (button, answer)
@@ -1603,6 +1639,7 @@ class SettingsWindow(QDialog):
for name, who in cfg.TRANSCRIBERS.items(): for name, who in cfg.TRANSCRIBERS.items():
self._key_fields[name].setText(conf[who.key]) self._key_fields[name].setText(conf[who.key])
self._models[name] = conf[who.model] self._models[name] = conf[who.model]
self._key_fields["opencode"].setText(conf["opencode_api_key"])
self._shown_provider = "" self._shown_provider = ""
self._select_data(self.transcribe_provider, conf["transcribe_provider"]) self._select_data(self.transcribe_provider, conf["transcribe_provider"])
self._provider_changed() # selecting index 0 fires no signal self._provider_changed() # selecting index 0 fires no signal
@@ -1617,6 +1654,7 @@ class SettingsWindow(QDialog):
self.cleanup_codex_model.setCurrentText( self.cleanup_codex_model.setCurrentText(
conf["cleanup_codex_model"] or t("Codex's own default") conf["cleanup_codex_model"] or t("Codex's own default")
) )
self.cleanup_opencode_model.setCurrentText(conf["cleanup_opencode_model"])
self._select_data(self.cleanup_provider, conf["cleanup_provider"]) self._select_data(self.cleanup_provider, conf["cleanup_provider"])
self._cleanup_provider_changed() # selecting index 0 fires no signal self._cleanup_provider_changed() # selecting index 0 fires no signal
self._select_data(self.cleanup_reasoning, conf["cleanup_reasoning"]) self._select_data(self.cleanup_reasoning, conf["cleanup_reasoning"])
@@ -1636,6 +1674,7 @@ class SettingsWindow(QDialog):
self.assistant_codex_model.setCurrentText(conf["assistant_codex_model"]) self.assistant_codex_model.setCurrentText(conf["assistant_codex_model"])
self._select_data(self.assistant_codex_sandbox, conf["assistant_codex_sandbox"]) self._select_data(self.assistant_codex_sandbox, conf["assistant_codex_sandbox"])
self.assistant_openrouter_model.setCurrentText(conf["assistant_openrouter_model"]) self.assistant_openrouter_model.setCurrentText(conf["assistant_openrouter_model"])
self.assistant_opencode_model.setCurrentText(conf["assistant_opencode_model"])
self._assistant_provider_changed() # selecting index 0 fires no signal self._assistant_provider_changed() # selecting index 0 fires no signal
self._select_data(self.assistant_reasoning, conf["assistant_reasoning"]) self._select_data(self.assistant_reasoning, conf["assistant_reasoning"])
self.assistant_dir.setText(conf["assistant_dir"]) self.assistant_dir.setText(conf["assistant_dir"])
@@ -1701,6 +1740,7 @@ class SettingsWindow(QDialog):
for name, who in cfg.TRANSCRIBERS.items(): for name, who in cfg.TRANSCRIBERS.items():
conf[who.key] = self._key_fields[name].text().strip() conf[who.key] = self._key_fields[name].text().strip()
conf[who.model] = self._models[name].strip() or cfg.DEFAULTS[who.model] conf[who.model] = self._models[name].strip() or cfg.DEFAULTS[who.model]
conf["opencode_api_key"] = self._key_fields["opencode"].text().strip()
conf["local_model"] = self.local_whisper.selected() conf["local_model"] = self.local_whisper.selected()
conf["local_gpu"] = self.local_gpu.isChecked() conf["local_gpu"] = self.local_gpu.isChecked()
conf["local_preload"] = self.local_preload.isChecked() conf["local_preload"] = self.local_preload.isChecked()
@@ -1715,6 +1755,10 @@ class SettingsWindow(QDialog):
conf["cleanup_codex_model"] = ( conf["cleanup_codex_model"] = (
"" if codex_cleanup_model == t("Codex's own default") else codex_cleanup_model "" if codex_cleanup_model == t("Codex's own default") else codex_cleanup_model
) )
conf["cleanup_opencode_model"] = (
self.cleanup_opencode_model.currentText().strip()
or cfg.DEFAULTS["cleanup_opencode_model"]
)
conf["cleanup_reasoning"] = self.cleanup_reasoning.currentData() or "" conf["cleanup_reasoning"] = self.cleanup_reasoning.currentData() or ""
conf["local_llm_model"] = self.local_llm.selected() conf["local_llm_model"] = self.local_llm.selected()
conf["local_llm_repo"] = self.local_llm.repository() conf["local_llm_repo"] = self.local_llm.repository()
@@ -1748,6 +1792,10 @@ class SettingsWindow(QDialog):
self.assistant_openrouter_model.currentText().strip() self.assistant_openrouter_model.currentText().strip()
or cfg.DEFAULTS["assistant_openrouter_model"] or cfg.DEFAULTS["assistant_openrouter_model"]
) )
conf["assistant_opencode_model"] = (
self.assistant_opencode_model.currentText().strip()
or cfg.DEFAULTS["assistant_opencode_model"]
)
conf["assistant_reasoning"] = self.assistant_reasoning.currentData() or "" conf["assistant_reasoning"] = self.assistant_reasoning.currentData() or ""
conf["assistant_dir"] = self.assistant_dir.text().strip() conf["assistant_dir"] = self.assistant_dir.text().strip()
conf["assistant_timeout"] = self.assistant_timeout.value() conf["assistant_timeout"] = self.assistant_timeout.value()
@@ -1854,26 +1902,52 @@ class SettingsWindow(QDialog):
def _load_models(self): def _load_models(self):
self.refresh_models.setEnabled(False) self.refresh_models.setEnabled(False)
self.models_label.setText(t("Fetching model list…")) self.models_label.setText(t("Fetching model list…"))
# Whichever provider is selected for cleanup is the one whose models are
# fetched, so the list lands in the box of the provider on screen.
provider = self.cleanup_provider.currentData() or "openrouter"
if provider == "opencode":
key = (self.opencode_key.text().strip() or self.conf.opencode_key())
base = self.conf["opencode_base_url"]
def work():
try:
self._models_loaded.emit(
api.openai_models(key, base, "OpenCode Go"), "", provider)
except api.ApiError as exc:
self._models_loaded.emit([], str(exc), provider)
threading.Thread(target=work, daemon=True).start()
return
key = self.openrouter_key.text().strip() or self.conf.openrouter_key() key = self.openrouter_key.text().strip() or self.conf.openrouter_key()
def work(): def work():
try: try:
self._models_loaded.emit(api.openrouter_models(key), "") self._models_loaded.emit(api.openrouter_models(key), "", provider)
except api.ApiError as exc: except api.ApiError as exc:
self._models_loaded.emit([], str(exc)) self._models_loaded.emit([], str(exc), provider)
threading.Thread(target=work, daemon=True).start() threading.Thread(target=work, daemon=True).start()
def _on_models_loaded(self, models, error): def _on_models_loaded(self, models, error, provider):
self.refresh_models.setEnabled(True) self.refresh_models.setEnabled(True)
if error: if error:
self.models_label.setText(t("Could not fetch the list: {error}", error=error)) self.models_label.setText(t("Could not fetch the list: {error}", error=error))
return return
for combo in (self.cleanup_model, self.meeting_model): if provider == "opencode":
current = combo.currentText() combo = self.cleanup_opencode_model
combo.clear() else:
combo.addItems(models) combo = self.cleanup_model
combo.setCurrentText(current) current = combo.currentText()
combo.clear()
combo.addItems(models)
combo.setCurrentText(current)
if provider == "openrouter":
# The minutes summary runs on the same key, so the meeting box is
# filled from the same list.
current = self.meeting_model.currentText()
self.meeting_model.clear()
self.meeting_model.addItems(models)
self.meeting_model.setCurrentText(current)
self.models_label.setText(t("{count} models loaded.", count=len(models))) self.models_label.setText(t("{count} models loaded.", count=len(models)))
def _test_openai(self): def _test_openai(self):
@@ -1894,8 +1968,20 @@ class SettingsWindow(QDialog):
key, _ = self._typed_key("openrouter") key, _ = self._typed_key("openrouter")
self._test_key("openrouter", lambda: api.openrouter_key_status(key)) self._test_key("openrouter", lambda: api.openrouter_key_status(key))
def _test_opencode(self):
key, base = self._typed_key("opencode")
self._test_key("opencode", lambda: t(
"Connection works. {count} models visible.",
count=len(api.openai_models(key, base, "OpenCode Go")),
))
def _typed_key(self, provider): def _typed_key(self, provider):
"""(key, base URL) for a provider, preferring what is in the field now.""" """(key, base URL) for a provider, preferring what is in the field now."""
if provider == "opencode":
# The one key that is not in the TRANSCRIBERS table: it pays for
# cleanup and the agent rather than for speech to text.
typed = self._key_fields["opencode"].text().strip()
return typed or self.conf.opencode_key(), self.conf["opencode_base_url"]
who = cfg.TRANSCRIBERS[provider] who = cfg.TRANSCRIBERS[provider]
typed = self._key_fields[provider].text().strip() typed = self._key_fields[provider].text().strip()
return typed or self.conf.api_key(who.key), self.conf[who.url] return typed or self.conf.api_key(who.key), self.conf[who.url]
@@ -2118,6 +2204,8 @@ class SettingsWindow(QDialog):
provider == "claude") provider == "claude")
self.cleanup_form.setRowVisible(self.cleanup_codex_model, self.cleanup_form.setRowVisible(self.cleanup_codex_model,
provider == "codex") provider == "codex")
self.cleanup_form.setRowVisible(self.cleanup_opencode_model,
provider == "opencode")
self.cleanup_form.setRowVisible(self.cleanup_reasoning, self.cleanup_form.setRowVisible(self.cleanup_reasoning,
provider != "local") provider != "local")
self.cleanup_form.setRowVisible(self.local_llm, provider == "local") self.cleanup_form.setRowVisible(self.local_llm, provider == "local")
@@ -2126,6 +2214,8 @@ class SettingsWindow(QDialog):
found = shutil.which(binary) if binary else "" found = shutil.which(binary) if binary else ""
if provider == "local": if provider == "local":
self.models_label.setText(t("Runs on this machine, on llama.cpp.")) self.models_label.setText(t("Runs on this machine, on llama.cpp."))
elif provider == "opencode":
self.models_label.setText(t("Runs on OpenCode Go."))
elif not binary: elif not binary:
self.models_label.setText(t("Runs on OpenRouter.")) self.models_label.setText(t("Runs on OpenRouter."))
elif found: elif found:
@@ -2142,6 +2232,7 @@ class SettingsWindow(QDialog):
self.claude_box.setVisible(provider == "claude") self.claude_box.setVisible(provider == "claude")
self.codex_box.setVisible(provider == "codex") self.codex_box.setVisible(provider == "codex")
self.openrouter_box.setVisible(provider == "openrouter") self.openrouter_box.setVisible(provider == "openrouter")
self.opencode_box.setVisible(provider == "opencode")
self._refresh_assistant_status() self._refresh_assistant_status()
def _refresh_assistant_status(self): def _refresh_assistant_status(self):
@@ -2149,9 +2240,14 @@ class SettingsWindow(QDialog):
binary = assistant.executable(provider) binary = assistant.executable(provider)
found = shutil.which(binary) if binary else "" found = shutil.which(binary) if binary else ""
if not binary: if not binary:
self.assistant_found.setText( if provider == "opencode":
t("Needs no program installed, only the OpenRouter key.") self.assistant_found.setText(
) t("Needs no program installed, only an OpenCode Go key.")
)
else:
self.assistant_found.setText(
t("Needs no program installed, only the OpenRouter key.")
)
elif found: elif found:
self.assistant_found.setText(t("Found: {path}", path=found)) self.assistant_found.setText(t("Found: {path}", path=found))
else: else:
+1
View File
@@ -244,6 +244,7 @@ class Settings(DikteTest):
"""An OpenRouter id and a Claude alias are not the same field.""" """An OpenRouter id and a Claude alias are not the same field."""
window = self.window(cfg.Config()) window = self.window(cfg.Config())
boxes = {"openrouter": window.cleanup_model_row, boxes = {"openrouter": window.cleanup_model_row,
"opencode": window.cleanup_opencode_model,
"claude": window.cleanup_claude_model, "claude": window.cleanup_claude_model,
"codex": window.cleanup_codex_model} "codex": window.cleanup_codex_model}
for provider, box in boxes.items(): for provider, box in boxes.items():