awesome-templates
opencode/commands.py
无中文版
opencode/commands.py
"""opencode — OpenCode coding agent command.
The named command runs the real CLI (``opencode`` from npm package
``opencode-ai``). The prompt is copied into the child environment as
``OPENCODE_PROMPT`` and passed as one argv element. It is not interpolated
into a shell string.
``OPENCODE_API_KEY`` is the credential the OpenCode Zen provider reads
(provider id ``opencode``). This module does not call the CLI at import or
at server start. The CLI runs only when a caller invokes the ``opencode``
command. An empty key does not skip that call: the documented free model
may answer, and a failure that asks for a key raises ``RuntimeError``
naming ``OPENCODE_API_KEY``.
"""
from __future__ import annotations
import os
import subprocess
from agent_sandbox.server import command, start
# Verified in opencode-ai 1.19.0 and on https://opencode.ai/docs/zen/ :
# provider ``opencode`` lists model id ``big-pickle`` at cost 0.
# Selector form is provider/model. OpenSandbox's older default
# ``opencode/deepseek-v4-flash-free`` is a finished promotion.
DEFAULT_MODEL = "opencode/big-pickle"
PROMPT_ENV = "OPENCODE_PROMPT"
_KEY_MARKERS = (
"opencode_api_key",
"api key",
"api_key",
"apikey",
"no credentials",
"missing credential",
"unauthorized",
"authentication",
"not authenticated",
"auth required",
"invalid key",
)
def _resolved_model(model: str) -> str:
"""Command argument, then ``OPENCODE_MODEL``, then the documented free model."""
chosen = (model or os.environ.get("OPENCODE_MODEL") or DEFAULT_MODEL).strip()
return chosen or DEFAULT_MODEL
def _key_set() -> bool:
return bool(os.environ.get("OPENCODE_API_KEY", "").strip())
def _needs_api_key(detail: str) -> bool:
lowered = detail.lower()
return any(marker in lowered for marker in _KEY_MARKERS)
@command("opencode", description="Run OpenCode non-interactively (opencode run).")
def opencode(prompt: str, model: str = "") -> str:
"""Run ``opencode run --format json --auto --model <model> <prompt>``.
``<prompt>`` is the value of ``OPENCODE_PROMPT`` in the child process.
``<model>`` is *model*, else ``OPENCODE_MODEL``, else ``opencode/big-pickle``.
"""
text = prompt.strip()
if not text:
raise ValueError("prompt is required")
chosen = _resolved_model(model)
env = os.environ.copy()
env[PROMPT_ENV] = text
env["OPENCODE_MODEL"] = chosen
env.setdefault("OPENCODE_DISABLE_AUTOUPDATE", "true")
if not env.get("OPENCODE_API_KEY", "").strip():
env.pop("OPENCODE_API_KEY", None)
# Argv, not a shell. The prompt is read back from the environment.
argv = [
"opencode",
"run",
"--format",
"json",
"--auto",
"--model",
chosen,
env[PROMPT_ENV],
]
workdir = "/app" if os.path.isdir("/app") else None
try:
result = subprocess.run(
argv,
cwd=workdir,
env=env,
stdin=subprocess.DEVNULL,
capture_output=True,
text=True,
timeout=600,
check=False,
)
except FileNotFoundError as exc:
raise RuntimeError("opencode CLI was not found on PATH") from exc
except subprocess.TimeoutExpired as exc:
raise RuntimeError("opencode run timed out after 600 seconds") from exc
if result.returncode != 0:
detail = (result.stderr or result.stdout or "").strip()
if not _key_set() and _needs_api_key(detail):
message = "OPENCODE_API_KEY is required for this OpenCode model"
if detail:
message = f"{message}: {detail}"
raise RuntimeError(message)
raise RuntimeError(detail or f"opencode exited {result.returncode}")
return result.stdout
if __name__ == "__main__":
start(int(os.environ.get("ABX_SERVER_PORT", "9000")))