Skip to content
Closed
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
5 changes: 4 additions & 1 deletion llm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import logging
from typing import Any, Dict, Optional
from models import ModelProvider, OllamaProvider, GeminiProvider
from models import ModelProvider, OllamaProvider, GeminiProvider, CopilotProvider
from prompt import MODEL_PROVIDER_MAPPING, GEMINI_API_KEY

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -57,6 +57,9 @@ def initialize_llm_provider(model_name: str) -> Any:
else:
logger.info(f"🔄 Using Google Gemini API provider with model {model_name}")
provider = GeminiProvider(api_key=GEMINI_API_KEY)
elif model_provider == ModelProvider.COPILOT:
logger.info(f"🔄 Using GitHub Copilot SDK provider with model {model_name}")
provider = CopilotProvider()
else:
logger.info(f"🔄 Using Ollama provider with model {model_name}")
return provider
90 changes: 90 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class ModelProvider(Enum):

OLLAMA = "ollama"
GEMINI = "gemini"
COPILOT = "copilot"


@runtime_checkable
Expand Down Expand Up @@ -389,3 +390,92 @@ def chat(
f"Retrying in {sleep_time}s..."
)
time.sleep(sleep_time)


class CopilotProvider:
"""GitHub Copilot SDK API provider implementation."""

def __init__(self):
import concurrent.futures
self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)

def chat(
self,
model: str,
messages: List[Dict[str, str]],
options: Dict[str, Any] = None,
**kwargs
) -> Dict[str, Any]:
"""Send a chat request to GitHub Copilot SDK."""
import asyncio
import concurrent.futures
from copilot import CopilotClient
from copilot.session_events import AssistantMessageData, SessionIdleData, SessionErrorData
from copilot.session import PermissionHandler

async def run_chat():
system_msgs = [msg["content"] for msg in messages if msg["role"] == "system"]
system_msg = "\n\n".join(system_msgs)

non_system_msgs = [msg for msg in messages if msg["role"] != "system"]
if len(non_system_msgs) == 1 and non_system_msgs[0]["role"] == "user":
user_msg = non_system_msgs[0]["content"]
else:
conversation = []
for msg in non_system_msgs:
role = "User" if msg["role"] == "user" else "Assistant"
conversation.append(f"{role}:\n{msg['content']}")
user_msg = "\n\n".join(conversation)

if kwargs.get("format") == "json":
json_instruction = "You must output the result in strictly valid JSON format."
system_msg = f"{system_msg}\n\n{json_instruction}" if system_msg else json_instruction

async with CopilotClient() as client:
session_params = {
"model": model,
}
Comment thread
Harshalj2108 marked this conversation as resolved.
Comment thread
Harshalj2108 marked this conversation as resolved.
if "stream" in kwargs:
session_params["streaming"] = kwargs["stream"]
if "on_permission_request" in kwargs:
session_params["on_permission_request"] = kwargs["on_permission_request"]
elif options and "on_permission_request" in options:
session_params["on_permission_request"] = options["on_permission_request"]
if system_msg:
session_params["system_message"] = {
"mode": "replace",
"content": system_msg
}

async with await client.create_session(**session_params) as session:
done = asyncio.Event()
response_content = []
session_error = None

def on_event(event):
nonlocal session_error
if isinstance(event.data, AssistantMessageData):
if event.data.content:
response_content.append(event.data.content)
elif isinstance(event.data, SessionErrorData):
session_error = getattr(event.data, "error", event.data)
done.set()
elif isinstance(event.data, SessionIdleData):
done.set()

session.on(on_event)
await session.send(user_msg)
await done.wait()

if session_error:
if isinstance(session_error, Exception):
raise session_error
raise RuntimeError(f"Copilot SDK Error: {session_error}")

Comment thread
Harshalj2108 marked this conversation as resolved.
return "".join(response_content)

# Use the class-level ThreadPoolExecutor as a clean sync-async bridge
future = self.executor.submit(asyncio.run, run_chat())
content = future.result()

return {"message": {"role": "assistant", "content": content}}
18 changes: 18 additions & 0 deletions prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@
"gemini-2.5-flash-lite": {"temperature": 0.1, "top_p": 0.9},
"gemini-3.5-flash": {"temperature": 0.1, "top_p": 0.9},
"gemini-3.1-flash-lite": {"temperature": 0.1, "top_p": 0.9},
# GitHub Copilot models
"gpt-5.5": {},
"gpt-5.6-luna": {},
"gpt-5.6-sol": {},
"gpt-5.6-terra": {},
"claude-sonnet-5": {},
"claude-opus-4.8": {},
"claude-fable-5": {},
"mai-code-1-flash": {},
}

# Model provider mapping
Expand All @@ -61,6 +70,15 @@
"gemini-2.5-pro": ModelProvider.GEMINI,
"gemini-3.5-flash": ModelProvider.GEMINI,
"gemini-3.1-flash-lite": ModelProvider.GEMINI,
# GitHub Copilot models
"gpt-5.5": ModelProvider.COPILOT,
"gpt-5.6-luna": ModelProvider.COPILOT,
"gpt-5.6-sol": ModelProvider.COPILOT,
"gpt-5.6-terra": ModelProvider.COPILOT,
"claude-sonnet-5": ModelProvider.COPILOT,
"claude-opus-4.8": ModelProvider.COPILOT,
"claude-fable-5": ModelProvider.COPILOT,
"mai-code-1-flash": ModelProvider.COPILOT,
}

# Get API keys from environment
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ pymupdf4llm==0.0.27
Jinja2==3.1.6
google-generativeai==0.4.0
python-dotenv==1.0.1
black==25.9.0
black==25.9.0
github-copilot-sdk==1.0.6