72 lines
2.8 KiB
Python
72 lines
2.8 KiB
Python
"""Gemini CLI backend."""
|
|||
|
|
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import shutil
|
||
|
|
|
||
|
|
from tai_pkg.clirun import Cli
|
||
|
|
from tai_pkg.errors import BackendError
|
||
|
|
from tai_pkg.opencode import Opencode
|
||
|
|
|
||
|
|
__all__ = ["Gemini"]
|
||
|
|
|
||
|
|
|
||
|
|
class Gemini:
|
||
|
|
"""Gemini."""
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def gemini_binary():
|
||
|
|
return os.environ.get("GEMINI_BIN") or shutil.which("gemini")
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def gemini_parse_output(stdout):
|
||
|
|
try:
|
||
|
|
data = json.loads(stdout or "")
|
||
|
|
except ValueError:
|
||
|
|
return Opencode.opencode_parse(stdout)
|
||
|
|
if not isinstance(data, dict):
|
||
|
|
return Opencode.opencode_parse(stdout)
|
||
|
|
err = data.get("error")
|
||
|
|
if err:
|
||
|
|
raise BackendError("gemini: %s" % (json.dumps(err) if isinstance(err, dict) else str(err))[:300])
|
||
|
|
response = data.get("response")
|
||
|
|
if isinstance(response, str) and response.strip():
|
||
|
|
return Opencode.opencode_parse(response)
|
||
|
|
return Opencode.opencode_parse(stdout)
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def gemini_run(prompt, timeout):
|
||
|
|
binary = Gemini.gemini_binary()
|
||
|
|
if not binary:
|
||
|
|
raise BackendError("gemini CLI not found (GEMINI_BIN or PATH)")
|
||
|
|
base = [binary, "-p", prompt]
|
||
|
|
variants = [
|
||
|
|
base + ["--output-format", "json", "--approval-mode", "plan"],
|
||
|
|
base + ["--output-format", "json"],
|
||
|
|
base,
|
||
|
|
]
|
||
|
|
last_error = ""
|
||
|
|
for argv in variants:
|
||
|
|
done = Cli._cli_run(argv, None, timeout, "gemini")
|
||
|
|
if done.returncode != 0 and Cli._cli_unknown_flag(done.stderr) and argv is not variants[-1]:
|
||
|
|
last_error = (done.stderr or "").strip()[:200]
|
||
|
|
continue
|
||
|
|
if done.returncode != 0 and not (done.stdout or "").strip():
|
||
|
|
blob = ((done.stderr or "") + "\n" + (done.stdout or "")).strip()
|
||
|
|
if Cli._cli_auth_failure(blob):
|
||
|
|
raise BackendError("gemini not authenticated (run gemini login or set GEMINI_API_KEY): %s" % blob[:200])
|
||
|
|
hint = {42: "input error", 53: "turn limit exceeded"}.get(done.returncode, "")
|
||
|
|
raise BackendError("gemini exit %d%s: %s" % (done.returncode, " (%s)" % hint if hint else "", blob[:300] or last_error or "no output"))
|
||
|
|
if not (done.stdout or "").strip():
|
||
|
|
blob = (done.stderr or "").strip()
|
||
|
|
raise BackendError("gemini returned no output: %s" % (blob[:300] or last_error or "empty stdout"))
|
||
|
|
return done.stdout or ""
|
||
|
|
raise BackendError("gemini failed: %s" % (last_error or "no usable output"))
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def gemini_health():
|
||
|
|
binary = Gemini.gemini_binary()
|
||
|
|
if not binary:
|
||
|
|
return "missing: gemini CLI not found"
|
||
|
|
return Cli._cli_version_only_health(binary, "gemini")
|