Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ omit =
*/vscode_extension/*
*/ui/*
*/assets/*
*/agent/*
adapters/roo_adapter.py
setup.py
*/site-packages/*

Expand Down
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[flake8]
max-line-length = 120
extend-ignore = E203, E266, E501, W503
extend-ignore = E203, E266, E402, E501, W503
exclude =
.git,
__pycache__,
Expand Down
56 changes: 28 additions & 28 deletions adapters/copilot_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,32 @@
class CopilotAdapter:
"""
Adapter for GitHub Copilot API integration.

Supports:
- Code completions
- Chat conversations
- Agentic workflows
- Multi-turn sessions
"""

def __init__(
self,
api_key: Optional[str] = None,
base_url: Optional[str] = None
):
"""
Initialize the Copilot adapter.

Args:
api_key: GitHub API token with Copilot access. Falls back to GITHUB_COPILOT_API_KEY env var.
base_url: Base URL for Copilot API. Falls back to GITHUB_COPILOT_BASE_URL env var.
"""
self.api_key = api_key or os.getenv("GITHUB_COPILOT_API_KEY", "")
self.base_url = base_url or os.getenv("GITHUB_COPILOT_BASE_URL", "https://api.github.com/copilot")

if not self.api_key:
raise ValueError("GitHub Copilot API key is required")

async def chat_completion(
self,
messages: List[Dict[str, str]],
Expand All @@ -62,18 +62,18 @@ async def chat_completion(
) -> Dict[str, Any]:
"""
Send a chat completion request to GitHub Copilot.

Note: GitHub Copilot API may require specific endpoints or SDK usage.
This implementation provides a compatible interface that can be adapted
when official endpoints are available.

Args:
messages: List of message objects with 'role' and 'content'
model: Model identifier
stream: Whether to stream the response
max_tokens: Maximum tokens to generate
temperature: Sampling temperature

Returns:
Response from Copilot API in OpenAI-compatible format
"""
Expand All @@ -84,7 +84,7 @@ async def chat_completion(
"Editor-Version": "vscode/1.80.0", # Required by some Copilot endpoints
"Editor-Plugin-Version": "copilot/1.0.0"
}

# GitHub Copilot may use OpenAI-compatible format
payload = {
"messages": messages,
Expand All @@ -93,7 +93,7 @@ async def chat_completion(
"temperature": temperature,
"n": 1
}

# Try standard completions endpoint
# Note: Actual endpoint may vary based on GitHub Copilot access level
async with httpx.AsyncClient(timeout=60.0) as client:
Expand All @@ -104,7 +104,7 @@ async def chat_completion(
)
response.raise_for_status()
return response.json()

async def code_completion(
self,
prompt: str,
Expand All @@ -113,55 +113,55 @@ async def code_completion(
) -> Dict[str, Any]:
"""
Request code completion from GitHub Copilot.

Args:
prompt: Code context/prompt
language: Programming language hint
max_tokens: Maximum tokens to generate

Returns:
Code completion response
"""
messages = [
{"role": "system", "content": f"You are a coding assistant. Language: {language or 'auto-detect'}"},
{"role": "user", "content": prompt}
]

return await self.chat_completion(
messages=messages,
max_tokens=max_tokens,
temperature=0.2 # Lower temperature for more deterministic code
)

async def agent_workflow(
self,
task: str,
tools: Optional[List[Dict[str, Any]]] = None
) -> Dict[str, Any]:
"""
Execute a multi-step agentic workflow.

Note: This uses the standard chat completions endpoint with enhanced
system prompts to simulate agentic behavior. Future GitHub Copilot SDK
may provide dedicated agent endpoints.

Args:
task: Task description
tools: Optional list of tools the agent can use

Returns:
Workflow execution result
"""
messages = [
{
"role": "system",
"content": "You are a GitHub Copilot agent capable of multi-step task execution. "
"Break down complex tasks, provide detailed solutions, and explain your reasoning. "
"When appropriate, suggest commands, code, or configuration changes."
"content": ("You are a GitHub Copilot agent capable of multi-step task execution. "
"Break down complex tasks, provide detailed solutions, and explain your reasoning. "
"When appropriate, suggest commands, code, or configuration changes.")
},
{"role": "user", "content": task}
]

# Use the standard chat completion with agent-like prompting
return await self.chat_completion(
messages=messages,
Expand All @@ -174,36 +174,36 @@ async def agent_workflow(
# Synchronous wrapper for backwards compatibility
class CopilotAdapterSync:
"""Synchronous wrapper for CopilotAdapter."""

def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
import requests
self.api_key = api_key or os.getenv("GITHUB_COPILOT_API_KEY", "")
self.base_url = base_url or os.getenv("GITHUB_COPILOT_BASE_URL", "https://api.github.com/copilot")
self.session = requests.Session()

if not self.api_key:
raise ValueError("GitHub Copilot API key is required")

def query(self, prompt: str, model: str = "copilot-gpt-4") -> str:
"""Simple synchronous query method."""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}

payload = {
"messages": [{"role": "user", "content": prompt}],
"model": model
}

response = self.session.post(
f"{self.base_url}/chat/completions",
json=payload,
headers=headers,
timeout=60
)
response.raise_for_status()

data = response.json()
choices = data.get("choices", [])
if not choices:
Expand Down
48 changes: 24 additions & 24 deletions adapters/roo_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ class RooAdapter:
Adapter for connecting to the Perplexity Bridge API.
Supports configurable URL and API key via environment variables.
"""

def __init__(self, url: Optional[str] = None, api_key: Optional[str] = None, timeout: int = 60):
"""
Initialize the Roo Adapter.

Args:
url: Bridge API URL (defaults to ROO_BRIDGE_URL env var or localhost:7860)
api_key: API key for authentication (defaults to ROO_BRIDGE_KEY env var or dev-secret)
Expand All @@ -27,21 +27,21 @@ def __init__(self, url: Optional[str] = None, api_key: Optional[str] = None, tim
self.api_key = api_key or os.getenv("ROO_BRIDGE_KEY", "dev-secret")
self.timeout = timeout
self.default_model = "mistral-7b-instruct"

# Ensure URL doesn't end with trailing slash
self.url = self.url.rstrip('/')

def query(self, prompt: str, model: Optional[str] = None) -> str:
"""
Query the bridge API with a prompt.

Args:
prompt: The user prompt/question
model: Model to use (defaults to configured default model)

Returns:
Response content from the API

Raises:
ValueError: If prompt is empty or invalid
ConnectionError: If unable to connect to the API
Expand All @@ -50,83 +50,83 @@ def query(self, prompt: str, model: Optional[str] = None) -> str:
"""
if not prompt or not prompt.strip():
raise ValueError("Prompt cannot be empty")

model = model or self.default_model
endpoint = f"{self.url}/v1/chat/completions"

body = {
"model": model,
"messages": [{"role": "user", "content": prompt.strip()}]
}

headers = {
"X-API-KEY": self.api_key,
"Content-Type": "application/json"
}

logger.info(f"RooAdapter: Sending request to {endpoint} with model {model}")

try:
response = requests.post(
endpoint,
headers=headers,
json=body,
timeout=self.timeout
)

# Check HTTP status
response.raise_for_status()

# Parse JSON response
try:
data = response.json()
except ValueError as e:
logger.error(f"RooAdapter: Invalid JSON response: {e}")
raise RuntimeError(f"Invalid JSON response from API: {e}")

# Check for API errors in response
if "error" in data:
error_msg = data["error"].get("message", "Unknown API error") if isinstance(data["error"], dict) else str(data["error"])
logger.error(f"RooAdapter: API error: {error_msg}")
raise RuntimeError(f"API error: {error_msg}")

# Validate response structure
if "choices" not in data or not isinstance(data["choices"], list) or len(data["choices"]) == 0:
logger.error("RooAdapter: Invalid response format - no choices")
raise RuntimeError("Invalid response format: no choices returned")

choice = data["choices"][0]
if "message" not in choice or "content" not in choice["message"]:
logger.error("RooAdapter: Invalid response format - missing message content")
raise RuntimeError("Invalid response format: missing message content")

content = choice["message"]["content"]
logger.info("RooAdapter: Successfully received response")
return content

except Timeout:
logger.error(f"RooAdapter: Request timed out after {self.timeout} seconds")
raise Timeout(f"Request to {endpoint} timed out after {self.timeout} seconds")

except ConnectionError as e:
logger.error(f"RooAdapter: Connection error: {e}")
raise ConnectionError(f"Unable to connect to {endpoint}. Is the server running?") from e

except requests.exceptions.HTTPError as e:
logger.error(f"RooAdapter: HTTP error {e.response.status_code}: {e.response.text}")
error_msg = f"HTTP {e.response.status_code}"
try:
error_data = e.response.json()
if "error" in error_data:
error_msg = error_data["error"].get("message", error_msg)
except:
except Exception:
pass
raise RuntimeError(f"API request failed: {error_msg}") from e

except RequestException as e:
logger.error(f"RooAdapter: Request error: {e}")
raise RuntimeError(f"Request failed: {str(e)}") from e

except Exception as e:
logger.error(f"RooAdapter: Unexpected error: {e}", exc_info=True)
raise RuntimeError(f"Unexpected error: {str(e)}") from e
Loading
Loading