feat: orchestrate goals with local and cloud models
This commit is contained in:
@@ -12,7 +12,9 @@ import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from typing import Dict, Optional
|
||||
|
||||
@@ -33,6 +35,7 @@ PROVIDERS = {
|
||||
}
|
||||
RUNNING: Dict[str, subprocess.Popen] = {}
|
||||
LOCK = threading.Lock()
|
||||
MODEL_LOCKS = {provider: threading.Lock() for provider in PROVIDERS}
|
||||
|
||||
|
||||
def command_status(provider: str) -> dict:
|
||||
@@ -109,6 +112,66 @@ def start_login(provider: str) -> dict:
|
||||
return {"success": True, "reason": "browser_login_started", "provider": command_status(provider)}
|
||||
|
||||
|
||||
def generate_with_account(provider: str, prompt: str) -> dict:
|
||||
status = command_status(provider)
|
||||
if not status["available"] or not status["loggedIn"]:
|
||||
return {"success": False, "reason": "provider_not_logged_in"}
|
||||
if not prompt or len(prompt) > 24000:
|
||||
return {"success": False, "reason": "prompt_length_invalid"}
|
||||
started = time.monotonic()
|
||||
with MODEL_LOCKS[provider], tempfile.TemporaryDirectory(prefix="casan-account-model-") as directory:
|
||||
try:
|
||||
if provider == "codex":
|
||||
output_path = os.path.join(directory, "last-message.txt")
|
||||
command = [
|
||||
"codex", "exec", "--ephemeral", "--ignore-user-config", "--ignore-rules",
|
||||
"--skip-git-repo-check", "--sandbox", "read-only", "--color", "never",
|
||||
"--cd", directory, "--output-last-message", output_path, "-",
|
||||
]
|
||||
result = subprocess.run(
|
||||
command, input=prompt, capture_output=True, text=True, timeout=300,
|
||||
env={**os.environ, "NO_COLOR": "1"},
|
||||
)
|
||||
text = ""
|
||||
if result.returncode == 0 and os.path.isfile(output_path):
|
||||
with open(output_path, encoding="utf-8") as handle:
|
||||
text = handle.read().strip()
|
||||
usage = {}
|
||||
model = "codex-account-default"
|
||||
else:
|
||||
command = [
|
||||
"claude", "--print", "--output-format", "json", "--permission-mode", "plan",
|
||||
"--tools", "", "--safe-mode", "--no-session-persistence",
|
||||
]
|
||||
result = subprocess.run(
|
||||
command, input=prompt, capture_output=True, text=True, timeout=300, cwd=directory,
|
||||
env={**os.environ, "NO_COLOR": "1"},
|
||||
)
|
||||
try:
|
||||
payload = json.loads(result.stdout or "{}")
|
||||
except ValueError:
|
||||
payload = {}
|
||||
text = str(payload.get("result") or "").strip()
|
||||
raw_usage = payload.get("usage") if isinstance(payload.get("usage"), dict) else {}
|
||||
usage = {
|
||||
"input_tokens": int(raw_usage.get("input_tokens") or 0),
|
||||
"output_tokens": int(raw_usage.get("output_tokens") or 0),
|
||||
}
|
||||
model = str(payload.get("model") or "claude-account-default")
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
return {"success": False, "reason": "account_model_unreachable"}
|
||||
if result.returncode != 0 or not text:
|
||||
return {"success": False, "reason": "account_model_failed"}
|
||||
return {
|
||||
"success": True,
|
||||
"provider": provider,
|
||||
"model": model,
|
||||
"text": text,
|
||||
"usage": usage,
|
||||
"latency_ms": int((time.monotonic() - started) * 1000),
|
||||
}
|
||||
|
||||
|
||||
class BridgeHandler(BaseHTTPRequestHandler):
|
||||
server_version = "CASANAuthBridge/1.0"
|
||||
|
||||
@@ -130,6 +193,19 @@ class BridgeHandler(BaseHTTPRequestHandler):
|
||||
supplied = self.headers.get("X-CASAN-Bridge-Token", "")
|
||||
return bool(expected) and hmac.compare_digest(expected, supplied)
|
||||
|
||||
def read_json(self) -> dict:
|
||||
try:
|
||||
length = int(self.headers.get("Content-Length", "0"))
|
||||
except ValueError:
|
||||
return {}
|
||||
if length < 1 or length > 100000:
|
||||
return {}
|
||||
try:
|
||||
payload = json.loads(self.rfile.read(length).decode("utf-8"))
|
||||
except (UnicodeDecodeError, ValueError):
|
||||
return {}
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
|
||||
def do_GET(self) -> None:
|
||||
if self.path == "/healthz":
|
||||
self.send_json(200, {"status": "ok"})
|
||||
@@ -151,6 +227,11 @@ class BridgeHandler(BaseHTTPRequestHandler):
|
||||
result = start_login(parts[2])
|
||||
self.send_json(202 if result["success"] else 503, result)
|
||||
return
|
||||
if len(parts) == 4 and parts[:2] == ["v1", "models"] and parts[3] == "generate" and parts[2] in PROVIDERS:
|
||||
body = self.read_json()
|
||||
result = generate_with_account(parts[2], str(body.get("prompt") or ""))
|
||||
self.send_json(200 if result["success"] else 503, result)
|
||||
return
|
||||
self.send_json(404, {"success": False, "reason": "not_found"})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user