mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 19:06:11 +00:00
Add OpenCode Go as a cleanup and agent provider
OpenCode Go serves open coding models from an OpenAI-compatible endpoint. It cannot transcribe, so it joins the two LLM jobs: transcript cleanup and the plain-chat agent. The key falls back to OPENCODE_API_KEY, and api.chat() stops hardcoding the OpenRouter name so the same call serves both.
This commit is contained in:
+32
-23
@@ -1,16 +1,17 @@
|
||||
"""Handing a dictation to an agent as a command, and pasting back its answer.
|
||||
|
||||
Three of them, because not everyone has the same one installed:
|
||||
Four of them, because not everyone has the same one installed:
|
||||
|
||||
Claude Code `claude -p`, the session you would have opened yourself
|
||||
Codex `codex exec`, the same idea from the other shop
|
||||
OpenRouter a plain chat request, over the key that is already configured
|
||||
OpenCode Go a plain chat request, over a subscription to open coding models
|
||||
|
||||
The first two are the whole machine: they run commands, read files, and reach
|
||||
whatever skills and services you have connected, which is what makes "put that
|
||||
in my calendar on Thursday" a thing you can say. OpenRouter cannot touch any of
|
||||
that, and is there so that a question still gets an answer on a machine with
|
||||
neither CLI installed.
|
||||
in my calendar on Thursday" a thing you can say. The two chat requests cannot
|
||||
touch any of that, and are there so that a question still gets an answer on a
|
||||
machine with neither CLI installed.
|
||||
|
||||
Whichever it is, the reply is pasted exactly where the transcript would have
|
||||
been, and the conversation carries across dictations so that "and move that to
|
||||
@@ -34,11 +35,11 @@ from . import config as cfg
|
||||
from .i18n import t
|
||||
|
||||
SESSION_FILE = cfg.DATA_DIR / "assistant.json"
|
||||
PROVIDERS = ("claude", "codex", "openrouter")
|
||||
PROVIDERS = ("claude", "codex", "openrouter", "opencode")
|
||||
|
||||
# How many messages of an OpenRouter conversation are carried forward. The two
|
||||
# CLIs keep their own history and need no such number; here every turn is resent
|
||||
# in full, so the window has to end somewhere.
|
||||
# How many messages of a chat provider's conversation are carried forward. The
|
||||
# two CLIs keep their own history and need no such number; here every turn is
|
||||
# resent in full, so the window has to end somewhere.
|
||||
MAX_HISTORY = 24
|
||||
|
||||
# What to say in the indicator for a tool, keyed by the name the CLI uses.
|
||||
@@ -103,7 +104,9 @@ def executable(name):
|
||||
|
||||
def display_name(conf):
|
||||
"""What to call the thing being asked, in the tray and in the corner."""
|
||||
return {"claude": "Claude", "codex": "Codex"}.get(provider(conf), "OpenRouter")
|
||||
names = {"claude": "Claude", "codex": "Codex",
|
||||
"openrouter": "OpenRouter", "opencode": "OpenCode Go"}
|
||||
return names.get(provider(conf), "OpenRouter")
|
||||
|
||||
|
||||
# --- the conversation -----------------------------------------------------
|
||||
@@ -196,8 +199,9 @@ def ask(prompt, conf, on_stage=None, should_stop=None):
|
||||
one, and only the denial explains why it did not do what it was asked to.
|
||||
"""
|
||||
name = provider(conf)
|
||||
if name == "openrouter":
|
||||
return _ask_openrouter(prompt, conf, on_stage)
|
||||
if name in ("openrouter", "opencode"):
|
||||
service = "OpenRouter" if name == "openrouter" else "OpenCode Go"
|
||||
return _ask_chat(name, service, prompt, conf, on_stage)
|
||||
|
||||
binary = executable(name)
|
||||
if not shutil.which(binary):
|
||||
@@ -338,29 +342,34 @@ def _codex_label(item):
|
||||
return t("Using {name}…", name=item_type or "a tool")
|
||||
|
||||
|
||||
# --- OpenRouter -----------------------------------------------------------
|
||||
# --- OpenRouter and OpenCode Go -------------------------------------------
|
||||
|
||||
def _ask_openrouter(prompt, conf, on_stage):
|
||||
"""No tools, no files, no calendar: a question and an answer.
|
||||
def _ask_chat(name, service, prompt, conf, on_stage):
|
||||
"""A plain question and answer, over a chat provider's key.
|
||||
|
||||
It is the fallback for a machine with neither CLI on it, so it says what it
|
||||
knows and nothing else. The conversation is ours to keep here, since there
|
||||
is no session on the other end to resume.
|
||||
No tools, no files, no calendar. It is the fallback for a machine with
|
||||
neither CLI on it, so it says what it knows and nothing else. The
|
||||
conversation is ours to keep here, since there is no session on the other
|
||||
end to resume.
|
||||
"""
|
||||
if on_stage:
|
||||
on_stage(t("Thinking…"))
|
||||
history = read_messages("openrouter", conf["assistant_session_minutes"] * 60)
|
||||
history = read_messages(name, conf["assistant_session_minutes"] * 60)
|
||||
messages = history + [{"role": "user", "content": prompt}]
|
||||
model = (conf["assistant_openrouter_model"] if name == "openrouter"
|
||||
else conf["assistant_opencode_model"])
|
||||
base_url = (conf["openrouter_base_url"] if name == "openrouter"
|
||||
else conf["opencode_base_url"])
|
||||
key = conf.openrouter_key() if name == "openrouter" else conf.opencode_key()
|
||||
try:
|
||||
answer = api.chat(
|
||||
messages, conf.openrouter_key(), conf["assistant_openrouter_model"],
|
||||
conf.assistant_prompt(), reasoning=conf["assistant_reasoning"],
|
||||
base_url=conf["openrouter_base_url"],
|
||||
timeout=conf["assistant_timeout"],
|
||||
messages, key, model, conf.assistant_prompt(),
|
||||
reasoning=conf["assistant_reasoning"], base_url=base_url,
|
||||
timeout=conf["assistant_timeout"], provider=name, service=service,
|
||||
)
|
||||
except api.ApiError as exc:
|
||||
raise AssistantError(str(exc)) from exc
|
||||
write_session("openrouter",
|
||||
write_session(name,
|
||||
messages=messages + [{"role": "assistant", "content": answer}])
|
||||
return answer, ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user