Spaces:
Sleeping
Sleeping
Add Microsoft Copilot provider - Browser-based provider using Playwright - Integrated into engine and config - Added test script and documentation
Browse files- KAIGUIDE.md +11 -1
- config.py +4 -0
- engine.py +6 -1
- providers/copilot_provider.py +243 -0
- test_copilot_browser.py +61 -0
KAIGUIDE.md
CHANGED
|
@@ -68,7 +68,17 @@ Uses Playwright Chromium to interact with `gemini.google.com` as a real browser.
|
|
| 68 |
- **Files**: `providers/gemini_provider.py`, `test_gemini_browser.py`.
|
| 69 |
- **Status**: **Experimental**. Requires local Playwright environment.
|
| 70 |
|
| 71 |
-
###
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
The API includes a search engine (`search_engine.py`) powered by DuckDuckGo (via `duckduckgo_search`).
|
| 73 |
- **`/search`**: Returns raw search results.
|
| 74 |
- **`/deep_research`**: Multi-step process:
|
|
|
|
| 68 |
- **Files**: `providers/gemini_provider.py`, `test_gemini_browser.py`.
|
| 69 |
- **Status**: **Experimental**. Requires local Playwright environment.
|
| 70 |
|
| 71 |
+
### E. Microsoft Copilot (Browser-Based Provider)
|
| 72 |
+
Uses Playwright Chromium to interact with `copilot.microsoft.com` as a real browser.
|
| 73 |
+
- **Why Browser**: Microsoft's Copilot requires a browser session to function properly.
|
| 74 |
+
- **Input**: Multiple selector strategies for robust input detection (`[data-testid="chat-input"]`, `div[contenteditable="true"]`).
|
| 75 |
+
- **Features**: Handles "Continue" buttons automatically for longer responses.
|
| 76 |
+
- **Model**: `copilot-gpt-4` (GPT-4 powered responses).
|
| 77 |
+
- **Files**: `providers/copilot_provider.py`.
|
| 78 |
+
- **Status**: **Experimental**. Requires local Playwright environment.
|
| 79 |
+
- **Vercel**: **DISABLED** (no Chromium in serverless). Local/Docker only.
|
| 80 |
+
|
| 81 |
+
### F. Search & Deep Research
|
| 82 |
The API includes a search engine (`search_engine.py`) powered by DuckDuckGo (via `duckduckgo_search`).
|
| 83 |
- **`/search`**: Returns raw search results.
|
| 84 |
- **`/deep_research`**: Multi-step process:
|
config.py
CHANGED
|
@@ -25,6 +25,7 @@ MODEL_RANKING = [
|
|
| 25 |
("gpt-4o-mini", "g4f", "gpt-4o-mini"),
|
| 26 |
("glm-5", "zai", "glm-5"),
|
| 27 |
("gemini-3-flash", "gemini", "gemini-3-flash"),
|
|
|
|
| 28 |
("gpt-oss-20b", "pollinations", "openai"),
|
| 29 |
("mistral-small-3.2", "pollinations", "mistral"),
|
| 30 |
|
|
@@ -81,6 +82,9 @@ PROVIDER_MODELS = {
|
|
| 81 |
"gemini": [
|
| 82 |
"gemini-3-flash",
|
| 83 |
],
|
|
|
|
|
|
|
|
|
|
| 84 |
"pollinations": [
|
| 85 |
"gpt-oss-20b",
|
| 86 |
"mistral-small-3.2-24b",
|
|
|
|
| 25 |
("gpt-4o-mini", "g4f", "gpt-4o-mini"),
|
| 26 |
("glm-5", "zai", "glm-5"),
|
| 27 |
("gemini-3-flash", "gemini", "gemini-3-flash"),
|
| 28 |
+
("copilot-gpt-4", "copilot", "copilot-gpt-4"),
|
| 29 |
("gpt-oss-20b", "pollinations", "openai"),
|
| 30 |
("mistral-small-3.2", "pollinations", "mistral"),
|
| 31 |
|
|
|
|
| 82 |
"gemini": [
|
| 83 |
"gemini-3-flash",
|
| 84 |
],
|
| 85 |
+
"copilot": [
|
| 86 |
+
"copilot-gpt-4",
|
| 87 |
+
],
|
| 88 |
"pollinations": [
|
| 89 |
"gpt-oss-20b",
|
| 90 |
"mistral-small-3.2-24b",
|
engine.py
CHANGED
|
@@ -19,6 +19,7 @@ from providers.g4f_provider import G4FProvider
|
|
| 19 |
from providers.pollinations_provider import PollinationsProvider
|
| 20 |
from providers.gemini_provider import GeminiProvider
|
| 21 |
from providers.zai_provider import ZaiProvider
|
|
|
|
| 22 |
from config import MODEL_RANKING, PROVIDER_MODELS, SUPABASE_URL, SUPABASE_KEY
|
| 23 |
from models import ModelInfo
|
| 24 |
from sanitizer import sanitize_response
|
|
@@ -56,8 +57,12 @@ class AIEngine:
|
|
| 56 |
# Gemini also uses Playwright, so we enable it here too
|
| 57 |
self._providers["gemini"] = GeminiProvider()
|
| 58 |
logger.info("β
Gemini provider enabled")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
else:
|
| 60 |
-
logger.warning("β οΈ Z.ai/Gemini providers disabled (Playwright not installed)")
|
| 61 |
# Success Tracker: Key = "provider/model_id"
|
| 62 |
# Value = {success, failure, consecutive_failures, avg_time_ms, total_time_ms, count_samples}
|
| 63 |
self._stats: dict[str, dict] = {}
|
|
|
|
| 19 |
from providers.pollinations_provider import PollinationsProvider
|
| 20 |
from providers.gemini_provider import GeminiProvider
|
| 21 |
from providers.zai_provider import ZaiProvider
|
| 22 |
+
from providers.copilot_provider import CopilotProvider
|
| 23 |
from config import MODEL_RANKING, PROVIDER_MODELS, SUPABASE_URL, SUPABASE_KEY
|
| 24 |
from models import ModelInfo
|
| 25 |
from sanitizer import sanitize_response
|
|
|
|
| 57 |
# Gemini also uses Playwright, so we enable it here too
|
| 58 |
self._providers["gemini"] = GeminiProvider()
|
| 59 |
logger.info("β
Gemini provider enabled")
|
| 60 |
+
|
| 61 |
+
# Copilot also uses Playwright
|
| 62 |
+
self._providers["copilot"] = CopilotProvider()
|
| 63 |
+
logger.info("β
Copilot provider enabled")
|
| 64 |
else:
|
| 65 |
+
logger.warning("β οΈ Z.ai/Gemini/Copilot providers disabled (Playwright not installed)")
|
| 66 |
# Success Tracker: Key = "provider/model_id"
|
| 67 |
# Value = {success, failure, consecutive_failures, avg_time_ms, total_time_ms, count_samples}
|
| 68 |
self._stats: dict[str, dict] = {}
|
providers/copilot_provider.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Microsoft Copilot Provider (Browser-Based)
|
| 3 |
+
-------------------------------------------
|
| 4 |
+
Uses Playwright Chromium to interact with https://copilot.microsoft.com/ as a real browser.
|
| 5 |
+
|
| 6 |
+
Strategy:
|
| 7 |
+
- Reuses the global Playwright browser instance (shared pattern with Z.ai/Gemini).
|
| 8 |
+
- Uses EPHEMERAL contexts (Tabs) per request for robust data isolation.
|
| 9 |
+
- Scrapes the AI response from the DOM.
|
| 10 |
+
- Handles the "Continue" button for longer responses.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import asyncio
|
| 14 |
+
import logging
|
| 15 |
+
import re
|
| 16 |
+
from providers.base import BaseProvider
|
| 17 |
+
from config import PROVIDER_MODELS
|
| 18 |
+
|
| 19 |
+
logger = logging.getLogger("kai_api.copilot")
|
| 20 |
+
|
| 21 |
+
_playwright = None
|
| 22 |
+
_browser = None
|
| 23 |
+
_lock = asyncio.Lock()
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class CopilotProvider(BaseProvider):
|
| 27 |
+
"""AI provider using Microsoft Copilot via Persistent Playwright Browser."""
|
| 28 |
+
|
| 29 |
+
RESPONSE_TIMEOUT = 60
|
| 30 |
+
HYDRATION_DELAY = 3.0
|
| 31 |
+
|
| 32 |
+
@property
|
| 33 |
+
def name(self) -> str:
|
| 34 |
+
return "copilot"
|
| 35 |
+
|
| 36 |
+
def get_available_models(self) -> list[str]:
|
| 37 |
+
return PROVIDER_MODELS.get("copilot", ["copilot-gpt-4"])
|
| 38 |
+
|
| 39 |
+
@staticmethod
|
| 40 |
+
def is_available() -> bool:
|
| 41 |
+
"""Check if Playwright is installed and usable."""
|
| 42 |
+
try:
|
| 43 |
+
from playwright.async_api import async_playwright
|
| 44 |
+
return True
|
| 45 |
+
except ImportError:
|
| 46 |
+
return False
|
| 47 |
+
|
| 48 |
+
async def _ensure_browser(self):
|
| 49 |
+
"""Start the persistent browser if it's not running."""
|
| 50 |
+
global _playwright, _browser
|
| 51 |
+
|
| 52 |
+
async with _lock:
|
| 53 |
+
if _browser and _browser.is_connected():
|
| 54 |
+
return
|
| 55 |
+
|
| 56 |
+
logger.info("π Copilot: Launching Persistent Browser...")
|
| 57 |
+
from playwright.async_api import async_playwright
|
| 58 |
+
|
| 59 |
+
_playwright = await async_playwright().start()
|
| 60 |
+
_browser = await _playwright.chromium.launch(
|
| 61 |
+
headless=True,
|
| 62 |
+
args=[
|
| 63 |
+
"--disable-blink-features=AutomationControlled",
|
| 64 |
+
"--no-sandbox",
|
| 65 |
+
"--disable-dev-shm-usage",
|
| 66 |
+
"--disable-gpu",
|
| 67 |
+
"--disable-web-security",
|
| 68 |
+
"--disable-features=IsolateOrigins,site-per-process",
|
| 69 |
+
],
|
| 70 |
+
)
|
| 71 |
+
logger.info("β
Copilot: Browser is Ready.")
|
| 72 |
+
|
| 73 |
+
async def send_message(
|
| 74 |
+
self,
|
| 75 |
+
prompt: str,
|
| 76 |
+
model: str | None = None,
|
| 77 |
+
system_prompt: str | None = None,
|
| 78 |
+
**kwargs,
|
| 79 |
+
) -> dict:
|
| 80 |
+
"""Send a message via Copilot browser automation."""
|
| 81 |
+
if not self.is_available():
|
| 82 |
+
raise RuntimeError("Playwright not installed.")
|
| 83 |
+
|
| 84 |
+
await self._ensure_browser()
|
| 85 |
+
selected_model = model or "copilot-gpt-4"
|
| 86 |
+
|
| 87 |
+
# Create Ephemeral Context
|
| 88 |
+
context = await _browser.new_context(
|
| 89 |
+
viewport={"width": 1920, "height": 1080},
|
| 90 |
+
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
| 91 |
+
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
| 92 |
+
"Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
|
| 93 |
+
locale="en-US",
|
| 94 |
+
timezone_id="America/New_York",
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
# Hide webdriver flag
|
| 98 |
+
await context.add_init_script("""
|
| 99 |
+
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
|
| 100 |
+
Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
|
| 101 |
+
window.chrome = { runtime: {} };
|
| 102 |
+
""")
|
| 103 |
+
|
| 104 |
+
page = await context.new_page()
|
| 105 |
+
|
| 106 |
+
try:
|
| 107 |
+
logger.info(f"Copilot request: {selected_model}")
|
| 108 |
+
|
| 109 |
+
# Navigate to Copilot
|
| 110 |
+
await page.goto("https://copilot.microsoft.com/", timeout=60000)
|
| 111 |
+
|
| 112 |
+
# Wait for the chat input to be ready
|
| 113 |
+
# Copilot uses contenteditable divs
|
| 114 |
+
input_selectors = [
|
| 115 |
+
'[data-testid="chat-input"]',
|
| 116 |
+
'div[contenteditable="true"]',
|
| 117 |
+
'[role="textbox"]',
|
| 118 |
+
'textarea',
|
| 119 |
+
'.input-area div[contenteditable]',
|
| 120 |
+
]
|
| 121 |
+
|
| 122 |
+
input_selector = None
|
| 123 |
+
for sel in input_selectors:
|
| 124 |
+
try:
|
| 125 |
+
await page.wait_for_selector(sel, timeout=10000)
|
| 126 |
+
input_selector = sel
|
| 127 |
+
logger.info(f"β
Copilot: Found input selector: {sel}")
|
| 128 |
+
break
|
| 129 |
+
except:
|
| 130 |
+
continue
|
| 131 |
+
|
| 132 |
+
if not input_selector:
|
| 133 |
+
raise RuntimeError("Could not find Copilot chat input")
|
| 134 |
+
|
| 135 |
+
await asyncio.sleep(self.HYDRATION_DELAY)
|
| 136 |
+
|
| 137 |
+
# Type the message
|
| 138 |
+
full_prompt = prompt
|
| 139 |
+
if system_prompt:
|
| 140 |
+
full_prompt = f"[System: {system_prompt}]\n\n{prompt}"
|
| 141 |
+
|
| 142 |
+
await page.click(input_selector)
|
| 143 |
+
await page.keyboard.type(full_prompt, delay=10)
|
| 144 |
+
await asyncio.sleep(0.5)
|
| 145 |
+
await page.keyboard.press("Enter")
|
| 146 |
+
|
| 147 |
+
logger.info("Copilot: Message sent...")
|
| 148 |
+
|
| 149 |
+
# Wait for response
|
| 150 |
+
response_text = await self._wait_for_response(page)
|
| 151 |
+
|
| 152 |
+
if not response_text:
|
| 153 |
+
raise ValueError("Empty response from Copilot")
|
| 154 |
+
|
| 155 |
+
return {
|
| 156 |
+
"response": response_text,
|
| 157 |
+
"model": selected_model,
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
except Exception as e:
|
| 161 |
+
logger.error(f"Copilot Error: {e}")
|
| 162 |
+
raise
|
| 163 |
+
finally:
|
| 164 |
+
await context.close()
|
| 165 |
+
|
| 166 |
+
async def _wait_for_response(self, page) -> str:
|
| 167 |
+
"""Wait for and extract the AI response from the DOM."""
|
| 168 |
+
last_text = ""
|
| 169 |
+
stable_count = 0
|
| 170 |
+
required_stable = 4
|
| 171 |
+
|
| 172 |
+
for i in range(self.RESPONSE_TIMEOUT * 2):
|
| 173 |
+
await asyncio.sleep(0.5)
|
| 174 |
+
|
| 175 |
+
# Check for "Continue" button and click it
|
| 176 |
+
try:
|
| 177 |
+
continue_btn = await page.query_selector(
|
| 178 |
+
'button:has-text("Continue"), button:has-text("Continue anyway")'
|
| 179 |
+
)
|
| 180 |
+
if continue_btn and await continue_btn.is_visible():
|
| 181 |
+
logger.info("Copilot: Clicking 'Continue' button...")
|
| 182 |
+
await continue_btn.click()
|
| 183 |
+
await asyncio.sleep(1)
|
| 184 |
+
except:
|
| 185 |
+
pass
|
| 186 |
+
|
| 187 |
+
# Extract response text
|
| 188 |
+
current_text = await page.evaluate("""
|
| 189 |
+
() => {
|
| 190 |
+
const selectors = [
|
| 191 |
+
'[data-testid="assistant-message"]',
|
| 192 |
+
'.message-content',
|
| 193 |
+
'[data-message-author-role="assistant"]',
|
| 194 |
+
'.ac-textBlock',
|
| 195 |
+
'[class*="response"]',
|
| 196 |
+
'[class*="message"] div',
|
| 197 |
+
'.markdown-body',
|
| 198 |
+
];
|
| 199 |
+
|
| 200 |
+
for (const sel of selectors) {
|
| 201 |
+
const els = document.querySelectorAll(sel);
|
| 202 |
+
if (els.length > 0) {
|
| 203 |
+
const last = els[els.length - 1];
|
| 204 |
+
const text = last.innerText || last.textContent || '';
|
| 205 |
+
if (text.trim().length > 10) return text.trim();
|
| 206 |
+
}
|
| 207 |
+
}
|
| 208 |
+
return '';
|
| 209 |
+
}
|
| 210 |
+
""")
|
| 211 |
+
|
| 212 |
+
if not current_text:
|
| 213 |
+
continue
|
| 214 |
+
|
| 215 |
+
# Clean the text
|
| 216 |
+
clean = self._clean_response(current_text)
|
| 217 |
+
|
| 218 |
+
if clean == last_text and len(clean) > 0:
|
| 219 |
+
stable_count += 1
|
| 220 |
+
if stable_count >= required_stable:
|
| 221 |
+
return clean
|
| 222 |
+
else:
|
| 223 |
+
stable_count = 0
|
| 224 |
+
last_text = clean
|
| 225 |
+
|
| 226 |
+
if i % 10 == 9:
|
| 227 |
+
logger.info(f"Copilot: Stream... {len(last_text)} chars")
|
| 228 |
+
|
| 229 |
+
if last_text:
|
| 230 |
+
logger.warning("Copilot: Timeout, returning partial.")
|
| 231 |
+
return last_text
|
| 232 |
+
|
| 233 |
+
raise TimeoutError("Copilot no response")
|
| 234 |
+
|
| 235 |
+
def _clean_response(self, text: str) -> str:
|
| 236 |
+
"""Clean up Copilot response text."""
|
| 237 |
+
clean = text.strip()
|
| 238 |
+
|
| 239 |
+
# Remove common UI artifacts
|
| 240 |
+
clean = re.sub(r"^(Copilot\s*|Microsoft Copilot\s*)", "", clean, flags=re.IGNORECASE)
|
| 241 |
+
clean = re.sub(r"\n+\s*\n+", "\n\n", clean)
|
| 242 |
+
|
| 243 |
+
return clean.strip()
|
test_copilot_browser.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Test script for Microsoft Copilot Provider
|
| 3 |
+
Run this to verify the Copilot browser automation works.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import asyncio
|
| 7 |
+
import sys
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 11 |
+
|
| 12 |
+
from providers.copilot_provider import CopilotProvider
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
async def test_copilot():
|
| 16 |
+
"""Test the Copilot provider."""
|
| 17 |
+
print("π§ͺ Testing Microsoft Copilot Provider...")
|
| 18 |
+
print("-" * 50)
|
| 19 |
+
|
| 20 |
+
provider = CopilotProvider()
|
| 21 |
+
|
| 22 |
+
# Check if Playwright is available
|
| 23 |
+
if not provider.is_available():
|
| 24 |
+
print("β Playwright not installed. Run: pip install playwright && playwright install chromium")
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
print("β
Playwright is available")
|
| 28 |
+
print(f"π Available models: {provider.get_available_models()}")
|
| 29 |
+
print()
|
| 30 |
+
|
| 31 |
+
# Test prompts
|
| 32 |
+
test_prompts = [
|
| 33 |
+
"Say 'Hello from Copilot test' and nothing else.",
|
| 34 |
+
"What is 2+2? Answer with just the number.",
|
| 35 |
+
]
|
| 36 |
+
|
| 37 |
+
for i, prompt in enumerate(test_prompts, 1):
|
| 38 |
+
print(f"\nπ Test {i}: {prompt[:50]}...")
|
| 39 |
+
print("-" * 50)
|
| 40 |
+
|
| 41 |
+
try:
|
| 42 |
+
result = await provider.send_message(prompt)
|
| 43 |
+
print(f"β
SUCCESS!")
|
| 44 |
+
print(f"π€ Model: {result['model']}")
|
| 45 |
+
print(f"π¬ Response: {result['response'][:200]}...")
|
| 46 |
+
print()
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print(f"β FAILED: {e}")
|
| 49 |
+
import traceback
|
| 50 |
+
traceback.print_exc()
|
| 51 |
+
return False
|
| 52 |
+
|
| 53 |
+
print("\n" + "=" * 50)
|
| 54 |
+
print("π All Copilot tests passed!")
|
| 55 |
+
print("=" * 50)
|
| 56 |
+
return True
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
success = asyncio.run(test_copilot())
|
| 61 |
+
sys.exit(0 if success else 1)
|