import asyncio import json import logging from typing import AsyncGenerator from app.config import OPENCODE_SERVE_URL logger = logging.getLogger("opencode_proxy") class OpenCodeUnavailableError(Exception): pass async def query_opencode( message: str, session_id: str, user_context: str = "", ) -> AsyncGenerator[str, None]: prompt = message if user_context: prompt = f"[User context: {user_context}]\n\n{message}" try: proc = await asyncio.create_subprocess_exec( "opencode", "run", "--attach", OPENCODE_SERVE_URL, "--format", "json", prompt, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) while True: line = await proc.stdout.readline() if not line: break line = line.decode().strip() if line: try: data = json.loads(line) content = data.get("content", data.get("text", line)) yield content except json.JSONDecodeError: yield line await proc.wait() except FileNotFoundError: logger.error("opencode binary not found in PATH") raise OpenCodeUnavailableError( "opencode binary not found. Install opencode or run `opencode serve`." ) except Exception as e: logger.error("opencode proxy error: %s", e) raise OpenCodeUnavailableError(f"AI coach unavailable: {e}")