Support local OpenCode Sol provider
This commit is contained in:
parent
d123659bd2
commit
e11bcd1e59
2 changed files with 50 additions and 29 deletions
|
|
@ -27,7 +27,12 @@ class Command(BaseCommand):
|
||||||
"provider": "opencode",
|
"provider": "opencode",
|
||||||
"compute": spark,
|
"compute": spark,
|
||||||
"roles": ["PROJECT_BRAIN", "PLANNING", "ARCHAEOLOGY_INTERPRETATION"],
|
"roles": ["PROJECT_BRAIN", "PLANNING", "ARCHAEOLOGY_INTERPRETATION"],
|
||||||
"config": {"command": os.environ.get("ARTIFEX_SOL_OPENCODE_COMMAND", "opencode run --json --no-repo --stdin")},
|
"config": {
|
||||||
|
"transport": os.environ.get("ARTIFEX_SOL_TRANSPORT", "local"),
|
||||||
|
"command": os.environ.get("ARTIFEX_SOL_OPENCODE_COMMAND", "/home/daniel/.opencode/bin/opencode run"),
|
||||||
|
"use_pty": os.environ.get("ARTIFEX_SOL_USE_PTY", "1") == "1",
|
||||||
|
"timeout_seconds": int(os.environ.get("ARTIFEX_SOL_TIMEOUT_SECONDS", "120")),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Resource.objects.update_or_create(
|
Resource.objects.update_or_create(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
@ -36,6 +38,17 @@ def extract_json_object(text: str) -> dict[str, Any]:
|
||||||
return _extract_json(text[start : end + 1])
|
return _extract_json(text[start : end + 1])
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_opencode_output(text: str) -> str:
|
||||||
|
text = re.sub(r"\x1b\[[0-9;?]*[ -/]*[@-~]", "", text)
|
||||||
|
lines = []
|
||||||
|
for line in text.splitlines():
|
||||||
|
stripped = line.strip()
|
||||||
|
if not stripped or stripped.startswith("> build"):
|
||||||
|
continue
|
||||||
|
lines.append(line)
|
||||||
|
return "\n".join(lines).strip()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SolProvider:
|
class SolProvider:
|
||||||
resource: Resource
|
resource: Resource
|
||||||
|
|
@ -44,44 +57,47 @@ class SolProvider:
|
||||||
def complete(self, request: ModelRequestContract) -> ModelResponseContract:
|
def complete(self, request: ModelRequestContract) -> ModelResponseContract:
|
||||||
config = self.resource.config
|
config = self.resource.config
|
||||||
compute = self.resource.compute
|
compute = self.resource.compute
|
||||||
ssh_alias = (compute.config if compute else {}).get("ssh_alias", config.get("ssh_alias", "spark"))
|
|
||||||
timeout = int(config.get("timeout_seconds", 120))
|
timeout = int(config.get("timeout_seconds", 120))
|
||||||
remote_command = config.get("command", "opencode run --json --no-repo --stdin")
|
command = str(config.get("command", "opencode run"))
|
||||||
payload = {
|
transport = str(config.get("transport", "ssh"))
|
||||||
"mode": "reasoning_only",
|
use_pty = bool(config.get("use_pty", False))
|
||||||
"output_schema": "json_object",
|
if transport == "local":
|
||||||
"prompt": request.prompt,
|
argv = [*shlex.split(command), request.prompt]
|
||||||
"token_budget": request.token_budget,
|
if use_pty:
|
||||||
}
|
shell_command = " ".join(shlex.quote(part) for part in argv)
|
||||||
completed = subprocess.run(
|
argv = ["script", "-q", "-e", "-c", shell_command, "/dev/null"]
|
||||||
["ssh", str(ssh_alias), str(remote_command)],
|
completed = subprocess.run(argv, capture_output=True, text=True, timeout=timeout, check=False)
|
||||||
input=json.dumps(payload),
|
else:
|
||||||
capture_output=True,
|
ssh_alias = (compute.config if compute else {}).get("ssh_alias", config.get("ssh_alias", "spark"))
|
||||||
text=True,
|
remote_command = " ".join(shlex.quote(part) for part in [*shlex.split(command), request.prompt])
|
||||||
timeout=timeout,
|
completed = subprocess.run(
|
||||||
check=False,
|
["ssh", "-tt", str(ssh_alias), remote_command],
|
||||||
)
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=timeout,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
if completed.returncode != 0:
|
if completed.returncode != 0:
|
||||||
raise ProviderError(completed.stderr.strip() or "Sol provider failed")
|
raise ProviderError(completed.stderr.strip() or "Sol provider failed")
|
||||||
data = _extract_json(completed.stdout)
|
content = _clean_opencode_output(completed.stdout)
|
||||||
content = data.get("content") or data.get("response") or data.get("plan")
|
if not content:
|
||||||
if isinstance(content, (dict, list)):
|
raise ProviderError("Sol provider response missing content")
|
||||||
content = json.dumps(content)
|
|
||||||
if not isinstance(content, str):
|
|
||||||
raise ProviderError("Sol provider response missing string content")
|
|
||||||
return ModelResponseContract(
|
return ModelResponseContract(
|
||||||
model=self.resource.name,
|
model=self.resource.name,
|
||||||
content=content,
|
content=content,
|
||||||
metadata={"provider": self.provider_name, "usage": data.get("usage", {})},
|
metadata={"provider": self.provider_name, "usage": {}},
|
||||||
)
|
)
|
||||||
|
|
||||||
def health(self) -> str:
|
def health(self) -> str:
|
||||||
compute = self.resource.compute
|
config = self.resource.config
|
||||||
ssh_alias = (compute.config if compute else {}).get("ssh_alias", self.resource.config.get("ssh_alias", "spark"))
|
|
||||||
try:
|
try:
|
||||||
completed = subprocess.run(
|
if config.get("transport") == "local":
|
||||||
["ssh", str(ssh_alias), "true"], capture_output=True, text=True, timeout=10, check=False
|
command = shlex.split(str(config.get("command", "opencode")))[0]
|
||||||
)
|
completed = subprocess.run([command, "--version"], capture_output=True, text=True, timeout=10, check=False)
|
||||||
|
else:
|
||||||
|
compute = self.resource.compute
|
||||||
|
ssh_alias = (compute.config if compute else {}).get("ssh_alias", self.resource.config.get("ssh_alias", "spark"))
|
||||||
|
completed = subprocess.run(["ssh", str(ssh_alias), "true"], capture_output=True, text=True, timeout=10, check=False)
|
||||||
except Exception:
|
except Exception:
|
||||||
return "UNAVAILABLE"
|
return "UNAVAILABLE"
|
||||||
return "AVAILABLE" if completed.returncode == 0 else "UNAVAILABLE"
|
return "AVAILABLE" if completed.returncode == 0 else "UNAVAILABLE"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue