KiWA001 commited on
Commit
c1c5f22
·
1 Parent(s): 2a87f4c

fix: Resolve OpenCode integration issues (terminal hang, missing models)

Browse files
engine.py CHANGED
@@ -21,6 +21,7 @@ from providers.gemini_provider import GeminiProvider
21
  from providers.zai_provider import ZaiProvider
22
  from providers.huggingchat_provider import HuggingChatProvider
23
  from providers.copilot_provider import CopilotProvider
 
24
  from config import MODEL_RANKING, PROVIDER_MODELS, SUPABASE_URL, SUPABASE_KEY
25
  from models import ModelInfo
26
  from sanitizer import sanitize_response
@@ -49,6 +50,7 @@ class AIEngine:
49
  self._providers: dict[str, BaseProvider] = {
50
  "g4f": G4FProvider(),
51
  "pollinations": PollinationsProvider(),
 
52
  }
53
  # Z.ai requires Playwright + Chromium (not available on Vercel serverless)
54
  if ZaiProvider.is_available():
 
21
  from providers.zai_provider import ZaiProvider
22
  from providers.huggingchat_provider import HuggingChatProvider
23
  from providers.copilot_provider import CopilotProvider
24
+ from providers.opencode_provider import OpenCodeProvider
25
  from config import MODEL_RANKING, PROVIDER_MODELS, SUPABASE_URL, SUPABASE_KEY
26
  from models import ModelInfo
27
  from sanitizer import sanitize_response
 
50
  self._providers: dict[str, BaseProvider] = {
51
  "g4f": G4FProvider(),
52
  "pollinations": PollinationsProvider(),
53
+ "opencode": OpenCodeProvider(),
54
  }
55
  # Z.ai requires Playwright + Chromium (not available on Vercel serverless)
56
  if ZaiProvider.is_available():
opencode_terminal.py CHANGED
@@ -108,7 +108,7 @@ class OpenCodeTerminalPortal:
108
  env['OPENCODE_CONFIG'] = os.path.abspath(self.config.config_path)
109
 
110
  self.process = subprocess.Popen(
111
- ['npx', 'opencode-ai'],
112
  cwd=self.config.project_dir,
113
  env=env,
114
  stdin=subprocess.PIPE,
 
108
  env['OPENCODE_CONFIG'] = os.path.abspath(self.config.config_path)
109
 
110
  self.process = subprocess.Popen(
111
+ ['npx', '-y', 'opencode-ai'],
112
  cwd=self.config.project_dir,
113
  env=env,
114
  stdin=subprocess.PIPE,
providers/opencode_provider.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import aiohttp
3
+ import json
4
+ import logging
5
+ import asyncio
6
+ from typing import Any, Dict, List
7
+ from .base import BaseProvider
8
+
9
+ logger = logging.getLogger("kai_api.providers.opencode")
10
+
11
+ class OpenCodeProvider(BaseProvider):
12
+ """
13
+ OpenCode AI Provider.
14
+ Uses the https://opencode.ai/zen/v1 compatible endpoint.
15
+ """
16
+
17
+ @property
18
+ def name(self) -> str:
19
+ return "opencode"
20
+
21
+ async def send_message(
22
+ self,
23
+ prompt: str,
24
+ model: str | None = None,
25
+ system_prompt: str | None = None,
26
+ **kwargs: Any,
27
+ ) -> Dict[str, Any]:
28
+ """Send message to OpenCode API."""
29
+ if not model:
30
+ model = "kimi-k2.5-free"
31
+
32
+ # The opencode config suggests:
33
+ # baseURL: "https://opencode.ai/zen/v1"
34
+ # We assume standard OpenAI format
35
+
36
+ url = "https://opencode.ai/zen/v1/chat/completions"
37
+
38
+ messages = []
39
+ if system_prompt:
40
+ messages.append({"role": "system", "content": system_prompt})
41
+ messages.append({"role": "user", "content": prompt})
42
+
43
+ # The config says model: "opencode-zen/{model}"
44
+ api_model = f"opencode-zen/{model}" if "/" not in model else model
45
+
46
+ try:
47
+ async with aiohttp.ClientSession() as session:
48
+ async with session.post(
49
+ url,
50
+ json={
51
+ "model": api_model,
52
+ "messages": messages,
53
+ "stream": False
54
+ },
55
+ headers={
56
+ "Content-Type": "application/json",
57
+ # "Authorization": "Bearer ..." # No key needed? We'll assume open for now
58
+ },
59
+ timeout=60
60
+ ) as response:
61
+ if response.status != 200:
62
+ text = await response.text()
63
+ logger.error(f"OpenCode API error {response.status}: {text}")
64
+ # If error, try fallback or just raise
65
+ raise ValueError(f"OpenCode API error {response.status}: {text}")
66
+
67
+ data = await response.json()
68
+
69
+ if "choices" in data and len(data["choices"]) > 0:
70
+ content = data["choices"][0]["message"]["content"]
71
+ return {
72
+ "response": content,
73
+ "model": model
74
+ }
75
+ else:
76
+ raise ValueError(f"Invalid response from OpenCode: {data}")
77
+
78
+ except Exception as e:
79
+ logger.error(f"OpenCode request failed: {e}")
80
+ raise
81
+
82
+ def get_available_models(self) -> List[str]:
83
+ # Copied from opencode_terminal.py
84
+ return [
85
+ "kimi-k2.5-free",
86
+ "minimax-m2.5-free",
87
+ "big-pickle",
88
+ "glm-4.7"
89
+ ]
90
+
91
+ async def health_check(self) -> bool:
92
+ try:
93
+ # Simple check
94
+ res = await self.send_message("hi", model="kimi-k2.5-free")
95
+ return bool(res and res.get("response"))
96
+ except:
97
+ return False
static/qaz.html CHANGED
@@ -1155,6 +1155,16 @@
1155
  body: JSON.stringify({ model })
1156
  });
1157
 
 
 
 
 
 
 
 
 
 
 
1158
  const data = await res.json();
1159
  if (data.status === 'success' || data.status === 'already_running') {
1160
  currentProvider = provider;
 
1155
  body: JSON.stringify({ model })
1156
  });
1157
 
1158
+ if (!res.ok) {
1159
+ const text = await res.text();
1160
+ try {
1161
+ const err = JSON.parse(text);
1162
+ throw new Error(err.detail || err.message || 'Unknown error');
1163
+ } catch (e) {
1164
+ throw new Error(`Failed to start terminal: ${res.status} ${res.statusText}`);
1165
+ }
1166
+ }
1167
+
1168
  const data = await res.json();
1169
  if (data.status === 'success' || data.status === 'already_running') {
1170
  currentProvider = provider;
supabase_proxies.sql ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- ============================================
2
+ -- K-AI API Gateway - IP/Proxy Management SQL
3
+ -- ============================================
4
+ -- This script creates tables for managing multiple proxy IPs
5
+
6
+ -- ============================================
7
+ -- Create kaiapi_proxies table for IP management
8
+ -- ============================================
9
+ CREATE TABLE IF NOT EXISTS kaiapi_proxies (
10
+ id SERIAL PRIMARY KEY,
11
+ name VARCHAR(100), -- Optional friendly name (e.g., "USA Proxy 1")
12
+ ip VARCHAR(255) NOT NULL,
13
+ port INTEGER NOT NULL,
14
+ protocol VARCHAR(20) DEFAULT 'http',
15
+ username VARCHAR(255), -- Optional auth
16
+ password VARCHAR(255), -- Optional auth
17
+ country VARCHAR(100),
18
+ city VARCHAR(100),
19
+ is_active BOOLEAN NOT NULL DEFAULT false,
20
+ is_default BOOLEAN NOT NULL DEFAULT false, -- Only one can be default
21
+ last_tested TIMESTAMP WITH TIME ZONE,
22
+ is_working BOOLEAN DEFAULT true,
23
+ response_time_ms INTEGER,
24
+ fail_count INTEGER DEFAULT 0,
25
+ success_count INTEGER DEFAULT 0,
26
+ notes TEXT, -- User notes about this proxy
27
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
28
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
29
+ );
30
+
31
+ -- Create index for faster lookups
32
+ CREATE INDEX IF NOT EXISTS idx_kaiapi_proxies_active ON kaiapi_proxies(is_active);
33
+ CREATE INDEX IF NOT EXISTS idx_kaiapi_proxies_default ON kaiapi_proxies(is_default) WHERE is_default = true;
34
+ CREATE INDEX IF NOT EXISTS idx_kaiapi_proxies_working ON kaiapi_proxies(is_working);
35
+
36
+ -- ============================================
37
+ -- Insert sample proxies (optional examples)
38
+ -- ============================================
39
+ -- Uncomment to add sample data:
40
+ -- INSERT INTO kaiapi_proxies (name, ip, port, protocol, country, is_active, notes) VALUES
41
+ -- ('US Proxy 1', '192.168.1.100', 8080, 'http', 'United States', true, 'Main proxy'),
42
+ -- ('UK Proxy 1', '10.0.0.50', 3128, 'http', 'United Kingdom', false, 'Backup');
43
+
44
+ -- ============================================
45
+ -- Create trigger to ensure only one default proxy
46
+ -- ============================================
47
+ CREATE OR REPLACE FUNCTION ensure_single_default_proxy()
48
+ RETURNS TRIGGER AS $$
49
+ BEGIN
50
+ IF NEW.is_default = true THEN
51
+ -- Set all other proxies to not default
52
+ UPDATE kaiapi_proxies SET is_default = false WHERE id != NEW.id;
53
+ END IF;
54
+ RETURN NEW;
55
+ END;
56
+ $$ LANGUAGE plpgsql;
57
+
58
+ DROP TRIGGER IF EXISTS trigger_single_default_proxy ON kaiapi_proxies;
59
+ CREATE TRIGGER trigger_single_default_proxy
60
+ BEFORE INSERT OR UPDATE ON kaiapi_proxies
61
+ FOR EACH ROW
62
+ EXECUTE FUNCTION ensure_single_default_proxy();
63
+
64
+ -- ============================================
65
+ -- Create updated_at trigger
66
+ -- ============================================
67
+ CREATE OR REPLACE FUNCTION update_updated_at_column()
68
+ RETURNS TRIGGER AS $$
69
+ BEGIN
70
+ NEW.updated_at = NOW();
71
+ RETURN NEW;
72
+ END;
73
+ $$ LANGUAGE 'plpgsql';
74
+
75
+ DROP TRIGGER IF EXISTS update_kaiapi_proxies_updated_at ON kaiapi_proxies;
76
+ CREATE TRIGGER update_kaiapi_proxies_updated_at
77
+ BEFORE UPDATE ON kaiapi_proxies
78
+ FOR EACH ROW
79
+ EXECUTE FUNCTION update_updated_at_column();
80
+
81
+ -- ============================================
82
+ -- Useful Queries
83
+ -- ============================================
84
+
85
+ -- Get all active proxies:
86
+ -- SELECT * FROM kaiapi_proxies WHERE is_active = true ORDER BY created_at DESC;
87
+
88
+ -- Get default proxy:
89
+ -- SELECT * FROM kaiapi_proxies WHERE is_default = true LIMIT 1;
90
+
91
+ -- Activate a proxy:
92
+ -- UPDATE kaiapi_proxies SET is_active = true WHERE id = 1;
93
+
94
+ -- Deactivate a proxy:
95
+ -- UPDATE kaiapi_proxies SET is_active = false WHERE id = 1;
96
+
97
+ -- Delete a proxy:
98
+ -- DELETE FROM kaiapi_proxies WHERE id = 1;
99
+
100
+ -- Mark proxy as tested:
101
+ -- UPDATE kaiapi_proxies SET
102
+ -- last_tested = NOW(),
103
+ -- is_working = true,
104
+ -- response_time_ms = 500,
105
+ -- success_count = success_count + 1
106
+ -- WHERE id = 1;
107
+
108
+ -- Mark proxy as failed:
109
+ -- UPDATE kaiapi_proxies SET
110
+ -- last_tested = NOW(),
111
+ -- is_working = false,
112
+ -- fail_count = fail_count + 1
113
+ -- WHERE id = 1;
114
+
115
+ -- Get proxy statistics:
116
+ -- SELECT
117
+ -- COUNT(*) as total,
118
+ -- COUNT(*) FILTER (WHERE is_active) as active,
119
+ -- COUNT(*) FILTER (WHERE is_working) as working,
120
+ -- COUNT(*) FILTER (WHERE is_default) as default_proxy
121
+ -- FROM kaiapi_proxies;
122
+
123
+ -- ============================================
124
+ -- Verification
125
+ -- ============================================
126
+ SELECT 'kaiapi_proxies table created successfully' as message;
127
+ SELECT column_name, data_type
128
+ FROM information_schema.columns
129
+ WHERE table_name = 'kaiapi_proxies'
130
+ ORDER BY ordinal_position;