mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Reach OpenCode Go from the command line
test-key accepts opencode and reports the model count its /models endpoint offers, ask --provider accepts it and maps --model to its own setting, doctor reports its key for cleanup on it, and config list masks the key like the other two.
This commit is contained in:
+18
-5
@@ -227,7 +227,8 @@ def cmd_ask(opts):
|
|||||||
conf["assistant_provider"] = opts.provider
|
conf["assistant_provider"] = opts.provider
|
||||||
if opts.model:
|
if opts.model:
|
||||||
key = {"claude": "assistant_model", "codex": "assistant_codex_model",
|
key = {"claude": "assistant_model", "codex": "assistant_codex_model",
|
||||||
"openrouter": "assistant_openrouter_model"}[assistant.provider(conf)]
|
"openrouter": "assistant_openrouter_model",
|
||||||
|
"opencode": "assistant_opencode_model"}[assistant.provider(conf)]
|
||||||
conf[key] = opts.model
|
conf[key] = opts.model
|
||||||
if opts.dir:
|
if opts.dir:
|
||||||
conf["assistant_dir"] = opts.dir
|
conf["assistant_dir"] = opts.dir
|
||||||
@@ -519,7 +520,7 @@ def cmd_history_clear(opts):
|
|||||||
|
|
||||||
# --- settings ---------------------------------------------------------------
|
# --- settings ---------------------------------------------------------------
|
||||||
|
|
||||||
SECRET_KEYS = ("openai_api_key", "openrouter_api_key")
|
SECRET_KEYS = ("openai_api_key", "openrouter_api_key", "opencode_api_key")
|
||||||
|
|
||||||
|
|
||||||
def _mask(key, value):
|
def _mask(key, value):
|
||||||
@@ -709,6 +710,14 @@ def cmd_test_key(opts):
|
|||||||
results[name] = {"ok": True, "message": message}
|
results[name] = {"ok": True, "message": message}
|
||||||
except api.ApiError as exc:
|
except api.ApiError as exc:
|
||||||
results[name] = {"ok": False, "message": str(exc)}
|
results[name] = {"ok": False, "message": str(exc)}
|
||||||
|
if opts.which in ("opencode", "all"):
|
||||||
|
try:
|
||||||
|
count = len(api.openai_models(conf.opencode_key(),
|
||||||
|
conf["opencode_base_url"], "OpenCode Go"))
|
||||||
|
results["opencode"] = {"ok": True,
|
||||||
|
"message": f"connection works, {count} models visible"}
|
||||||
|
except api.ApiError as exc:
|
||||||
|
results["opencode"] = {"ok": False, "message": str(exc)}
|
||||||
everything_ok = all(item["ok"] for item in results.values())
|
everything_ok = all(item["ok"] for item in results.values())
|
||||||
lines = [f"{'✓' if item['ok'] else '✗'} {name}: {item['message']}"
|
lines = [f"{'✓' if item['ok'] else '✗'} {name}: {item['message']}"
|
||||||
for name, item in results.items()]
|
for name, item in results.items()]
|
||||||
@@ -875,7 +884,9 @@ def cmd_doctor(opts):
|
|||||||
"key": bool(target.api_key)},
|
"key": bool(target.api_key)},
|
||||||
"cleanup": {"enabled": conf["cleanup_enabled"], "provider": cleaner,
|
"cleanup": {"enabled": conf["cleanup_enabled"], "provider": cleaner,
|
||||||
"model": cleanup.model(conf),
|
"model": cleanup.model(conf),
|
||||||
"key": bool(conf.openrouter_key())},
|
"key": (bool(conf.openrouter_key()) if cleaner == "openrouter"
|
||||||
|
else bool(conf.opencode_key())
|
||||||
|
if cleaner == "opencode" else None)},
|
||||||
"agent": {"provider": assistant.provider(conf),
|
"agent": {"provider": assistant.provider(conf),
|
||||||
"directory": assistant.working_dir(conf)},
|
"directory": assistant.working_dir(conf)},
|
||||||
"running": ipc.send("status") is not None,
|
"running": ipc.send("status") is not None,
|
||||||
@@ -888,6 +899,8 @@ def cmd_doctor(opts):
|
|||||||
# Cleanup on a CLI needs no key, so what is checked is the program.
|
# Cleanup on a CLI needs no key, so what is checked is the program.
|
||||||
(f"{'✓' if conf.openrouter_key() else '✗'} OpenRouter key, cleaning up on "
|
(f"{'✓' if conf.openrouter_key() else '✗'} OpenRouter key, cleaning up on "
|
||||||
f"{conf['cleanup_model']}") if cleaner == "openrouter" else
|
f"{conf['cleanup_model']}") if cleaner == "openrouter" else
|
||||||
|
(f"{'✓' if conf.opencode_key() else '✗'} OpenCode Go key, cleaning up on "
|
||||||
|
f"{conf['cleanup_opencode_model']}") if cleaner == "opencode" else
|
||||||
(f"{'✓' if programs[cleanup.executable(cleaner)] else '✗'} "
|
(f"{'✓' if programs[cleanup.executable(cleaner)] else '✗'} "
|
||||||
f"{cleanup.executable(cleaner)}, cleaning up on {cleanup.model(conf)}"),
|
f"{cleanup.executable(cleaner)}, cleaning up on {cleanup.model(conf)}"),
|
||||||
f"{'✓' if checks['running'] else '·'} application "
|
f"{'✓' if checks['running'] else '·'} application "
|
||||||
@@ -977,7 +990,7 @@ def build_parser():
|
|||||||
ask = leaf(subs, "ask", "put a command to the agent")
|
ask = leaf(subs, "ask", "put a command to the agent")
|
||||||
ask.add_argument("text", nargs="*", help="the command; read from stdin, or "
|
ask.add_argument("text", nargs="*", help="the command; read from stdin, or "
|
||||||
"recorded when there is none")
|
"recorded when there is none")
|
||||||
ask.add_argument("--provider", choices=("claude", "codex", "openrouter"),
|
ask.add_argument("--provider", choices=("claude", "codex", "openrouter", "opencode"),
|
||||||
help="just for this run")
|
help="just for this run")
|
||||||
ask.add_argument("--model", help="just for this run")
|
ask.add_argument("--model", help="just for this run")
|
||||||
ask.add_argument("--dir", help="working directory, just for this run")
|
ask.add_argument("--dir", help="working directory, just for this run")
|
||||||
@@ -1100,7 +1113,7 @@ def build_parser():
|
|||||||
models.set_defaults(func=cmd_models)
|
models.set_defaults(func=cmd_models)
|
||||||
test = leaf(subs, "test-key", "check the API keys")
|
test = leaf(subs, "test-key", "check the API keys")
|
||||||
test.add_argument("which", nargs="?", default="all",
|
test.add_argument("which", nargs="?", default="all",
|
||||||
choices=("all", *cfg.TRANSCRIBERS))
|
choices=("all", *cfg.TRANSCRIBERS, "opencode"))
|
||||||
test.set_defaults(func=cmd_test_key)
|
test.set_defaults(func=cmd_test_key)
|
||||||
leaf(subs, "doctor", "keys, programs, and what is missing").set_defaults(func=cmd_doctor)
|
leaf(subs, "doctor", "keys, programs, and what is missing").set_defaults(func=cmd_doctor)
|
||||||
|
|
||||||
|
|||||||
@@ -423,6 +423,16 @@ class Providers(DikteTest):
|
|||||||
self.assertIn("groq", out)
|
self.assertIn("groq", out)
|
||||||
self.assertIn("Groq", out)
|
self.assertIn("Groq", out)
|
||||||
|
|
||||||
|
def test_opencode_is_a_choice_and_reports_under_its_own_name(self):
|
||||||
|
parser = cli.build_parser()
|
||||||
|
self.assertEqual(
|
||||||
|
parser.parse_args(["test-key", "opencode"]).which, "opencode")
|
||||||
|
self.write_config({"opencode_api_key": "opencode-test"})
|
||||||
|
with fake_urlopen({"data": [{"id": "deepseek-v4-flash"}]}):
|
||||||
|
code, out, _ = self.run_cmd(cli.cmd_test_key, which="opencode")
|
||||||
|
self.assertEqual(code, 0)
|
||||||
|
self.assertIn("opencode: connection works, 1 models visible", out)
|
||||||
|
|
||||||
|
|
||||||
class Updates(DikteTest):
|
class Updates(DikteTest):
|
||||||
"""`dikte update` looks, says what it found, and installs nothing."""
|
"""`dikte update` looks, says what it found, and installs nothing."""
|
||||||
@@ -503,6 +513,15 @@ class Doctor(DikteTest):
|
|||||||
self.assertIn("OpenRouter key, cleaning up on some/model",
|
self.assertIn("OpenRouter key, cleaning up on some/model",
|
||||||
self.run_doctor(as_json=False, cleanup_model="some/model"))
|
self.run_doctor(as_json=False, cleanup_model="some/model"))
|
||||||
|
|
||||||
|
def test_cleanup_on_opencode_is_a_question_about_its_own_key(self):
|
||||||
|
reply = self.run_doctor(cleanup_provider="opencode",
|
||||||
|
cleanup_opencode_model="glm-5.3")
|
||||||
|
self.assertEqual(reply["cleanup"]["provider"], "opencode")
|
||||||
|
self.assertEqual(reply["cleanup"]["model"], "glm-5.3")
|
||||||
|
self.assertIn("OpenCode Go key, cleaning up on glm-5.3",
|
||||||
|
self.run_doctor(as_json=False, cleanup_provider="opencode",
|
||||||
|
cleanup_opencode_model="glm-5.3"))
|
||||||
|
|
||||||
def test_it_asks_after_the_programs_this_desktop_actually_uses(self):
|
def test_it_asks_after_the_programs_this_desktop_actually_uses(self):
|
||||||
"""A missing ydotool on a Mac is a red mark with nothing behind it."""
|
"""A missing ydotool on a Mac is a red mark with nothing behind it."""
|
||||||
with mock.patch.object(cli.paste, "desktop", return_value=paste.MACOS):
|
with mock.patch.object(cli.paste, "desktop", return_value=paste.MACOS):
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ class Keys(DikteTest):
|
|||||||
def test_every_provider_falls_back_to_the_variable_of_its_own_name(self):
|
def test_every_provider_falls_back_to_the_variable_of_its_own_name(self):
|
||||||
with mock.patch.dict(os.environ, {"GROQ_API_KEY": "gsk-env"}):
|
with mock.patch.dict(os.environ, {"GROQ_API_KEY": "gsk-env"}):
|
||||||
self.assertEqual(cfg.Config().groq_key(), "gsk-env")
|
self.assertEqual(cfg.Config().groq_key(), "gsk-env")
|
||||||
|
with mock.patch.dict(os.environ, {"OPENCODE_API_KEY": "opencode-env"}):
|
||||||
|
self.assertEqual(cfg.Config().opencode_key(), "opencode-env")
|
||||||
|
|
||||||
|
|
||||||
class TranscribeTarget(DikteTest):
|
class TranscribeTarget(DikteTest):
|
||||||
@@ -449,6 +451,13 @@ class Defaults(unittest.TestCase):
|
|||||||
def test_the_keys_ship_empty(self):
|
def test_the_keys_ship_empty(self):
|
||||||
self.assertEqual(cfg.DEFAULTS["openai_api_key"], "")
|
self.assertEqual(cfg.DEFAULTS["openai_api_key"], "")
|
||||||
self.assertEqual(cfg.DEFAULTS["openrouter_api_key"], "")
|
self.assertEqual(cfg.DEFAULTS["openrouter_api_key"], "")
|
||||||
|
self.assertEqual(cfg.DEFAULTS["opencode_api_key"], "")
|
||||||
|
|
||||||
|
def test_opencode_ships_on_its_own_endpoint(self):
|
||||||
|
self.assertEqual(cfg.DEFAULTS["opencode_base_url"],
|
||||||
|
"https://opencode.ai/zen/go/v1")
|
||||||
|
self.assertEqual(cfg.DEFAULTS["cleanup_opencode_model"], "deepseek-v4-flash")
|
||||||
|
self.assertEqual(cfg.DEFAULTS["assistant_opencode_model"], "deepseek-v4-flash")
|
||||||
|
|
||||||
def test_every_language_specific_prompt_has_both_languages(self):
|
def test_every_language_specific_prompt_has_both_languages(self):
|
||||||
for name in ("CLEANUP_PROMPT", "FILE_CLEANUP_PROMPT", "MEETING_PROMPT",
|
for name in ("CLEANUP_PROMPT", "FILE_CLEANUP_PROMPT", "MEETING_PROMPT",
|
||||||
|
|||||||
Reference in New Issue
Block a user