God Agent CI commited on
Commit
d78bb58
·
1 Parent(s): 991f569

🚀 God Agent OS v7 deploy 2026-05-15 10:23:55 UTC

Browse files
Dockerfile.hf CHANGED
@@ -1,22 +1,27 @@
1
- FROM python:3.12-slim
2
 
3
  WORKDIR /app
4
 
5
- RUN apt-get update && apt-get install -y \
6
- git curl build-essential \
7
- && rm -rf /var/lib/apt/lists/*
 
8
 
 
9
  COPY requirements.txt .
10
  RUN pip install --no-cache-dir -r requirements.txt
11
 
 
12
  COPY . .
13
 
14
- RUN mkdir -p /tmp/god_workspace /tmp/workspace
 
15
 
16
- ENV PYTHONUNBUFFERED=1
17
- ENV DB_PATH=/tmp/devin_agent.db
18
  ENV WORKSPACE_DIR=/tmp/god_workspace
 
19
 
20
  EXPOSE 7860
21
 
22
- CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
 
1
+ FROM python:3.11-slim
2
 
3
  WORKDIR /app
4
 
5
+ # System deps
6
+ RUN apt-get update && apt-get install -y --no-install-recommends \
7
+ git curl build-essential && \
8
+ rm -rf /var/lib/apt/lists/*
9
 
10
+ # Python deps
11
  COPY requirements.txt .
12
  RUN pip install --no-cache-dir -r requirements.txt
13
 
14
+ # Copy backend source
15
  COPY . .
16
 
17
+ # Workspace dir
18
+ RUN mkdir -p /tmp/god_workspace
19
 
20
+ # HF Spaces runs on port 7860
21
+ ENV PORT=7860
22
  ENV WORKSPACE_DIR=/tmp/god_workspace
23
+ ENV PYTHONPATH=/app
24
 
25
  EXPOSE 7860
26
 
27
+ CMD ["uvicorn", "main_v7:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
README.md CHANGED
@@ -7,46 +7,10 @@ sdk: docker
7
  app_port: 7860
8
  pinned: true
9
  license: mit
10
- short_description: Autonomous Engineering OS v7 16 AI Agents
11
  ---
12
 
13
- # 🤖 GOD AGENT OS v7
14
- **Autonomous Engineering Operating System**
15
- *Manus + Genspark + Devin (OneHand) — Combined*
16
 
17
- [![GitHub](https://img.shields.io/badge/GitHub-god--agent--os-blue?logo=github)](https://github.com/pyaesonegtckglay-dotcom/god-agent-os)
18
-
19
- ## 🚀 16-Agent Fleet
20
-
21
- | Agent | Capability |
22
- |-------|-----------|
23
- | 🧠 OrchestratorV7 | Central brain, parallel routing |
24
- | 📋 Planner | Task graph decomposition |
25
- | 💻 Coding | Production code generation |
26
- | 🐛 Debug | Self-healing error resolution |
27
- | 🌐 **Browser** ⭐ | Web research & scraping |
28
- | 📁 **File** ⭐ | File system & project scaffold |
29
- | 🔀 **Git** ⭐ | Git ops & GitHub PR creation |
30
- | 🧪 **Test** ⭐ | Auto test generation & execution |
31
- | 🎨 **Vision** ⭐ | Design-to-code UI generation |
32
- | 🖥️ Sandbox | Isolated code execution |
33
- | 🚀 Deploy | Auto-deploy to cloud |
34
- | 🔌 Connector | External integrations |
35
- | 🧠 Memory | Long-term context |
36
- | ⚙️ Workflow | n8n automation |
37
- | 🎯 UI | Real-time UI updates |
38
- | 🤔 Reasoning | Deep reasoning chains |
39
-
40
- ## 🔑 Environment Variables (Set in Space Settings)
41
-
42
- | Variable | Description |
43
- |----------|-------------|
44
- | `OPENAI_API_KEY` | GPT-4o (optional) |
45
- | `GROQ_API_KEY` | Llama 3.3 70B — **Free & Recommended!** |
46
- | `OPENROUTER_API_KEY` | 100+ models |
47
- | `ANTHROPIC_API_KEY` | Claude 3.5 |
48
- | `GITHUB_TOKEN` | Git operations |
49
-
50
- ## API
51
- - `/api/docs` — Interactive API documentation
52
- - `/` — System status & agent list
 
7
  app_port: 7860
8
  pinned: true
9
  license: mit
10
+ short_description: Autonomous Engineering OS — Manus + Genspark + Devin
11
  ---
12
 
13
+ # 🤖 God Agent OS v7
14
+ **Autonomous Engineering Platform — Manus + Genspark + Devin (OneHand)**
 
15
 
16
+ 16-agent system for autonomous software engineering.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ai_router/router.py CHANGED
@@ -1,98 +1,85 @@
1
  """
2
- God Agent OS V7 Multi-LLM Router
3
- Gemini (6 keys) + SambaNova (9 keys) + GitHub Models (9 keys)
4
- Smart pool rotation, failover, task-based routing
5
  """
6
 
7
  import asyncio
8
  import json
9
  import os
10
  import time
11
- import random
12
- from typing import Any, Dict, List, Optional, Tuple
13
 
14
  import httpx
15
  import structlog
16
 
17
  log = structlog.get_logger()
18
 
19
- # ─── Provider Definitions ─────────────────────────────────────────────────────
20
-
21
- GEMINI_KEYS = [k.strip() for k in os.environ.get("GEMINI_KEYS", "").split(",") if k.strip()]
22
- SAMBANOVA_KEYS = [k.strip() for k in os.environ.get("SAMBANOVA_KEYS", "").split(",") if k.strip()]
23
- GITHUB_KEYS = [k.strip() for k in os.environ.get("GITHUB_KEYS", "").split(",") if k.strip()]
24
- OPENAI_KEY = os.environ.get("OPENAI_API_KEY", "")
25
- GROQ_KEY = os.environ.get("GROQ_API_KEY", "")
26
- ANTHROPIC_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
27
-
28
-
29
- class KeyPool:
30
- """Rotating key pool with cooldown on failure."""
31
-
32
- def __init__(self, keys: List[str]):
33
- self.keys = [{"key": k, "fail": 0, "cooldown": 0} for k in keys]
34
- self._idx = 0
35
-
36
- def pick(self) -> Optional[Dict]:
37
- now = time.time()
38
- usable = [k for k in self.keys if k["cooldown"] < now]
39
- if not usable:
40
- return None
41
- usable.sort(key=lambda x: x["fail"])
42
- return usable[0]
43
-
44
- def mark_fail(self, key_obj: Dict):
45
- key_obj["fail"] += 1
46
- if key_obj["fail"] >= 2:
47
- key_obj["cooldown"] = time.time() + 300 # 5 min cooldown
48
-
49
- def mark_ok(self, key_obj: Dict):
50
- key_obj["fail"] = 0
51
- key_obj["cooldown"] = 0
52
-
53
- def available(self) -> bool:
54
- return any(k["cooldown"] < time.time() for k in self.keys)
 
 
 
 
 
 
 
 
 
 
 
55
 
56
 
57
  class AIRouter:
58
  """
59
- God Agent OS V7 Multi-LLM Router
60
- Priority: Gemini SambaNova GitHub Models → OpenAI → Groq → Anthropic
61
  """
62
 
63
  def __init__(self, ws_manager=None):
64
  self.ws = ws_manager
65
- self._gemini_pool = KeyPool(GEMINI_KEYS)
66
- self._sambanova_pool = KeyPool(SAMBANOVA_KEYS)
67
- self._github_pool = KeyPool(GITHUB_KEYS)
68
- self._stats: Dict[str, Dict] = {}
69
 
70
- # ─── Task Classifier ─────────────────────────────────────────────────────
 
71
 
72
- def _classify_task(self, messages: List[Dict]) -> str:
73
- content = " ".join(m.get("content", "") for m in messages if m.get("role") == "user").lower()
74
- if any(w in content for w in ["code", "build", "api", "python", "javascript", "typescript", "function", "class", "debug", "error", "fix"]):
75
- return "coding"
76
- if any(w in content for w in ["plan", "workflow", "step", "task", "automate", "json"]):
77
- return "planning"
78
- if any(w in content for w in ["why", "explain", "analyze", "reason", "logic"]):
79
- return "reasoning"
80
- if any(w in content for w in ["ဘာ", "ဘယ်", "ဘယ်လို", "ကျေးဇူး", "မြန်မာ"]):
81
- return "burmese"
82
- return "general"
83
-
84
- def _get_provider_order(self, task_type: str) -> List[str]:
85
- """Route by task type — best model for the job."""
86
- if task_type == "coding":
87
- return ["sambanova", "github", "gemini", "openai", "groq"]
88
- elif task_type == "planning":
89
- return ["gemini", "github", "sambanova", "openai", "groq"]
90
- elif task_type == "reasoning":
91
- return ["gemini", "sambanova", "openai", "github", "groq"]
92
- elif task_type == "burmese":
93
- return ["gemini", "openai", "sambanova", "github", "groq"]
94
- else:
95
- return ["gemini", "sambanova", "github", "openai", "groq", "anthropic"]
96
 
97
  # ─── Main Entry Point ─────────────────────────────────────────────────────
98
 
@@ -106,195 +93,46 @@ class AIRouter:
106
  preferred_model: str = "",
107
  stream: bool = True,
108
  ) -> str:
109
- task_type = self._classify_task(messages)
110
- provider_order = self._get_provider_order(task_type)
111
- log.info("AI Router routing", task_type=task_type, providers=provider_order[:3])
 
 
112
 
113
  last_error = None
114
- for provider_name in provider_order:
115
  try:
116
- result = await self._call_provider(
117
- provider_name, messages, task_id, session_id, temperature, max_tokens
118
- )
119
- if result:
120
- log.info("AI Router success", provider=provider_name, chars=len(result))
121
- return result
 
 
 
 
 
 
 
 
122
  except Exception as e:
123
  last_error = e
124
- log.warning("AI Router failover", provider=provider_name, error=str(e)[:100])
 
125
  continue
126
 
127
- # Last resort: demo stream
128
- log.error("All providers failed", error=str(last_error))
129
  return await self._demo_stream(messages, task_id, session_id)
130
 
131
- # ─── Provider Dispatchers ─────────────────────────────────────────────────
132
-
133
- async def _call_provider(
134
- self, name: str, messages: List[Dict],
135
- task_id: str, session_id: str,
136
- temperature: float, max_tokens: int
137
- ) -> Optional[str]:
138
- if name == "gemini":
139
- return await self._call_gemini(messages, task_id, session_id, temperature, max_tokens)
140
- elif name == "sambanova":
141
- return await self._call_sambanova(messages, task_id, session_id, temperature, max_tokens)
142
- elif name == "github":
143
- return await self._call_github_models(messages, task_id, session_id, temperature, max_tokens)
144
- elif name == "openai" and OPENAI_KEY:
145
- return await self._call_openai_compat(
146
- "openai", OPENAI_KEY, "https://api.openai.com/v1",
147
- os.environ.get("DEFAULT_MODEL", "gpt-4o"),
148
- messages, task_id, session_id, temperature, max_tokens
149
- )
150
- elif name == "groq" and GROQ_KEY:
151
- return await self._call_openai_compat(
152
- "groq", GROQ_KEY, "https://api.groq.com/openai/v1",
153
- "llama-3.3-70b-versatile",
154
- messages, task_id, session_id, temperature, max_tokens
155
- )
156
- elif name == "anthropic" and ANTHROPIC_KEY:
157
- return await self._call_anthropic(messages, task_id, session_id, temperature, max_tokens)
158
- return None
159
-
160
- # ─── Gemini Provider ──────────────────────────────────────────────────────
161
-
162
- async def _call_gemini(
163
- self, messages: List[Dict],
164
- task_id: str, session_id: str,
165
- temperature: float, max_tokens: int
166
- ) -> Optional[str]:
167
- if not self._gemini_pool.available():
168
- return None
169
- key_obj = self._gemini_pool.pick()
170
- if not key_obj:
171
- return None
172
-
173
- # Convert messages to Gemini format
174
- system_parts = []
175
- user_parts = []
176
- contents = []
177
-
178
- for m in messages:
179
- role = m.get("role", "user")
180
- content = m.get("content", "")
181
- if role == "system":
182
- system_parts.append(content)
183
- elif role == "user":
184
- if contents and contents[-1]["role"] == "user":
185
- contents[-1]["parts"].append({"text": content})
186
- else:
187
- contents.append({"role": "user", "parts": [{"text": content}]})
188
- elif role == "assistant":
189
- contents.append({"role": "model", "parts": [{"text": content}]})
190
-
191
- # Prepend system to first user message
192
- if system_parts and contents:
193
- sys_text = "\n".join(system_parts) + "\n\n"
194
- contents[0]["parts"].insert(0, {"text": sys_text})
195
- elif system_parts and not contents:
196
- contents = [{"role": "user", "parts": [{"text": "\n".join(system_parts)}]}]
197
-
198
- payload = {
199
- "contents": contents,
200
- "generationConfig": {
201
- "temperature": temperature,
202
- "maxOutputTokens": max_tokens,
203
- "topP": 0.95,
204
- },
205
- }
206
-
207
- url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent?alt=sse&key={key_obj['key']}"
208
-
209
- full_text = ""
210
- try:
211
- async with httpx.AsyncClient(timeout=120) as client:
212
- async with client.stream("POST", url, json=payload) as resp:
213
- if resp.status_code != 200:
214
- self._gemini_pool.mark_fail(key_obj)
215
- raise Exception(f"Gemini HTTP {resp.status_code}")
216
- async for line in resp.aiter_lines():
217
- if not line.startswith("data:"):
218
- continue
219
- chunk_str = line[5:].strip()
220
- if not chunk_str or chunk_str == "[DONE]":
221
- continue
222
- try:
223
- data = json.loads(chunk_str)
224
- text = data.get("candidates", [{}])[0].get("content", {}).get("parts", [{}])[0].get("text", "")
225
- if text:
226
- full_text += text
227
- await self._emit_chunk(text, task_id, session_id)
228
- except Exception:
229
- pass
230
- self._gemini_pool.mark_ok(key_obj)
231
- return full_text if full_text else None
232
- except Exception as e:
233
- self._gemini_pool.mark_fail(key_obj)
234
- raise e
235
-
236
- # ─── SambaNova Provider ───────────────────────────────────────────────────
237
-
238
- async def _call_sambanova(
239
- self, messages: List[Dict],
240
- task_id: str, session_id: str,
241
- temperature: float, max_tokens: int
242
- ) -> Optional[str]:
243
- if not self._sambanova_pool.available():
244
- return None
245
- key_obj = self._sambanova_pool.pick()
246
- if not key_obj:
247
- return None
248
-
249
- return await self._call_openai_compat(
250
- "sambanova", key_obj["key"],
251
- "https://api.sambanova.ai/v1",
252
- "Meta-Llama-3.3-70B-Instruct",
253
- messages, task_id, session_id, temperature, max_tokens,
254
- key_pool=self._sambanova_pool, key_obj=key_obj
255
- )
256
-
257
- # ─── GitHub Models Provider ───────────────────────────────────────────────
258
 
259
- async def _call_github_models(
260
- self, messages: List[Dict],
261
- task_id: str, session_id: str,
262
- temperature: float, max_tokens: int
263
- ) -> Optional[str]:
264
- if not self._github_pool.available():
265
- return None
266
- key_obj = self._github_pool.pick()
267
- if not key_obj:
268
- return None
269
-
270
- return await self._call_openai_compat(
271
- "github", key_obj["key"],
272
- "https://models.inference.ai.azure.com",
273
- "gpt-4o",
274
- messages, task_id, session_id, temperature, max_tokens,
275
- key_pool=self._github_pool, key_obj=key_obj
276
- )
277
-
278
- # ─── OpenAI-Compatible Stream ─────────────────────────────────────────────
279
-
280
- async def _call_openai_compat(
281
- self,
282
- provider_name: str,
283
- api_key: str,
284
- base_url: str,
285
- model: str,
286
- messages: List[Dict],
287
- task_id: str,
288
- session_id: str,
289
- temperature: float,
290
- max_tokens: int,
291
- key_pool: Optional[KeyPool] = None,
292
- key_obj: Optional[Dict] = None,
293
- ) -> Optional[str]:
294
- headers = {
295
- "Authorization": f"Bearer {api_key}",
296
- "Content-Type": "application/json",
297
- }
298
  payload = {
299
  "model": model,
300
  "messages": messages,
@@ -302,47 +140,36 @@ class AIRouter:
302
  "temperature": temperature,
303
  "max_tokens": max_tokens,
304
  }
305
-
306
  full_text = ""
307
- try:
308
- async with httpx.AsyncClient(timeout=120) as client:
309
- async with client.stream(
310
- "POST", f"{base_url}/chat/completions",
311
- headers=headers, json=payload
312
- ) as resp:
313
- if resp.status_code not in (200, 206):
314
- if key_pool and key_obj:
315
- key_pool.mark_fail(key_obj)
316
- raise Exception(f"{provider_name} HTTP {resp.status_code}")
317
- async for line in resp.aiter_lines():
318
- if not line.startswith("data:"):
319
- continue
320
- chunk_str = line[6:].strip()
321
- if chunk_str == "[DONE]":
322
- break
323
- try:
324
- data = json.loads(chunk_str)
325
- delta = data["choices"][0]["delta"].get("content", "")
326
- if delta:
327
- full_text += delta
328
- await self._emit_chunk(delta, task_id, session_id)
329
- except Exception:
330
- pass
331
- if key_pool and key_obj:
332
- key_pool.mark_ok(key_obj)
333
- return full_text if full_text else None
334
- except Exception as e:
335
- if key_pool and key_obj:
336
- key_pool.mark_fail(key_obj)
337
- raise e
338
 
339
- # ─── Anthropic ────────────────────────────────────────────────────────────
340
 
341
- async def _call_anthropic(
342
- self, messages: List[Dict],
343
- task_id: str, session_id: str,
344
- temperature: float, max_tokens: int
345
- ) -> Optional[str]:
346
  system = ""
347
  filtered = []
348
  for m in messages:
@@ -350,26 +177,19 @@ class AIRouter:
350
  system = m["content"]
351
  else:
352
  filtered.append(m)
353
-
354
  payload = {
355
- "model": "claude-3-5-sonnet-20241022",
356
  "max_tokens": max_tokens,
357
  "messages": filtered,
358
  "stream": True,
359
  }
360
  if system:
361
  payload["system"] = system
362
-
363
  full_text = ""
364
  async with httpx.AsyncClient(timeout=120) as client:
365
  async with client.stream(
366
- "POST", "https://api.anthropic.com/v1/messages",
367
- headers={
368
- "x-api-key": ANTHROPIC_KEY,
369
- "anthropic-version": "2023-06-01",
370
- "Content-Type": "application/json",
371
- },
372
- json=payload
373
  ) as resp:
374
  resp.raise_for_status()
375
  async for line in resp.aiter_lines():
@@ -384,44 +204,37 @@ class AIRouter:
384
  await self._emit_chunk(delta, task_id, session_id)
385
  except Exception:
386
  pass
387
- return full_text if full_text else None
388
 
389
  # ─── Demo Stream ──────────────────────────────────────────────────────────
390
 
391
- async def _demo_stream(self, messages: List[Dict], task_id: str, session_id: str) -> str:
392
  last_user = next(
393
- (m["content"] for m in reversed(messages) if m.get("role") == "user"), "Hello"
394
  )
395
- burmese = any("ဘာ" in m.get("content", "") or "မြန်မာ" in m.get("content", "") for m in messages)
396
-
397
- if burmese:
398
- response = (
399
- f"🤖 **God Agent OS V7** (ပြင်ပ AI Key မသတ်မှတ်ရသေးပါ)\n\n"
400
- f"သင်၏ message: *{last_user[:100]}*\n\n"
401
- f"**ရရှိနိုင်သော LLM Providers:**\n"
402
- f"- Gemini 2.0 Flash ({len(GEMINI_KEYS)} keys)\n"
403
- f"- SambaNova Llama 3.3 70B ({len(SAMBANOVA_KEYS)} keys)\n"
404
- f"- GitHub Models GPT-4o ({len(GITHUB_KEYS)} keys)\n\n"
405
- f"**HF Space secrets set လုပ်ပေးပါ** — GEMINI_KEYS, SAMBANOVA_KEYS, GITHUB_KEYS"
406
- )
407
- else:
408
- response = (
409
- f"🤖 **God Agent OS V7** — AI Operating System\n\n"
410
- f"Your message: *{last_user[:100]}*\n\n"
411
- f"**LLM Providers configured:**\n"
412
- f"- 🌟 Gemini 2.0 Flash ({len(GEMINI_KEYS)} keys loaded)\n"
413
- f"- ⚡ SambaNova Llama 3.3 70B ({len(SAMBANOVA_KEYS)} keys loaded)\n"
414
- f"- 🔮 GitHub Models GPT-4o ({len(GITHUB_KEYS)} keys loaded)\n\n"
415
- f"Set HF Space secrets to activate real AI responses."
416
- )
417
-
418
- full = ""
419
  for word in response.split():
420
  chunk = word + " "
421
- full += chunk
422
  await asyncio.sleep(0.02)
423
  await self._emit_chunk(chunk, task_id, session_id, demo=True)
424
- return full
425
 
426
  # ─── Emit Helper ──────────────────────────────────────────────────────────
427
 
@@ -437,38 +250,15 @@ class AIRouter:
437
  # ─── Stats ────────────────────────────────────────────────────────────────
438
 
439
  def get_stats(self) -> Dict:
440
- return {
441
- "gemini": {
442
- "calls": 0,
443
- "errors": 0,
444
- "avg_latency_ms": 0,
445
- "available": self._gemini_pool.available(),
446
- "keys": len(GEMINI_KEYS),
447
- },
448
- "sambanova": {
449
- "calls": 0,
450
- "errors": 0,
451
- "avg_latency_ms": 0,
452
- "available": self._sambanova_pool.available(),
453
- "keys": len(SAMBANOVA_KEYS),
454
- },
455
- "github_models": {
456
- "calls": 0,
457
- "errors": 0,
458
- "avg_latency_ms": 0,
459
- "available": self._github_pool.available(),
460
- "keys": len(GITHUB_KEYS),
461
- },
462
- "openai": {
463
- "available": bool(OPENAI_KEY),
464
- "keys": 1 if OPENAI_KEY else 0,
465
- },
466
- "groq": {
467
- "available": bool(GROQ_KEY),
468
- "keys": 1 if GROQ_KEY else 0,
469
- },
470
- "anthropic": {
471
- "available": bool(ANTHROPIC_KEY),
472
- "keys": 1 if ANTHROPIC_KEY else 0,
473
- },
474
- }
 
1
  """
2
+ Multi-Model AI RouterPhase 9
3
+ Supports: OpenAI, Groq, Cerebras, OpenRouter, HuggingFace
4
+ Automatic failover chain: OpenAI Groq → Cerebras → OpenRouter → HF
5
  """
6
 
7
  import asyncio
8
  import json
9
  import os
10
  import time
11
+ from typing import Any, Dict, List, Optional
 
12
 
13
  import httpx
14
  import structlog
15
 
16
  log = structlog.get_logger()
17
 
18
+ # ─── Provider Config ──────────────────────────────────────────────────────────
19
+ PROVIDERS = [
20
+ {
21
+ "name": "openai",
22
+ "key_env": "OPENAI_API_KEY",
23
+ "base_url": os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1"),
24
+ "default_model": os.environ.get("DEFAULT_MODEL", "gpt-4o"),
25
+ "headers_fn": lambda k: {"Authorization": f"Bearer {k}", "Content-Type": "application/json"},
26
+ },
27
+ {
28
+ "name": "groq",
29
+ "key_env": "GROQ_API_KEY",
30
+ "base_url": "https://api.groq.com/openai/v1",
31
+ "default_model": "llama-3.3-70b-versatile",
32
+ "headers_fn": lambda k: {"Authorization": f"Bearer {k}", "Content-Type": "application/json"},
33
+ },
34
+ {
35
+ "name": "cerebras",
36
+ "key_env": "CEREBRAS_API_KEY",
37
+ "base_url": "https://api.cerebras.ai/v1",
38
+ "default_model": "llama3.1-70b",
39
+ "headers_fn": lambda k: {"Authorization": f"Bearer {k}", "Content-Type": "application/json"},
40
+ },
41
+ {
42
+ "name": "openrouter",
43
+ "key_env": "OPENROUTER_API_KEY",
44
+ "base_url": "https://openrouter.ai/api/v1",
45
+ "default_model": "meta-llama/llama-3.3-70b-instruct:free",
46
+ "headers_fn": lambda k: {
47
+ "Authorization": f"Bearer {k}",
48
+ "Content-Type": "application/json",
49
+ "HTTP-Referer": "https://god-agent.ai",
50
+ "X-Title": "God Agent Platform",
51
+ },
52
+ },
53
+ {
54
+ "name": "anthropic",
55
+ "key_env": "ANTHROPIC_API_KEY",
56
+ "base_url": "https://api.anthropic.com/v1",
57
+ "default_model": "claude-3-5-sonnet-20241022",
58
+ "headers_fn": lambda k: {
59
+ "x-api-key": k,
60
+ "anthropic-version": "2023-06-01",
61
+ "Content-Type": "application/json",
62
+ },
63
+ },
64
+ ]
65
 
66
 
67
  class AIRouter:
68
  """
69
+ God Mode AI Router automatically routes and fails over across providers.
70
+ Supports streaming token output via WebSocket.
71
  """
72
 
73
  def __init__(self, ws_manager=None):
74
  self.ws = ws_manager
75
+ self._stats: Dict[str, Dict] = {p["name"]: {"calls": 0, "errors": 0, "latency": []} for p in PROVIDERS}
 
 
 
76
 
77
+ def _get_provider(self, name: str) -> Optional[Dict]:
78
+ return next((p for p in PROVIDERS if p["name"] == name), None)
79
 
80
+ def _available_providers(self) -> List[Dict]:
81
+ """Return providers with valid API keys, in priority order."""
82
+ return [p for p in PROVIDERS if os.environ.get(p["key_env"], "")]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  # ─── Main Entry Point ─────────────────────────────────────────────────────
85
 
 
93
  preferred_model: str = "",
94
  stream: bool = True,
95
  ) -> str:
96
+ """Route completion through available providers with failover."""
97
+ providers = self._available_providers()
98
+
99
+ if not providers:
100
+ return await self._demo_stream(messages, task_id, session_id)
101
 
102
  last_error = None
103
+ for provider in providers:
104
  try:
105
+ start = time.time()
106
+ if provider["name"] == "anthropic":
107
+ result = await self._anthropic_stream(
108
+ provider, messages, task_id, session_id, temperature, max_tokens
109
+ )
110
+ else:
111
+ result = await self._openai_compat_stream(
112
+ provider, messages, task_id, session_id, temperature, max_tokens, preferred_model
113
+ )
114
+ elapsed = time.time() - start
115
+ self._stats[provider["name"]]["calls"] += 1
116
+ self._stats[provider["name"]]["latency"].append(elapsed)
117
+ log.info("AI Router success", provider=provider["name"], ms=round(elapsed * 1000))
118
+ return result
119
  except Exception as e:
120
  last_error = e
121
+ self._stats[provider["name"]]["errors"] += 1
122
+ log.warning("AI Router failover", provider=provider["name"], error=str(e))
123
  continue
124
 
125
+ log.error("All AI providers failed", last_error=str(last_error))
 
126
  return await self._demo_stream(messages, task_id, session_id)
127
 
128
+ # ─── OpenAI-compatible Stream (OpenAI, Groq, Cerebras, OpenRouter) ────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
+ async def _openai_compat_stream(
131
+ self, provider, messages, task_id, session_id, temperature, max_tokens, preferred_model
132
+ ) -> str:
133
+ key = os.environ.get(provider["key_env"], "")
134
+ model = preferred_model or provider["default_model"]
135
+ headers = provider["headers_fn"](key)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  payload = {
137
  "model": model,
138
  "messages": messages,
 
140
  "temperature": temperature,
141
  "max_tokens": max_tokens,
142
  }
 
143
  full_text = ""
144
+ async with httpx.AsyncClient(timeout=120) as client:
145
+ async with client.stream(
146
+ "POST", f"{provider['base_url']}/chat/completions",
147
+ headers=headers, json=payload
148
+ ) as resp:
149
+ resp.raise_for_status()
150
+ async for line in resp.aiter_lines():
151
+ if not line.startswith("data:"):
152
+ continue
153
+ chunk = line[6:].strip()
154
+ if chunk == "[DONE]":
155
+ break
156
+ try:
157
+ data = json.loads(chunk)
158
+ delta = data["choices"][0]["delta"].get("content", "")
159
+ if delta:
160
+ full_text += delta
161
+ await self._emit_chunk(delta, task_id, session_id)
162
+ except Exception:
163
+ pass
164
+ return full_text
 
 
 
 
 
 
 
 
 
 
165
 
166
+ # ─── Anthropic Stream ─────────────────────────────────────────────────────
167
 
168
+ async def _anthropic_stream(
169
+ self, provider, messages, task_id, session_id, temperature, max_tokens
170
+ ) -> str:
171
+ key = os.environ.get(provider["key_env"], "")
172
+ headers = provider["headers_fn"](key)
173
  system = ""
174
  filtered = []
175
  for m in messages:
 
177
  system = m["content"]
178
  else:
179
  filtered.append(m)
 
180
  payload = {
181
+ "model": provider["default_model"],
182
  "max_tokens": max_tokens,
183
  "messages": filtered,
184
  "stream": True,
185
  }
186
  if system:
187
  payload["system"] = system
 
188
  full_text = ""
189
  async with httpx.AsyncClient(timeout=120) as client:
190
  async with client.stream(
191
+ "POST", f"{provider['base_url']}/messages",
192
+ headers=headers, json=payload
 
 
 
 
 
193
  ) as resp:
194
  resp.raise_for_status()
195
  async for line in resp.aiter_lines():
 
204
  await self._emit_chunk(delta, task_id, session_id)
205
  except Exception:
206
  pass
207
+ return full_text
208
 
209
  # ─── Demo Stream ──────────────────────────────────────────────────────────
210
 
211
+ async def _demo_stream(self, messages, task_id, session_id) -> str:
212
  last_user = next(
213
+ (m["content"] for m in reversed(messages) if m["role"] == "user"), "Hello"
214
  )
215
+ response = (
216
+ f"🤖 **God Agent** (Demo Mode)\n\n"
217
+ f"Received: *{last_user[:100]}*\n\n"
218
+ f"To enable real AI, set one of these env vars:\n"
219
+ f"- `OPENAI_API_KEY` (GPT-4o)\n"
220
+ f"- `GROQ_API_KEY` (Llama 3.3 70B — Free)\n"
221
+ f"- `OPENROUTER_API_KEY` (Multi-model)\n"
222
+ f"- `ANTHROPIC_API_KEY` (Claude 3.5)\n\n"
223
+ f"**God Mode+ Capabilities Active:**\n"
224
+ f"- Multi-agent orchestration\n"
225
+ f"- 🔧 Autonomous coding & debugging\n"
226
+ f"- 🧠 Persistent memory system\n"
227
+ f"- 🔌 Connector ecosystem\n"
228
+ f"- 📡 Real-time streaming\n"
229
+ f"- 🌐 Multi-model failover\n"
230
+ )
231
+ full_text = ""
 
 
 
 
 
 
 
232
  for word in response.split():
233
  chunk = word + " "
234
+ full_text += chunk
235
  await asyncio.sleep(0.02)
236
  await self._emit_chunk(chunk, task_id, session_id, demo=True)
237
+ return full_text
238
 
239
  # ─── Emit Helper ──────────────────────────────────────────────────────────
240
 
 
250
  # ─── Stats ────────────────────────────────────────────────────────────────
251
 
252
  def get_stats(self) -> Dict:
253
+ stats = {}
254
+ for name, s in self._stats.items():
255
+ avg_lat = round(sum(s["latency"][-20:]) / max(len(s["latency"][-20:]), 1) * 1000, 1)
256
+ stats[name] = {
257
+ "calls": s["calls"],
258
+ "errors": s["errors"],
259
+ "avg_latency_ms": avg_lat,
260
+ "available": bool(os.environ.get(
261
+ next((p["key_env"] for p in PROVIDERS if p["name"] == name), ""), ""
262
+ )),
263
+ }
264
+ return stats
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
api/routes/agents.py CHANGED
@@ -1,11 +1,9 @@
1
  """
2
- God Agent Orchestrator API Routes — GOD MODE+ V5
3
- Added: sandbox execute/write/workspace endpoints with timeout guards
4
  """
5
  from fastapi import APIRouter, Request, HTTPException
6
  from pydantic import BaseModel
7
  from typing import Optional, Dict, Any
8
- import asyncio
9
 
10
  router = APIRouter()
11
 
@@ -21,17 +19,16 @@ class SandboxExecRequest(BaseModel):
21
  command: str
22
  cwd: str = ""
23
  timeout: int = 30
24
- session_id: str = "default"
25
 
26
 
27
  class FileWriteRequest(BaseModel):
28
  filename: str
29
  content: str
30
- session_id: str = "default"
31
 
32
 
33
  @router.post("/orchestrate")
34
  async def orchestrate(req: OrchestrateRequest, request: Request):
 
35
  orchestrator = getattr(request.app.state, "orchestrator", None)
36
  if not orchestrator:
37
  raise HTTPException(500, "Orchestrator not initialized")
@@ -46,6 +43,7 @@ async def orchestrate(req: OrchestrateRequest, request: Request):
46
 
47
  @router.get("/status")
48
  async def agent_status(request: Request):
 
49
  orchestrator = getattr(request.app.state, "orchestrator", None)
50
  if not orchestrator:
51
  return {"status": "not_initialized"}
@@ -54,26 +52,18 @@ async def agent_status(request: Request):
54
 
55
  @router.post("/sandbox/execute")
56
  async def sandbox_execute(req: SandboxExecRequest, request: Request):
57
- """Execute command in sandbox — with timeout guard to prevent app freeze."""
58
  orchestrator = getattr(request.app.state, "orchestrator", None)
59
  if not orchestrator:
60
  raise HTTPException(500, "Orchestrator not initialized")
61
  sandbox = orchestrator.get_agent("sandbox")
62
  if not sandbox:
63
  raise HTTPException(503, "SandboxAgent not available")
64
- try:
65
- result = await asyncio.wait_for(
66
- sandbox.execute(req.command, cwd=req.cwd, timeout=req.timeout, session_id=req.session_id),
67
- timeout=float(req.timeout) + 5.0,
68
- )
69
- except asyncio.TimeoutError:
70
- result = f"⏱️ Command timed out after {req.timeout}s"
71
- except Exception as e:
72
- result = f"❌ Error: {str(e)}"
73
- return {"result": result, "command": req.command, "session_id": req.session_id}
74
-
75
-
76
- @router.post("/sandbox/write")
77
  async def sandbox_write_file(req: FileWriteRequest, request: Request):
78
  """Write file to sandbox workspace."""
79
  orchestrator = getattr(request.app.state, "orchestrator", None)
@@ -82,32 +72,27 @@ async def sandbox_write_file(req: FileWriteRequest, request: Request):
82
  sandbox = orchestrator.get_agent("sandbox")
83
  if not sandbox:
84
  raise HTTPException(503, "SandboxAgent not available")
85
- result = await sandbox.write_file(req.filename, req.content, session_id=req.session_id)
86
- return {"result": result, "filename": req.filename, "session_id": req.session_id}
87
-
88
-
89
- @router.post("/sandbox/file")
90
- async def sandbox_write_file_compat(req: FileWriteRequest, request: Request):
91
- """Backwards compat alias for /sandbox/write."""
92
- return await sandbox_write_file(req, request)
93
 
94
 
95
  @router.get("/sandbox/workspace")
96
- async def sandbox_workspace(request: Request, session_id: str = "default"):
97
- """Get workspace file listing for a session."""
98
  orchestrator = getattr(request.app.state, "orchestrator", None)
99
  if not orchestrator:
100
  raise HTTPException(500, "Orchestrator not initialized")
101
  sandbox = orchestrator.get_agent("sandbox")
102
  if not sandbox:
103
  raise HTTPException(503, "SandboxAgent not available")
104
- info = await sandbox.get_workspace_info(session_id=session_id)
105
  return info
106
 
107
 
108
  @router.get("/ai-router/stats")
109
  async def ai_router_stats(request: Request):
 
110
  ai_router = getattr(request.app.state, "ai_router", None)
111
  if not ai_router:
112
  return {"stats": {}}
113
- return {"stats": ai_router.get_provider_status()}
 
1
  """
2
+ God Agent Orchestrator API Routes
 
3
  """
4
  from fastapi import APIRouter, Request, HTTPException
5
  from pydantic import BaseModel
6
  from typing import Optional, Dict, Any
 
7
 
8
  router = APIRouter()
9
 
 
19
  command: str
20
  cwd: str = ""
21
  timeout: int = 30
 
22
 
23
 
24
  class FileWriteRequest(BaseModel):
25
  filename: str
26
  content: str
 
27
 
28
 
29
  @router.post("/orchestrate")
30
  async def orchestrate(req: OrchestrateRequest, request: Request):
31
+ """Route message through God Agent Orchestrator."""
32
  orchestrator = getattr(request.app.state, "orchestrator", None)
33
  if not orchestrator:
34
  raise HTTPException(500, "Orchestrator not initialized")
 
43
 
44
  @router.get("/status")
45
  async def agent_status(request: Request):
46
+ """Get all agent statuses."""
47
  orchestrator = getattr(request.app.state, "orchestrator", None)
48
  if not orchestrator:
49
  return {"status": "not_initialized"}
 
52
 
53
  @router.post("/sandbox/execute")
54
  async def sandbox_execute(req: SandboxExecRequest, request: Request):
55
+ """Execute command in sandbox."""
56
  orchestrator = getattr(request.app.state, "orchestrator", None)
57
  if not orchestrator:
58
  raise HTTPException(500, "Orchestrator not initialized")
59
  sandbox = orchestrator.get_agent("sandbox")
60
  if not sandbox:
61
  raise HTTPException(503, "SandboxAgent not available")
62
+ result = await sandbox.execute(req.command, cwd=req.cwd, timeout=req.timeout)
63
+ return {"result": result, "command": req.command}
64
+
65
+
66
+ @router.post("/sandbox/file")
 
 
 
 
 
 
 
 
67
  async def sandbox_write_file(req: FileWriteRequest, request: Request):
68
  """Write file to sandbox workspace."""
69
  orchestrator = getattr(request.app.state, "orchestrator", None)
 
72
  sandbox = orchestrator.get_agent("sandbox")
73
  if not sandbox:
74
  raise HTTPException(503, "SandboxAgent not available")
75
+ result = await sandbox.write_file(req.filename, req.content)
76
+ return {"result": result, "filename": req.filename}
 
 
 
 
 
 
77
 
78
 
79
  @router.get("/sandbox/workspace")
80
+ async def sandbox_workspace(request: Request):
81
+ """Get workspace info."""
82
  orchestrator = getattr(request.app.state, "orchestrator", None)
83
  if not orchestrator:
84
  raise HTTPException(500, "Orchestrator not initialized")
85
  sandbox = orchestrator.get_agent("sandbox")
86
  if not sandbox:
87
  raise HTTPException(503, "SandboxAgent not available")
88
+ info = await sandbox.get_workspace_info()
89
  return info
90
 
91
 
92
  @router.get("/ai-router/stats")
93
  async def ai_router_stats(request: Request):
94
+ """Get AI router statistics."""
95
  ai_router = getattr(request.app.state, "ai_router", None)
96
  if not ai_router:
97
  return {"stats": {}}
98
+ return {"stats": ai_router.get_stats()}
api/routes/chat.py CHANGED
@@ -1,16 +1,12 @@
1
  """
2
- Chat API Routes — GOD MODE+ V5
3
- Real SSE streaming with Gemini/SambaNova/GitHub Models/OpenAI/Groq
4
- No demo mode when keys exist. Fixed provider routing.
5
  """
6
 
7
  import asyncio
8
  import json
9
- import os
10
  import time
11
  import uuid
12
 
13
- import httpx
14
  from fastapi import APIRouter, HTTPException, Request
15
  from fastapi.responses import StreamingResponse
16
 
@@ -20,10 +16,6 @@ from memory.db import save_memory, get_history
20
  router = APIRouter()
21
 
22
 
23
- def get_ai_router(request: Request):
24
- return getattr(request.app.state, "ai_router", None)
25
-
26
-
27
  def get_engine(request: Request):
28
  return request.app.state.task_engine
29
 
@@ -32,235 +24,133 @@ def get_ws(request: Request):
32
  return request.app.state.ws_manager
33
 
34
 
35
- # ─── SSE stream generators ─────────────────────────────────────────────────────
36
-
37
- async def _gemini_stream_sse(messages, key, model="gemini-2.0-flash"):
38
- contents = []
39
- system_txt = ""
40
- for m in messages:
41
- if m["role"] == "system":
42
- system_txt = m["content"]
43
- elif m["role"] == "user":
44
- contents.append({"role": "user", "parts": [{"text": m["content"]}]})
45
- elif m["role"] == "assistant":
46
- contents.append({"role": "model", "parts": [{"text": m["content"]}]})
47
-
48
- if not contents:
49
- contents = [{"role": "user", "parts": [{"text": "Hello"}]}]
50
-
51
- payload = {"contents": contents, "generationConfig": {"maxOutputTokens": 2048, "temperature": 0.7}}
52
- if system_txt:
53
- payload["systemInstruction"] = {"parts": [{"text": system_txt}]}
54
-
55
- url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:streamGenerateContent?alt=sse&key={key}"
56
- full = ""
57
- try:
58
- async with httpx.AsyncClient(timeout=90) as client:
59
- async with client.stream("POST", url, json=payload) as resp:
60
- if resp.status_code != 200:
61
- error_body = await resp.aread()
62
- raise Exception(f"Gemini {resp.status_code}: {error_body[:200]}")
63
- async for line in resp.aiter_lines():
64
- if not line.startswith("data:"):
65
- continue
66
- chunk_str = line[5:].strip()
67
- if not chunk_str or chunk_str == "[DONE]":
68
- continue
69
- try:
70
- data = json.loads(chunk_str)
71
- delta = (
72
- data.get("candidates", [{}])[0]
73
- .get("content", {})
74
- .get("parts", [{}])[0]
75
- .get("text", "")
76
- )
77
- if delta:
78
- full += delta
79
- yield delta, full, None
80
- except Exception:
81
- pass
82
- except Exception as e:
83
- yield None, full, str(e)
84
-
85
-
86
- async def _openai_compat_stream_sse(messages, api_key, base_url, model):
87
- headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
88
- payload = {"model": model, "messages": messages, "stream": True, "max_tokens": 2048, "temperature": 0.7}
89
- url = f"{base_url.rstrip('/')}/chat/completions"
90
- full = ""
91
- try:
92
- async with httpx.AsyncClient(timeout=90) as client:
93
- async with client.stream("POST", url, headers=headers, json=payload) as resp:
94
- if resp.status_code != 200:
95
- error_body = await resp.aread()
96
- raise Exception(f"{base_url} {resp.status_code}: {error_body[:200]}")
97
- async for line in resp.aiter_lines():
98
- if not line.startswith("data:"):
99
- continue
100
- chunk_str = line[6:].strip()
101
- if chunk_str == "[DONE]":
102
- break
103
- try:
104
- data = json.loads(chunk_str)
105
- delta = data["choices"][0]["delta"].get("content", "")
106
- if delta:
107
- full += delta
108
- yield delta, full, None
109
- except Exception:
110
- pass
111
- except Exception as e:
112
- yield None, full, str(e)
113
 
 
 
 
 
 
114
 
115
- async def _sse_generator(req: ChatRequest, ai_router):
116
- """Main SSE generator — tries all providers, no demo if keys exist."""
117
  messages = [{"role": m.role, "content": m.content} for m in req.messages]
118
- session_id = req.session_id
119
- full_response = ""
120
- provider_tried = False
121
-
122
- providers = ai_router._available_providers() if ai_router else []
123
-
124
- for name in providers:
125
- pool = ai_router.pools.get(name)
126
- if not pool:
127
- continue
128
- key = pool.pick()
129
- if not key:
130
- continue
131
-
132
- cfg = ai_router.provider_configs.get(name, {})
133
- provider_tried = True
134
- success = False
135
 
136
- try:
137
- if name == "gemini":
138
- async for delta, full, err in _gemini_stream_sse(messages, key, cfg.get("model", "gemini-2.0-flash")):
139
- if err:
140
- pool.mark_fail(key)
141
- break
142
- if delta:
143
- full_response = full
144
- yield f"data: {json.dumps({'type':'llm_chunk','data':{'chunk':delta},'session_id':session_id})}\n\n"
145
- if full_response:
146
- pool.mark_success(key)
147
- success = True
148
-
149
- elif name in ("sambanova", "github_models", "openai", "groq", "cerebras", "openrouter"):
150
- base_url = cfg.get("base_url", "https://api.openai.com/v1")
151
- model = cfg.get("model", "gpt-4o")
152
- async for delta, full, err in _openai_compat_stream_sse(messages, key, base_url, model):
153
- if err:
154
- pool.mark_fail(key)
155
- break
156
- if delta:
157
- full_response = full
158
- yield f"data: {json.dumps({'type':'llm_chunk','data':{'chunk':delta},'session_id':session_id})}\n\n"
159
- if full_response:
160
- pool.mark_success(key)
161
- success = True
162
-
163
- elif name == "anthropic":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  headers = {
165
- "x-api-key": key,
166
- "anthropic-version": "2023-06-01",
167
- "content-type": "application/json",
168
  }
169
- anthropic_msgs = [m for m in messages if m["role"] != "system"]
170
- system_txt = next((m["content"] for m in messages if m["role"] == "system"), "")
171
  payload = {
172
- "model": cfg.get("model", "claude-3-5-haiku-20241022"),
173
- "max_tokens": 2048,
174
  "stream": True,
175
- "messages": anthropic_msgs,
176
- "temperature": 0.7,
177
  }
178
- if system_txt:
179
- payload["system"] = system_txt
180
- try:
181
- async with httpx.AsyncClient(timeout=90) as client:
182
- async with client.stream(
183
- "POST", "https://api.anthropic.com/v1/messages",
184
- headers=headers, json=payload
185
- ) as resp:
186
- async for line in resp.aiter_lines():
187
- if not line.startswith("data:"):
188
- continue
189
- chunk_str = line[6:].strip()
190
- try:
191
- data = json.loads(chunk_str)
192
- if data.get("type") == "content_block_delta":
193
- delta = data.get("delta", {}).get("text", "")
194
- if delta:
195
- full_response += delta
196
- yield f"data: {json.dumps({'type':'llm_chunk','data':{'chunk':delta},'session_id':session_id})}\n\n"
197
- except Exception:
198
- pass
199
- if full_response:
200
- pool.mark_success(key)
201
- success = True
202
- except Exception as e:
203
- pool.mark_fail(key)
204
-
205
- except Exception as e:
206
- pool.mark_fail(key)
207
- continue
 
 
208
 
209
- if success:
210
- break
211
-
212
- # Only show error if truly no response
213
- if not full_response:
214
- if provider_tried:
215
- error_msg = "⚠️ All AI providers returned errors. Please check API keys in Connectors panel or try again."
216
- else:
217
- error_msg = (
218
- "⚠️ No AI providers configured.\n\n"
219
- "Add API keys via **Connectors** panel:\n"
220
- "- Gemini (GEMINI_API_KEYS)\n"
221
- "- SambaNova (SAMBANOVA_API_KEYS)\n"
222
- "- GitHub Models (GITHUB_API_KEYS)"
223
- )
224
- for word in error_msg.split():
225
- chunk = word + " "
226
- full_response += chunk
227
- yield f"data: {json.dumps({'type':'llm_chunk','data':{'chunk':chunk},'session_id':session_id})}\n\n"
228
- await asyncio.sleep(0.01)
229
-
230
- yield f"data: {json.dumps({'type':'stream_end','data':{'full_response':full_response},'session_id':session_id})}\n\n"
231
-
232
-
233
- # ─── Routes ────────────────────────────────────────────────────────────────────
234
-
235
- @router.post("/chat", summary="Chat with God Agent — V5 streaming")
236
- async def chat(req: ChatRequest, request: Request):
237
- ai_router = get_ai_router(request)
238
- if req.stream:
239
  return StreamingResponse(
240
- _sse_generator(req, ai_router),
241
  media_type="text/event-stream",
242
  headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
243
  )
244
  else:
245
- full = ""
246
- async for chunk in _sse_generator(req, ai_router):
247
- if chunk.startswith("data:"):
248
- try:
249
- d = json.loads(chunk[6:].strip())
250
- if d.get("type") == "llm_chunk":
251
- full += d["data"].get("chunk", "")
252
- except Exception:
253
- pass
254
- return {"response": full, "session_id": req.session_id, "timestamp": time.time()}
255
-
256
-
257
- @router.post("/chat/stream", summary="Explicit streaming chat")
258
  async def chat_stream(req: ChatRequest, request: Request):
259
  req.stream = True
260
  return await chat(req, request)
261
 
262
 
263
- @router.post("/goal", summary="Submit high-level goal to task engine")
 
 
264
  async def submit_goal(req: GoalRequest, request: Request):
265
  engine = get_engine(request)
266
  task_req = TaskCreateRequest(
@@ -268,7 +158,7 @@ async def submit_goal(req: GoalRequest, request: Request):
268
  session_id=req.session_id,
269
  project_id=req.project_id,
270
  stream=req.stream,
271
- metadata={"source": "goal_api", "github_repo": req.github_repo or ""},
272
  )
273
  task_id = await engine.submit(task_req)
274
  return {
@@ -277,29 +167,48 @@ async def submit_goal(req: GoalRequest, request: Request):
277
  "status": "queued",
278
  "session_id": req.session_id,
279
  "ws_url": f"/ws/tasks/{task_id}",
 
280
  }
281
 
282
 
283
- @router.post("/goal/stream")
284
  async def submit_goal_stream(req: GoalRequest, request: Request):
285
  req.stream = True
286
  return await submit_goal(req, request)
287
 
288
 
289
- @router.post("/execute")
290
- async def execute(tool: str, task: str, request: Request, session_id: str = ""):
 
 
 
 
 
 
 
291
  from tools.executor import ToolExecutor
292
  ws = get_ws(request)
293
  executor = ToolExecutor(ws)
294
- result = await executor.run(tool=tool, task=task, session_id=session_id)
295
- return {"tool": tool, "task": task, "result": result}
 
 
 
 
296
 
297
 
298
- @router.post("/plan")
 
 
299
  async def generate_plan(req: GoalRequest, request: Request):
300
  from core.agent import AgentCore
301
  ws = get_ws(request)
302
  agent = AgentCore(ws)
303
  task_id = f"plan_{uuid.uuid4().hex[:8]}"
304
  plan = await agent.plan(goal=req.goal, task_id=task_id, session_id=req.session_id)
305
- return {"goal": req.goal, "plan": plan.model_dump(), "task_id": task_id}
 
 
 
 
 
 
1
  """
2
+ Chat + Goal API Routes — Real-time streaming responses
 
 
3
  """
4
 
5
  import asyncio
6
  import json
 
7
  import time
8
  import uuid
9
 
 
10
  from fastapi import APIRouter, HTTPException, Request
11
  from fastapi.responses import StreamingResponse
12
 
 
16
  router = APIRouter()
17
 
18
 
 
 
 
 
19
  def get_engine(request: Request):
20
  return request.app.state.task_engine
21
 
 
24
  return request.app.state.ws_manager
25
 
26
 
27
+ # ─── Chat (REST + SSE streaming) ───────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ @router.post("/chat", summary="Chat with the agent")
30
+ async def chat(req: ChatRequest, request: Request):
31
+ from core.agent import AgentCore
32
+ ws = get_ws(request)
33
+ agent = AgentCore(ws)
34
 
 
 
35
  messages = [{"role": m.role, "content": m.content} for m in req.messages]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
+ if req.stream:
38
+ async def stream_gen():
39
+ async def _run():
40
+ result = await agent.llm_stream(
41
+ messages=messages,
42
+ session_id=req.session_id,
43
+ model=req.model,
44
+ temperature=req.temperature,
45
+ max_tokens=req.max_tokens,
46
+ )
47
+ await save_memory(
48
+ content=result,
49
+ memory_type="conversation",
50
+ session_id=req.session_id,
51
+ project_id=req.project_id,
52
+ key="assistant",
53
+ )
54
+ # Save user message too
55
+ user_msg = next((m["content"] for m in reversed(messages) if m["role"] == "user"), "")
56
+ await save_memory(
57
+ content=user_msg,
58
+ memory_type="conversation",
59
+ session_id=req.session_id,
60
+ project_id=req.project_id,
61
+ key="user",
62
+ )
63
+ return result
64
+
65
+ room_buffer = []
66
+ original_emit_chat = ws.emit_chat
67
+ async def capture_emit(sid, etype, data):
68
+ if etype == "llm_chunk":
69
+ chunk = data.get("chunk", "")
70
+ room_buffer.append(chunk)
71
+ yield_data = json.dumps({"type": etype, "data": data, "session_id": sid})
72
+ return yield_data
73
+ return None
74
+
75
+ # Stream tokens directly
76
+ full = ""
77
+ from core.agent import AgentCore as _A
78
+ import httpx
79
+ import os
80
+ OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
81
+ ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
82
+
83
+ if OPENAI_API_KEY:
84
  headers = {
85
+ "Authorization": f"Bearer {OPENAI_API_KEY}",
86
+ "Content-Type": "application/json",
 
87
  }
 
 
88
  payload = {
89
+ "model": req.model,
90
+ "messages": messages,
91
  "stream": True,
92
+ "temperature": req.temperature,
93
+ "max_tokens": req.max_tokens,
94
  }
95
+ from core.agent import OPENAI_BASE_URL
96
+ async with httpx.AsyncClient(timeout=120) as client:
97
+ async with client.stream("POST", f"{OPENAI_BASE_URL}/chat/completions",
98
+ headers=headers, json=payload) as resp:
99
+ async for line in resp.aiter_lines():
100
+ if not line.startswith("data:"):
101
+ continue
102
+ chunk_str = line[6:].strip()
103
+ if chunk_str == "[DONE]":
104
+ break
105
+ try:
106
+ data = json.loads(chunk_str)
107
+ delta = data["choices"][0]["delta"].get("content", "")
108
+ if delta:
109
+ full += delta
110
+ yield f"data: {json.dumps({'type': 'llm_chunk', 'data': {'chunk': delta}, 'session_id': req.session_id})}\n\n"
111
+ except Exception:
112
+ pass
113
+ else:
114
+ # Demo streaming
115
+ demo = (
116
+ f"Hello! I'm your Devin-style AI Agent. I received: '{req.messages[-1].content[:80]}'. "
117
+ f"Set OPENAI_API_KEY or ANTHROPIC_API_KEY for real AI responses. "
118
+ f"I support real-time streaming, task planning, GitHub automation, and more!"
119
+ )
120
+ for word in demo.split():
121
+ chunk = word + " "
122
+ full += chunk
123
+ await asyncio.sleep(0.04)
124
+ yield f"data: {json.dumps({'type': 'llm_chunk', 'data': {'chunk': chunk}, 'session_id': req.session_id})}\n\n"
125
+
126
+ yield f"data: {json.dumps({'type': 'stream_end', 'data': {'full_response': full}, 'session_id': req.session_id})}\n\n"
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  return StreamingResponse(
129
+ stream_gen(),
130
  media_type="text/event-stream",
131
  headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
132
  )
133
  else:
134
+ # Non-streaming
135
+ agent = AgentCore(get_ws(request))
136
+ result = await agent.llm_stream(messages, session_id=req.session_id)
137
+ return {
138
+ "response": result,
139
+ "session_id": req.session_id,
140
+ "model": req.model,
141
+ "timestamp": time.time(),
142
+ }
143
+
144
+
145
+ @router.post("/chat/stream", summary="Explicit streaming chat endpoint")
 
146
  async def chat_stream(req: ChatRequest, request: Request):
147
  req.stream = True
148
  return await chat(req, request)
149
 
150
 
151
+ # ─── Goal API (create task from goal) ─────────────────────────────────────────
152
+
153
+ @router.post("/goal", summary="Submit a high-level goal to the agent")
154
  async def submit_goal(req: GoalRequest, request: Request):
155
  engine = get_engine(request)
156
  task_req = TaskCreateRequest(
 
158
  session_id=req.session_id,
159
  project_id=req.project_id,
160
  stream=req.stream,
161
+ metadata={"source": "goal_api", "github_repo": req.github_repo},
162
  )
163
  task_id = await engine.submit(task_req)
164
  return {
 
167
  "status": "queued",
168
  "session_id": req.session_id,
169
  "ws_url": f"/ws/tasks/{task_id}",
170
+ "stream_url": f"/api/v1/tasks/{task_id}/stream",
171
  }
172
 
173
 
174
+ @router.post("/goal/stream", summary="Submit goal with SSE streaming response")
175
  async def submit_goal_stream(req: GoalRequest, request: Request):
176
  req.stream = True
177
  return await submit_goal(req, request)
178
 
179
 
180
+ # ─── Execute (direct tool execution) ──────────────────────────────────────────
181
+
182
+ @router.post("/execute", summary="Execute a tool directly")
183
+ async def execute(
184
+ tool: str,
185
+ task: str,
186
+ request: Request,
187
+ session_id: str = "",
188
+ ):
189
  from tools.executor import ToolExecutor
190
  ws = get_ws(request)
191
  executor = ToolExecutor(ws)
192
+ result = await executor.run(
193
+ tool=tool,
194
+ task=task,
195
+ session_id=session_id,
196
+ )
197
+ return {"tool": tool, "task": task, "result": result, "session_id": session_id}
198
 
199
 
200
+ # ─── Plan (generate plan without executing) ───────────────────────────────────
201
+
202
+ @router.post("/plan", summary="Generate execution plan for a goal")
203
  async def generate_plan(req: GoalRequest, request: Request):
204
  from core.agent import AgentCore
205
  ws = get_ws(request)
206
  agent = AgentCore(ws)
207
  task_id = f"plan_{uuid.uuid4().hex[:8]}"
208
  plan = await agent.plan(goal=req.goal, task_id=task_id, session_id=req.session_id)
209
+ return {
210
+ "goal": req.goal,
211
+ "plan": plan.model_dump(),
212
+ "session_id": req.session_id,
213
+ "task_id": task_id,
214
+ }
api/routes/connectors.py CHANGED
@@ -1,21 +1,13 @@
1
  """
2
- Connectors API — GOD MODE+ V5
3
- Fix: No auto-reconnect loop. Runtime tokens tracked separately from HF Secrets.
4
  """
5
- import os
6
- import httpx
7
  from fastapi import APIRouter, Request, HTTPException
8
  from pydantic import BaseModel
9
  from typing import Optional
10
  from connectors.manager import ConnectorManager
11
 
12
  router = APIRouter()
13
-
14
- # Track tokens set at runtime (not from HF Secrets) so we can safely remove them
15
- _runtime_tokens: dict = {}
16
-
17
- # Use a single shared manager instance
18
- _manager = ConnectorManager()
19
 
20
 
21
  class SetTokenRequest(BaseModel):
@@ -23,145 +15,35 @@ class SetTokenRequest(BaseModel):
23
  token: str
24
 
25
 
26
- class DisconnectRequest(BaseModel):
27
- connector_id: str
28
-
29
-
30
  @router.get("/")
31
  async def get_all_connectors():
32
- return {"connectors": _manager.get_all()}
33
 
34
 
35
  @router.get("/connected")
36
  async def get_connected():
37
- return {"connectors": _manager.get_connected()}
38
 
39
 
40
  @router.get("/summary")
41
  async def get_summary():
42
- return _manager.get_summary()
43
 
44
 
45
  @router.get("/category/{category}")
46
  async def get_by_category(category: str):
47
- return {"connectors": _manager.get_by_category(category)}
48
-
49
-
50
- @router.post("/connect")
51
- async def connect_connector(req: SetTokenRequest):
52
- """Connect a connector by setting its token at runtime."""
53
- env_key = _manager.get_env_key(req.connector_id)
54
- if not env_key:
55
- raise HTTPException(400, f"Unknown connector: {req.connector_id}")
56
-
57
- # Set in environment and track as runtime token
58
- os.environ[env_key] = req.token
59
- _runtime_tokens[env_key] = req.token
60
- _manager.set_token(req.connector_id, req.token)
61
-
62
- return {
63
- "status": "connected",
64
- "connector_id": req.connector_id,
65
- "connected": True,
66
- }
67
 
68
 
69
  @router.post("/set-token")
70
  async def set_token(req: SetTokenRequest):
71
- """Alias for /connect for backwards compatibility."""
72
- return await connect_connector(req)
73
-
74
-
75
- @router.post("/disconnect")
76
- async def disconnect_connector(req: DisconnectRequest):
77
- """Disconnect a connector — only removes runtime tokens, not HF Secrets."""
78
- env_key = _manager.get_env_key(req.connector_id)
79
- if not env_key:
80
- raise HTTPException(400, f"Unknown connector: {req.connector_id}")
81
-
82
- # Only remove if we set it at runtime (don't wipe HF Secrets)
83
- if env_key in _runtime_tokens:
84
- os.environ.pop(env_key, None)
85
- del _runtime_tokens[env_key]
86
- _manager.clear_token(req.connector_id)
87
- return {"status": "disconnected", "connector_id": req.connector_id}
88
- else:
89
- # It's a HF Secret — just report it as persistent
90
- return {
91
- "status": "persistent",
92
- "connector_id": req.connector_id,
93
- "message": "Token is a HuggingFace Secret and cannot be removed at runtime.",
94
- }
95
-
96
-
97
- @router.post("/test")
98
- async def test_connector(req: DisconnectRequest):
99
- """Test a connector's API connectivity."""
100
- connector_id = req.connector_id
101
- env_key = _manager.get_env_key(connector_id)
102
- token = os.environ.get(env_key, "") if env_key else ""
103
-
104
- if not token:
105
- return {"connector_id": connector_id, "status": "not_configured", "ok": False}
106
-
107
- try:
108
- if connector_id == "github":
109
- async with httpx.AsyncClient(timeout=10) as client:
110
- r = await client.get(
111
- "https://api.github.com/user",
112
- headers={"Authorization": f"Bearer {token}"},
113
- )
114
- ok = r.status_code == 200
115
- data = r.json() if ok else {}
116
- return {"connector_id": connector_id, "ok": ok, "user": data.get("login", "")}
117
-
118
- elif connector_id == "huggingface":
119
- async with httpx.AsyncClient(timeout=10) as client:
120
- r = await client.get(
121
- "https://huggingface.co/api/whoami-v2",
122
- headers={"Authorization": f"Bearer {token}"},
123
- )
124
- ok = r.status_code == 200
125
- data = r.json() if ok else {}
126
- return {"connector_id": connector_id, "ok": ok, "user": data.get("name", "")}
127
-
128
- elif connector_id == "vercel":
129
- async with httpx.AsyncClient(timeout=10) as client:
130
- r = await client.get(
131
- "https://api.vercel.com/v2/user",
132
- headers={"Authorization": f"Bearer {token}"},
133
- )
134
- ok = r.status_code == 200
135
- data = r.json() if ok else {}
136
- return {"connector_id": connector_id, "ok": ok, "user": data.get("user", {}).get("username", "")}
137
-
138
- elif connector_id == "gemini":
139
- # Test with a tiny generateContent call
140
- url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={token}"
141
- async with httpx.AsyncClient(timeout=15) as client:
142
- r = await client.post(url, json={"contents": [{"parts": [{"text": "hi"}]}]})
143
- ok = r.status_code == 200
144
- return {"connector_id": connector_id, "ok": ok}
145
-
146
- elif connector_id == "sambanova":
147
- async with httpx.AsyncClient(timeout=15) as client:
148
- r = await client.get(
149
- "https://api.sambanova.ai/v1/models",
150
- headers={"Authorization": f"Bearer {token}"},
151
- )
152
- ok = r.status_code == 200
153
- return {"connector_id": connector_id, "ok": ok}
154
-
155
- else:
156
- return {"connector_id": connector_id, "ok": True, "status": "assumed_ok"}
157
-
158
- except Exception as e:
159
- return {"connector_id": connector_id, "ok": False, "error": str(e)}
160
 
161
 
162
  @router.get("/{connector_id}/status")
163
  async def get_status(connector_id: str):
164
  return {
165
  "connector_id": connector_id,
166
- "connected": _manager.is_connected(connector_id),
167
  }
 
1
  """
2
+ Connectors API — Manus-style connector management
 
3
  """
 
 
4
  from fastapi import APIRouter, Request, HTTPException
5
  from pydantic import BaseModel
6
  from typing import Optional
7
  from connectors.manager import ConnectorManager
8
 
9
  router = APIRouter()
10
+ connector_manager = ConnectorManager()
 
 
 
 
 
11
 
12
 
13
  class SetTokenRequest(BaseModel):
 
15
  token: str
16
 
17
 
 
 
 
 
18
  @router.get("/")
19
  async def get_all_connectors():
20
+ return {"connectors": connector_manager.get_all()}
21
 
22
 
23
  @router.get("/connected")
24
  async def get_connected():
25
+ return {"connectors": connector_manager.get_connected()}
26
 
27
 
28
  @router.get("/summary")
29
  async def get_summary():
30
+ return connector_manager.get_summary()
31
 
32
 
33
  @router.get("/category/{category}")
34
  async def get_by_category(category: str):
35
+ return {"connectors": connector_manager.get_by_category(category)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
 
38
  @router.post("/set-token")
39
  async def set_token(req: SetTokenRequest):
40
+ connector_manager.set_token(req.connector_id, req.token)
41
+ return {"status": "ok", "connector": req.connector_id, "connected": True}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
 
44
  @router.get("/{connector_id}/status")
45
  async def get_status(connector_id: str):
46
  return {
47
  "connector_id": connector_id,
48
+ "connected": connector_manager.is_connected(connector_id),
49
  }
api/routes/github.py CHANGED
@@ -1,9 +1,336 @@
1
  """
2
- GitHub API Routes (legacy compat + new)
 
3
  """
4
- from fastapi import APIRouter
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  router = APIRouter()
6
 
7
- @router.get("/info")
8
- async def github_info():
9
- return {"status": "GitHub routes active — use /api/v1/exec/github/* for full ops"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ GitHub Autonomous Engineering API Routes
3
+ Clone, commit, push, PR, issues — all autonomous
4
  """
5
+
6
+ import os
7
+ import time
8
+ import asyncio
9
+ import tempfile
10
+ import shutil
11
+ from typing import Optional
12
+
13
+ import httpx
14
+ from fastapi import APIRouter, HTTPException, Request
15
+
16
+ from core.models import (
17
+ GitHubCloneRequest, GitHubCreateRepoRequest,
18
+ GitHubCommitRequest, GitHubPRRequest, GitHubIssueRequest,
19
+ )
20
+ from memory.db import save_memory
21
+
22
  router = APIRouter()
23
 
24
+ GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
25
+ GITHUB_OWNER = os.environ.get("GITHUB_OWNER", "")
26
+ GITHUB_API = "https://api.github.com"
27
+
28
+
29
+ def gh_headers():
30
+ if not GITHUB_TOKEN:
31
+ raise HTTPException(status_code=400, detail="GITHUB_TOKEN not configured")
32
+ return {
33
+ "Authorization": f"Bearer {GITHUB_TOKEN}",
34
+ "Accept": "application/vnd.github+json",
35
+ "X-GitHub-Api-Version": "2022-11-28",
36
+ }
37
+
38
+
39
+ async def gh_get(path: str) -> dict:
40
+ async with httpx.AsyncClient(timeout=30) as client:
41
+ r = await client.get(f"{GITHUB_API}{path}", headers=gh_headers())
42
+ r.raise_for_status()
43
+ return r.json()
44
+
45
+
46
+ async def gh_post(path: str, data: dict) -> dict:
47
+ async with httpx.AsyncClient(timeout=30) as client:
48
+ r = await client.post(f"{GITHUB_API}{path}", headers=gh_headers(), json=data)
49
+ r.raise_for_status()
50
+ return r.json()
51
+
52
+
53
+ async def gh_put(path: str, data: dict) -> dict:
54
+ async with httpx.AsyncClient(timeout=30) as client:
55
+ r = await client.put(f"{GITHUB_API}{path}", headers=gh_headers(), json=data)
56
+ r.raise_for_status()
57
+ return r.json()
58
+
59
+
60
+ async def gh_patch(path: str, data: dict) -> dict:
61
+ async with httpx.AsyncClient(timeout=30) as client:
62
+ r = await client.patch(f"{GITHUB_API}{path}", headers=gh_headers(), json=data)
63
+ r.raise_for_status()
64
+ return r.json()
65
+
66
+
67
+ # ─── Clone ────────────────────────────────────────────────────────────────────
68
+
69
+ @router.post("/clone", summary="Clone a GitHub repository")
70
+ async def clone_repo(req: GitHubCloneRequest):
71
+ try:
72
+ import git
73
+ except ImportError:
74
+ raise HTTPException(status_code=500, detail="gitpython not installed")
75
+
76
+ local_path = req.local_path or f"/tmp/repos/{req.repo_url.split('/')[-1].replace('.git', '')}"
77
+ os.makedirs(local_path, exist_ok=True)
78
+
79
+ if GITHUB_TOKEN:
80
+ url = req.repo_url.replace("https://", f"https://{GITHUB_TOKEN}@")
81
+ else:
82
+ url = req.repo_url
83
+
84
+ try:
85
+ if os.path.exists(os.path.join(local_path, ".git")):
86
+ repo = git.Repo(local_path)
87
+ repo.remotes.origin.pull()
88
+ action = "pulled"
89
+ else:
90
+ repo = git.Repo.clone_from(url, local_path, branch=req.branch, depth=1)
91
+ action = "cloned"
92
+
93
+ files = []
94
+ for root, dirs, fnames in os.walk(local_path):
95
+ dirs[:] = [d for d in dirs if d not in [".git", "node_modules", "__pycache__"]]
96
+ for f in fnames[:50]:
97
+ files.append(os.path.relpath(os.path.join(root, f), local_path))
98
+
99
+ # Save to memory
100
+ await save_memory(
101
+ content=f"Repo {req.repo_url} cloned to {local_path}. Files: {', '.join(files[:20])}",
102
+ memory_type="repo",
103
+ key=req.repo_url,
104
+ )
105
+
106
+ return {
107
+ "action": action,
108
+ "repo_url": req.repo_url,
109
+ "local_path": local_path,
110
+ "branch": req.branch,
111
+ "files_count": len(files),
112
+ "files": files[:30],
113
+ }
114
+ except Exception as e:
115
+ raise HTTPException(status_code=500, detail=f"Clone failed: {str(e)}")
116
+
117
+
118
+ # ─── Create Repo ──────────────────────────────────────────────────────────────
119
+
120
+ @router.post("/create_repo", summary="Create a new GitHub repository")
121
+ async def create_repo(req: GitHubCreateRepoRequest):
122
+ data = {
123
+ "name": req.name,
124
+ "description": req.description,
125
+ "private": req.private,
126
+ "auto_init": req.auto_init,
127
+ }
128
+ try:
129
+ result = await gh_post("/user/repos", data)
130
+ return {
131
+ "repo": result["full_name"],
132
+ "url": result["html_url"],
133
+ "clone_url": result["clone_url"],
134
+ "default_branch": result.get("default_branch", "main"),
135
+ "private": result["private"],
136
+ }
137
+ except httpx.HTTPStatusError as e:
138
+ raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
139
+
140
+
141
+ # ─── Commit Files ────────��────────────────────────────────────────────────────
142
+
143
+ @router.post("/commit", summary="Commit files to a repository")
144
+ async def commit_files(req: GitHubCommitRequest):
145
+ import base64
146
+
147
+ owner_repo = req.repo if "/" in req.repo else f"{GITHUB_OWNER}/{req.repo}"
148
+ results = []
149
+
150
+ for file_path, content in req.files.items():
151
+ encoded = base64.b64encode(content.encode()).decode()
152
+
153
+ # Get current SHA if file exists
154
+ sha = None
155
+ try:
156
+ existing = await gh_get(f"/repos/{owner_repo}/contents/{file_path}?ref={req.branch}")
157
+ sha = existing.get("sha")
158
+ except Exception:
159
+ pass
160
+
161
+ payload = {
162
+ "message": req.message,
163
+ "content": encoded,
164
+ "branch": req.branch,
165
+ }
166
+ if sha:
167
+ payload["sha"] = sha
168
+
169
+ try:
170
+ result = await gh_put(f"/repos/{owner_repo}/contents/{file_path}", payload)
171
+ results.append({"file": file_path, "status": "committed", "sha": result["content"]["sha"]})
172
+ except Exception as e:
173
+ results.append({"file": file_path, "status": "error", "error": str(e)})
174
+
175
+ return {
176
+ "repo": owner_repo,
177
+ "branch": req.branch,
178
+ "message": req.message,
179
+ "files": results,
180
+ "committed": sum(1 for r in results if r["status"] == "committed"),
181
+ }
182
+
183
+
184
+ # ─── Push ─────────────────────────────────────────────────────────────────────
185
+
186
+ @router.post("/push", summary="Push local changes to remote")
187
+ async def push_changes(
188
+ repo_path: str,
189
+ branch: str = "main",
190
+ message: str = "Auto-commit by Devin Agent",
191
+ ):
192
+ try:
193
+ import git
194
+ repo = git.Repo(repo_path)
195
+ repo.git.add(A=True)
196
+ if repo.index.diff("HEAD") or repo.untracked_files:
197
+ repo.index.commit(message)
198
+ origin = repo.remote("origin")
199
+ origin.push(refspec=f"HEAD:{branch}")
200
+ return {"status": "pushed", "branch": branch, "message": message}
201
+ except Exception as e:
202
+ raise HTTPException(status_code=500, detail=f"Push failed: {str(e)}")
203
+
204
+
205
+ # ─── Create PR ────────────────────────────────────────────────────────────────
206
+
207
+ @router.post("/pr/create", summary="Create a Pull Request")
208
+ async def create_pr(req: GitHubPRRequest):
209
+ owner_repo = req.repo if "/" in req.repo else f"{GITHUB_OWNER}/{req.repo}"
210
+ data = {
211
+ "title": req.title,
212
+ "body": req.body,
213
+ "head": req.head,
214
+ "base": req.base,
215
+ "draft": req.draft,
216
+ }
217
+ try:
218
+ result = await gh_post(f"/repos/{owner_repo}/pulls", data)
219
+ return {
220
+ "pr_number": result["number"],
221
+ "title": result["title"],
222
+ "url": result["html_url"],
223
+ "state": result["state"],
224
+ "head": req.head,
225
+ "base": req.base,
226
+ }
227
+ except httpx.HTTPStatusError as e:
228
+ raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
229
+
230
+
231
+ # ─── Create Issue ─────────────────────────────────────────────────────────────
232
+
233
+ @router.post("/issues/create", summary="Create a GitHub Issue")
234
+ async def create_issue(req: GitHubIssueRequest):
235
+ owner_repo = req.repo if "/" in req.repo else f"{GITHUB_OWNER}/{req.repo}"
236
+ data = {"title": req.title, "body": req.body, "labels": req.labels}
237
+ try:
238
+ result = await gh_post(f"/repos/{owner_repo}/issues", data)
239
+ return {
240
+ "issue_number": result["number"],
241
+ "title": result["title"],
242
+ "url": result["html_url"],
243
+ "state": result["state"],
244
+ }
245
+ except httpx.HTTPStatusError as e:
246
+ raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
247
+
248
+
249
+ # ─── Code Review ──────────────────────────────────────────────────────────────
250
+
251
+ @router.post("/review", summary="AI code review for a PR")
252
+ async def review_pr(repo: str, pr_number: int, request: Request):
253
+ owner_repo = repo if "/" in repo else f"{GITHUB_OWNER}/{repo}"
254
+ try:
255
+ pr = await gh_get(f"/repos/{owner_repo}/pulls/{pr_number}")
256
+ files = await gh_get(f"/repos/{owner_repo}/pulls/{pr_number}/files")
257
+
258
+ file_changes = []
259
+ for f in files[:10]:
260
+ file_changes.append(f"{f['filename']}: +{f.get('additions',0)}/-{f.get('deletions',0)}")
261
+
262
+ ws = request.app.state.ws_manager
263
+ from core.agent import AgentCore
264
+ agent = AgentCore(ws)
265
+
266
+ review_prompt = (
267
+ f"Review this Pull Request:\n"
268
+ f"Title: {pr['title']}\n"
269
+ f"Description: {pr.get('body', 'No description')}\n"
270
+ f"Files changed: {chr(10).join(file_changes)}\n\n"
271
+ f"Provide a constructive code review with: summary, potential issues, suggestions, and verdict."
272
+ )
273
+ messages = [
274
+ {"role": "system", "content": "You are a senior software engineer doing code review. Be constructive, specific, and helpful."},
275
+ {"role": "user", "content": review_prompt},
276
+ ]
277
+ review = await agent.llm_stream(messages)
278
+
279
+ # Post review comment
280
+ if GITHUB_TOKEN:
281
+ await gh_post(f"/repos/{owner_repo}/issues/{pr_number}/comments", {"body": f"🤖 **Devin Agent Code Review**\n\n{review}"})
282
+
283
+ return {
284
+ "pr_number": pr_number,
285
+ "title": pr["title"],
286
+ "review": review,
287
+ "files_reviewed": len(files),
288
+ "posted_to_github": bool(GITHUB_TOKEN),
289
+ }
290
+ except Exception as e:
291
+ raise HTTPException(status_code=500, detail=str(e))
292
+
293
+
294
+ # ─── Repo Info ────────────────────────────────────────────────────────────────
295
+
296
+ @router.get("/repo/{owner}/{repo}", summary="Get repository info")
297
+ async def get_repo_info(owner: str, repo: str):
298
+ try:
299
+ info = await gh_get(f"/repos/{owner}/{repo}")
300
+ return {
301
+ "name": info["name"],
302
+ "full_name": info["full_name"],
303
+ "description": info.get("description"),
304
+ "url": info["html_url"],
305
+ "default_branch": info["default_branch"],
306
+ "language": info.get("language"),
307
+ "stars": info["stargazers_count"],
308
+ "forks": info["forks_count"],
309
+ "open_issues": info["open_issues_count"],
310
+ "private": info["private"],
311
+ }
312
+ except httpx.HTTPStatusError as e:
313
+ raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
314
+
315
+
316
+ # ─── Status check ─────────────────────────────────────────────────────────────
317
+
318
+ @router.get("/status", summary="GitHub integration status")
319
+ async def github_status():
320
+ configured = bool(GITHUB_TOKEN)
321
+ user = None
322
+ if configured:
323
+ try:
324
+ user_info = await gh_get("/user")
325
+ user = user_info.get("login")
326
+ except Exception:
327
+ configured = False
328
+ return {
329
+ "configured": configured,
330
+ "user": user,
331
+ "owner": GITHUB_OWNER or user,
332
+ "capabilities": [
333
+ "clone", "create_repo", "commit", "push",
334
+ "pr/create", "issues/create", "review"
335
+ ],
336
+ }
api/routes/health.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Health + Status Routes — God Agent OS V7
3
  """
4
 
5
  import time
@@ -9,10 +9,6 @@ from fastapi import APIRouter, Request
9
 
10
  router = APIRouter()
11
 
12
- GEMINI_KEYS = [k.strip() for k in os.environ.get("GEMINI_KEYS", "").split(",") if k.strip()]
13
- SAMBANOVA_KEYS = [k.strip() for k in os.environ.get("SAMBANOVA_KEYS", "").split(",") if k.strip()]
14
- GITHUB_KEYS = [k.strip() for k in os.environ.get("GITHUB_KEYS", "").split(",") if k.strip()]
15
-
16
 
17
  @router.get("/health", summary="Health check")
18
  async def health(request: Request):
@@ -26,37 +22,24 @@ async def health(request: Request):
26
  cs = connector_manager.get_summary() if connector_manager else {}
27
  ai_stats = ai_router.get_stats() if ai_router else {}
28
 
29
- # Check if any LLM is available
30
- ai_ready = (
31
- bool(GEMINI_KEYS) or
32
- bool(SAMBANOVA_KEYS) or
33
- bool(GITHUB_KEYS) or
34
- bool(os.environ.get("OPENAI_API_KEY")) or
35
- bool(os.environ.get("GROQ_API_KEY")) or
36
- bool(os.environ.get("ANTHROPIC_API_KEY"))
37
- )
38
-
39
  return {
40
  "status": "healthy",
41
- "name": "God Agent OS V7",
42
- "version": "7.0.0",
43
  "timestamp": time.time(),
44
  "platform": {
45
- "mode": "god_agent_v7",
46
  "agents": orchestrator.get_status()["agents"] if orchestrator else [],
47
  "agent_count": orchestrator.get_status()["total_agents"] if orchestrator else 0,
48
  },
49
  "ai_router": {
50
- "providers": {k: v.get("available", False) for k, v in ai_stats.items()},
51
- "ai_ready": ai_ready,
52
- "gemini_keys": len(GEMINI_KEYS),
53
- "sambanova_keys": len(SAMBANOVA_KEYS),
54
- "github_keys": len(GITHUB_KEYS),
55
  },
56
  "connectors": {
57
  "connected": cs.get("connected", 0),
58
  "total": cs.get("total", 0),
59
- "ai_ready": ai_ready,
60
  },
61
  "task_engine": {
62
  "queue_size": engine._queue.qsize(),
@@ -67,14 +50,16 @@ async def health(request: Request):
67
  "rooms": list(stats["rooms"].keys()),
68
  },
69
  "phases": [
70
- "V7: Gemini 2.0 Flash multi-key pool ✅",
71
- "V7: SambaNova Llama 3.3 70B multi-key pool ✅",
72
- "V7: GitHub Models GPT-4o multi-key pool ✅",
73
- "V7: Task-based smart routing ✅",
74
- "V7: Mission Control UI ✅",
75
- "V7: Self-healing execution loop ✅",
76
- "V7: Real terminal sandbox ✅",
77
- "V7: GitHub autonomy ✅",
 
 
78
  ],
79
  }
80
 
@@ -96,10 +81,5 @@ async def metrics():
96
  "used_gb": round(disk.used / 1024 / 1024 / 1024, 1),
97
  "percent": disk.percent,
98
  },
99
- "llm_pools": {
100
- "gemini_keys": len(GEMINI_KEYS),
101
- "sambanova_keys": len(SAMBANOVA_KEYS),
102
- "github_keys": len(GITHUB_KEYS),
103
- },
104
  "timestamp": time.time(),
105
  }
 
1
  """
2
+ Health + Status Routes — God Mode+ v3.0
3
  """
4
 
5
  import time
 
9
 
10
  router = APIRouter()
11
 
 
 
 
 
12
 
13
  @router.get("/health", summary="Health check")
14
  async def health(request: Request):
 
22
  cs = connector_manager.get_summary() if connector_manager else {}
23
  ai_stats = ai_router.get_stats() if ai_router else {}
24
 
 
 
 
 
 
 
 
 
 
 
25
  return {
26
  "status": "healthy",
27
+ "name": "GOD MODE+ AI Operating System",
28
+ "version": "3.0.0",
29
  "timestamp": time.time(),
30
  "platform": {
31
+ "mode": "god_mode_plus",
32
  "agents": orchestrator.get_status()["agents"] if orchestrator else [],
33
  "agent_count": orchestrator.get_status()["total_agents"] if orchestrator else 0,
34
  },
35
  "ai_router": {
36
+ "providers": {k: v["available"] for k, v in ai_stats.items()},
37
+ "ai_ready": any(v["available"] for v in ai_stats.values()),
 
 
 
38
  },
39
  "connectors": {
40
  "connected": cs.get("connected", 0),
41
  "total": cs.get("total", 0),
42
+ "ai_ready": cs.get("ai_ready", False),
43
  },
44
  "task_engine": {
45
  "queue_size": engine._queue.qsize(),
 
50
  "rooms": list(stats["rooms"].keys()),
51
  },
52
  "phases": [
53
+ "Phase 1: God Agent Orchestrator ✅",
54
+ "Phase 2: Sandbox Agent ✅",
55
+ "Phase 3: Connector System ✅",
56
+ "Phase 4: Autonomous Coding Engine ✅",
57
+ "Phase 5: Memory System ✅",
58
+ "Phase 6: Real-time Streaming ✅",
59
+ "Phase 7: Workflow Factor OS ✅",
60
+ "Phase 8: Modern UI Rebuild ✅",
61
+ "Phase 9: Multi-Model AI Router ✅",
62
+ "Phase 10-12: Observability + Security + God Mode+ ✅",
63
  ],
64
  }
65
 
 
81
  "used_gb": round(disk.used / 1024 / 1024 / 1024, 1),
82
  "percent": disk.percent,
83
  },
 
 
 
 
 
84
  "timestamp": time.time(),
85
  }
connectors/manager.py CHANGED
@@ -1,7 +1,6 @@
1
  """
2
- Connector Manager — GOD MODE+ V5
3
- 16 connectors including Gemini, SambaNova, GitHub Models
4
- Multi-key aware: checks comma-separated env values
5
  """
6
  import json
7
  import os
@@ -12,123 +11,200 @@ import structlog
12
  log = structlog.get_logger()
13
 
14
  CONNECTORS_CONFIG = [
15
- # ── Code ──────────────────────────────────────────────────────────────────
16
- {"id": "github", "name": "GitHub", "icon": "github", "color": "#24292e",
17
- "env_key": "GITHUB_TOKEN", "description": "Repos, Issues, PRs, Commits",
18
- "scopes": ["repo", "issues", "pull_requests"], "category": "code"},
19
- # ── Deploy ────────────────────────────────────────────────────────────────
20
- {"id": "huggingface", "name": "HuggingFace", "icon": "huggingface", "color": "#ff9d00",
21
- "env_key": "HF_TOKEN", "description": "Spaces, Models, Datasets",
22
- "scopes": ["spaces", "models"], "category": "deploy"},
23
- {"id": "vercel", "name": "Vercel", "icon": "vercel", "color": "#000000",
24
- "env_key": "VERCEL_TOKEN", "description": "Deployments, Domains, Functions",
25
- "scopes": ["deployments", "projects"], "category": "deploy"},
26
- # ── AI Providers ──────────────────────────────────────────────────────────
27
- {"id": "gemini", "name": "Gemini", "icon": "google", "color": "#4285f4",
28
- "env_key": "GEMINI_API_KEYS", "description": "Gemini 2.0 Flash — 6 key pool",
29
- "scopes": ["chat", "vision"], "category": "ai", "multi_key": True},
30
- {"id": "sambanova", "name": "SambaNova", "icon": "sambanova", "color": "#e84142",
31
- "env_key": "SAMBANOVA_API_KEYS", "description": "Llama 3.3 70B — 9 key pool",
32
- "scopes": ["chat"], "category": "ai", "multi_key": True},
33
- {"id": "github_models", "name": "GitHub Models", "icon": "github", "color": "#6e40c9",
34
- "env_key": "GITHUB_API_KEYS", "description": "GPT-4o via Azure — 9 key pool",
35
- "scopes": ["chat"], "category": "ai", "multi_key": True},
36
- {"id": "openai", "name": "OpenAI", "icon": "openai", "color": "#10a37f",
37
- "env_key": "OPENAI_API_KEY", "description": "GPT-4o, Embeddings, DALL-E",
38
- "scopes": ["chat", "embeddings"], "category": "ai"},
39
- {"id": "groq", "name": "Groq", "icon": "groq", "color": "#f55036",
40
- "env_key": "GROQ_API_KEY", "description": "Llama 3.3 70B — Ultra Fast",
41
- "scopes": ["chat"], "category": "ai"},
42
- {"id": "cerebras", "name": "Cerebras", "icon": "cerebras", "color": "#7c3aed",
43
- "env_key": "CEREBRAS_API_KEY", "description": "Llama 3.3 70B — Lightning Fast",
44
- "scopes": ["chat"], "category": "ai"},
45
- {"id": "anthropic", "name": "Anthropic", "icon": "anthropic", "color": "#d97706",
46
- "env_key": "ANTHROPIC_API_KEY", "description": "Claude 3.5 Haiku / Sonnet",
47
- "scopes": ["chat"], "category": "ai"},
48
- {"id": "openrouter", "name": "OpenRouter", "icon": "openrouter", "color": "#6366f1",
49
- "env_key": "OPENROUTER_API_KEY", "description": "100+ models via OpenRouter",
50
- "scopes": ["chat"], "category": "ai"},
51
- # ── Storage / Data ────────────────────────────────────────────────────────
52
- {"id": "supabase", "name": "Supabase", "icon": "supabase", "color": "#3ecf8e",
53
- "env_key": "SUPABASE_URL", "description": "Database, Auth, Storage",
54
- "scopes": ["database", "auth"], "category": "data"},
55
- {"id": "mongodb", "name": "MongoDB", "icon": "mongodb", "color": "#13aa52",
56
- "env_key": "MONGODB_URI", "description": "NoSQL Database",
57
- "scopes": ["database"], "category": "data"},
58
- # ── Comms ─────────────────────────────────────────────────────────────────
59
- {"id": "slack", "name": "Slack", "icon": "slack", "color": "#4a154b",
60
- "env_key": "SLACK_BOT_TOKEN", "description": "Messages, Channels, Notifications",
61
- "scopes": ["messages", "channels"], "category": "comms"},
62
- {"id": "discord", "name": "Discord", "icon": "discord", "color": "#5865f2",
63
- "env_key": "DISCORD_BOT_TOKEN", "description": "Bot, Messages, Webhooks",
64
- "scopes": ["messages"], "category": "comms"},
65
- {"id": "telegram", "name": "Telegram", "icon": "telegram", "color": "#2ca5e0",
66
- "env_key": "TELEGRAM_BOT_TOKEN", "description": "Bot API, Messages",
67
- "scopes": ["messages"], "category": "comms"},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  ]
69
 
70
 
71
  class ConnectorManager:
72
- def __init__(self):
73
- self._tokens: Dict[str, str] = {}
74
 
75
- def _is_configured(self, connector: dict) -> bool:
76
- """Check if a connector has its env var set (supports comma-separated multi-key)."""
77
- val = os.environ.get(connector["env_key"], "").strip()
78
- if not val:
79
- return False
80
- # For multi-key connectors, at least one key must be present
81
- keys = [k.strip() for k in val.split(",") if k.strip()]
82
- return len(keys) > 0
83
 
84
- def get_all(self) -> List[dict]:
 
85
  result = []
86
- for c in CONNECTORS_CONFIG:
87
- connected = self._is_configured(c) or c["id"] in self._tokens
88
  result.append({
89
- **c,
90
- "connected": connected,
91
- "has_token": connected,
92
  })
93
  return result
94
 
95
- def get_connected(self) -> List[dict]:
 
96
  return [c for c in self.get_all() if c["connected"]]
97
 
98
- def get_by_category(self, category: str) -> List[dict]:
99
- return [c for c in self.get_all() if c.get("category") == category]
 
100
 
101
  def is_connected(self, connector_id: str) -> bool:
102
- c = next((x for x in CONNECTORS_CONFIG if x["id"] == connector_id), None)
103
- if not c:
104
  return False
105
- return self._is_configured(c) or connector_id in self._tokens
106
-
107
- def set_token(self, connector_id: str, token: str):
108
- self._tokens[connector_id] = token
109
 
110
- def clear_token(self, connector_id: str):
111
- self._tokens.pop(connector_id, None)
 
 
 
112
 
113
- def get_env_key(self, connector_id: str) -> Optional[str]:
114
- c = next((x for x in CONNECTORS_CONFIG if x["id"] == connector_id), None)
115
- return c["env_key"] if c else None
 
 
 
116
 
117
- def get_summary(self) -> dict:
118
  all_c = self.get_all()
119
  connected = [c for c in all_c if c["connected"]]
120
-
121
- # AI ready = at least one of the primary AI providers is connected
122
- ai_providers = {"gemini", "sambanova", "github_models", "openai", "groq", "cerebras", "anthropic", "openrouter"}
123
- ai_ready = any(c["id"] in ai_providers for c in connected)
124
-
 
 
 
125
  return {
126
  "total": len(all_c),
127
  "connected": len(connected),
128
- "ai_ready": ai_ready,
129
- "categories": {
130
- cat: len([c for c in connected if c.get("category") == cat])
131
- for cat in ["ai", "code", "deploy", "data", "comms"]
132
- },
133
- "connected_ids": [c["id"] for c in connected],
134
  }
 
1
  """
2
+ Connector Manager — Manus-style connector ecosystem
3
+ Manages OAuth tokens, connection state, API access
 
4
  """
5
  import json
6
  import os
 
11
  log = structlog.get_logger()
12
 
13
  CONNECTORS_CONFIG = [
14
+ {
15
+ "id": "github",
16
+ "name": "GitHub",
17
+ "icon": "github",
18
+ "color": "#24292e",
19
+ "env_key": "GITHUB_TOKEN",
20
+ "description": "Repos, Issues, PRs, Commits",
21
+ "scopes": ["repo", "issues", "pull_requests"],
22
+ "category": "code",
23
+ },
24
+ {
25
+ "id": "huggingface",
26
+ "name": "HuggingFace",
27
+ "icon": "huggingface",
28
+ "color": "#ff9d00",
29
+ "env_key": "HF_TOKEN",
30
+ "description": "Spaces, Models, Datasets",
31
+ "scopes": ["spaces", "models"],
32
+ "category": "ai",
33
+ },
34
+ {
35
+ "id": "vercel",
36
+ "name": "Vercel",
37
+ "icon": "vercel",
38
+ "color": "#000000",
39
+ "env_key": "VERCEL_TOKEN",
40
+ "description": "Deployments, Domains, Functions",
41
+ "scopes": ["deployments", "projects"],
42
+ "category": "deploy",
43
+ },
44
+ {
45
+ "id": "openai",
46
+ "name": "OpenAI",
47
+ "icon": "openai",
48
+ "color": "#10a37f",
49
+ "env_key": "OPENAI_API_KEY",
50
+ "description": "GPT-4o, Embeddings, DALL-E",
51
+ "scopes": ["chat", "embeddings"],
52
+ "category": "ai",
53
+ },
54
+ {
55
+ "id": "groq",
56
+ "name": "Groq",
57
+ "icon": "groq",
58
+ "color": "#f55036",
59
+ "env_key": "GROQ_API_KEY",
60
+ "description": "Llama 3.3 70B — Ultra Fast",
61
+ "scopes": ["chat"],
62
+ "category": "ai",
63
+ },
64
+ {
65
+ "id": "cerebras",
66
+ "name": "Cerebras",
67
+ "icon": "cerebras",
68
+ "color": "#7c3aed",
69
+ "env_key": "CEREBRAS_API_KEY",
70
+ "description": "Llama 3.1 70B — Long Context",
71
+ "scopes": ["chat"],
72
+ "category": "ai",
73
+ },
74
+ {
75
+ "id": "openrouter",
76
+ "name": "OpenRouter",
77
+ "icon": "openrouter",
78
+ "color": "#6366f1",
79
+ "env_key": "OPENROUTER_API_KEY",
80
+ "description": "Multi-model router, free tier",
81
+ "scopes": ["chat"],
82
+ "category": "ai",
83
+ },
84
+ {
85
+ "id": "anthropic",
86
+ "name": "Anthropic",
87
+ "icon": "anthropic",
88
+ "color": "#d4a27f",
89
+ "env_key": "ANTHROPIC_API_KEY",
90
+ "description": "Claude 3.5 Sonnet",
91
+ "scopes": ["chat"],
92
+ "category": "ai",
93
+ },
94
+ {
95
+ "id": "n8n",
96
+ "name": "n8n",
97
+ "icon": "n8n",
98
+ "color": "#ea4b71",
99
+ "env_key": "N8N_URL",
100
+ "description": "Workflow automation engine",
101
+ "scopes": ["workflows", "executions"],
102
+ "category": "workflow",
103
+ },
104
+ {
105
+ "id": "telegram",
106
+ "name": "Telegram",
107
+ "icon": "telegram",
108
+ "color": "#0088cc",
109
+ "env_key": "TELEGRAM_BOT_TOKEN",
110
+ "description": "Bot API, messages, webhooks",
111
+ "scopes": ["messages", "bots"],
112
+ "category": "messaging",
113
+ },
114
+ {
115
+ "id": "discord",
116
+ "name": "Discord",
117
+ "icon": "discord",
118
+ "color": "#5865f2",
119
+ "env_key": "DISCORD_BOT_TOKEN",
120
+ "description": "Bot, channels, webhooks",
121
+ "scopes": ["messages", "bots"],
122
+ "category": "messaging",
123
+ },
124
+ {
125
+ "id": "slack",
126
+ "name": "Slack",
127
+ "icon": "slack",
128
+ "color": "#4a154b",
129
+ "env_key": "SLACK_BOT_TOKEN",
130
+ "description": "Messages, channels, workflows",
131
+ "scopes": ["messages", "channels"],
132
+ "category": "messaging",
133
+ },
134
+ {
135
+ "id": "cloudflare",
136
+ "name": "Cloudflare",
137
+ "icon": "cloudflare",
138
+ "color": "#f38020",
139
+ "env_key": "CLOUDFLARE_API_TOKEN",
140
+ "description": "Workers, KV, Pages",
141
+ "scopes": ["workers", "kv", "pages"],
142
+ "category": "infra",
143
+ },
144
  ]
145
 
146
 
147
  class ConnectorManager:
148
+ """Manages all platform connectors — connection state, tokens, status."""
 
149
 
150
+ def __init__(self):
151
+ self._configs = {c["id"]: c for c in CONNECTORS_CONFIG}
 
 
 
 
 
 
152
 
153
+ def get_all(self) -> List[Dict]:
154
+ """Get all connectors with connection status."""
155
  result = []
156
+ for cfg in CONNECTORS_CONFIG:
157
+ token = os.environ.get(cfg["env_key"], "")
158
  result.append({
159
+ **cfg,
160
+ "connected": bool(token),
161
+ "token_preview": f"{token[:8]}..." if token else None,
162
  })
163
  return result
164
 
165
+ def get_connected(self) -> List[Dict]:
166
+ """Get only connected connectors."""
167
  return [c for c in self.get_all() if c["connected"]]
168
 
169
+ def get_by_category(self, category: str) -> List[Dict]:
170
+ """Get connectors by category."""
171
+ return [c for c in self.get_all() if c["category"] == category]
172
 
173
  def is_connected(self, connector_id: str) -> bool:
174
+ cfg = self._configs.get(connector_id)
175
+ if not cfg:
176
  return False
177
+ return bool(os.environ.get(cfg["env_key"], ""))
 
 
 
178
 
179
+ def get_token(self, connector_id: str) -> Optional[str]:
180
+ cfg = self._configs.get(connector_id)
181
+ if not cfg:
182
+ return None
183
+ return os.environ.get(cfg["env_key"]) or None
184
 
185
+ def set_token(self, connector_id: str, token: str):
186
+ """Set connector token at runtime (does not persist across restarts)."""
187
+ cfg = self._configs.get(connector_id)
188
+ if cfg:
189
+ os.environ[cfg["env_key"]] = token
190
+ log.info("Connector token set", connector=connector_id)
191
 
192
+ def get_summary(self) -> Dict:
193
  all_c = self.get_all()
194
  connected = [c for c in all_c if c["connected"]]
195
+ by_cat = {}
196
+ for c in all_c:
197
+ cat = c["category"]
198
+ if cat not in by_cat:
199
+ by_cat[cat] = {"total": 0, "connected": 0}
200
+ by_cat[cat]["total"] += 1
201
+ if c["connected"]:
202
+ by_cat[cat]["connected"] += 1
203
  return {
204
  "total": len(all_c),
205
  "connected": len(connected),
206
+ "by_category": by_cat,
207
+ "ai_ready": self.is_connected("openai") or self.is_connected("groq")
208
+ or self.is_connected("openrouter") or self.is_connected("anthropic")
209
+ or self.is_connected("cerebras"),
 
 
210
  }
core/agent.py CHANGED
@@ -21,7 +21,7 @@ log = structlog.get_logger()
21
  OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
22
  ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
23
  DEFAULT_MODEL = os.environ.get("DEFAULT_MODEL", "gpt-4o")
24
- OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", os.environ.get("CUSTOM_GATEWAY_URL", "https://gateway.pyaesone-gtckglay.workers.dev/v1"))
25
 
26
 
27
  SYSTEM_PROMPT = """You are an elite autonomous AI software engineer — like Devin or Manus.
 
21
  OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
22
  ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
23
  DEFAULT_MODEL = os.environ.get("DEFAULT_MODEL", "gpt-4o")
24
+ OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1")
25
 
26
 
27
  SYSTEM_PROMPT = """You are an elite autonomous AI software engineer — like Devin or Manus.
main.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
- 🚀 GOD MODE+ Autonomous AI Operating System v4.0
3
- REAL Execution-First Architecture — Devin + Manus + Genspark Style
4
- Phase 4: Terminal Engine + FileSystem + GitHub + DAG + Self-Repair
5
  """
6
 
7
  import asyncio
@@ -24,7 +24,6 @@ from slowapi.errors import RateLimitExceeded
24
 
25
  from api.routes import tasks, chat, memory, github, health
26
  from api.routes import connectors, agents as agents_router
27
- from api.routes import execution as execution_router
28
  from api.websocket_manager import WebSocketManager
29
  from core.task_engine import TaskEngine
30
  from memory.db import init_db
@@ -44,12 +43,6 @@ from agents.sandbox_agent import SandboxAgent
44
  from agents.ui_agent import UIAgent
45
  from connectors.manager import ConnectorManager
46
 
47
- # ─── New Execution Tools ───────────────────────────────────────────────────────
48
- from tools.terminal_engine import TerminalEngine
49
- from tools.filesystem import FileSystemTool
50
- from tools.github_tool import GitHubTool
51
- from tools.task_dag import DAGEngine
52
-
53
  # ─── Structured Logging ────────────────────────────────────────────────────────
54
  structlog.configure(
55
  processors=[
@@ -70,16 +63,11 @@ task_engine = TaskEngine(ws_manager)
70
  ai_router = AIRouter(ws_manager)
71
  connector_manager = ConnectorManager()
72
 
73
- # ─── Execution Tools ───────────────────────────────────────────────────────────
74
- terminal_engine = TerminalEngine(ws_manager)
75
- fs_tool = FileSystemTool()
76
- github_tool = GitHubTool()
77
- dag_engine = DAGEngine(ws_manager)
78
-
79
-
80
  # ─── Build God Agent Ecosystem ────────────────────────────────────────────────
81
  def build_orchestrator() -> GodAgentOrchestrator:
82
  orchestrator = GodAgentOrchestrator(ws_manager=ws_manager, ai_router=ai_router)
 
 
83
  orchestrator.register_agent("chat", ChatAgent(ws_manager, ai_router))
84
  orchestrator.register_agent("planner", PlannerAgent(ws_manager, ai_router))
85
  orchestrator.register_agent("coding", CodingAgent(ws_manager, ai_router))
@@ -90,40 +78,34 @@ def build_orchestrator() -> GodAgentOrchestrator:
90
  orchestrator.register_agent("workflow", WorkflowAgent(ws_manager, ai_router))
91
  orchestrator.register_agent("sandbox", SandboxAgent(ws_manager, ai_router))
92
  orchestrator.register_agent("ui", UIAgent(ws_manager, ai_router))
 
93
  log.info("🤖 God Agent Ecosystem initialized", agents=10)
94
  return orchestrator
95
 
96
-
97
  orchestrator = build_orchestrator()
98
 
99
 
100
  @asynccontextmanager
101
  async def lifespan(app: FastAPI):
102
- log.info("🚀 Starting GOD MODE+ v4.0 AI Operating System...")
103
- log.info(" REAL Execution Engine: Terminal + FileSystem + GitHub + DAG")
104
  await init_db()
105
  await task_engine.start()
106
  asyncio.create_task(ws_manager.heartbeat_loop())
107
-
108
- # Ensure workspace exists
109
- ws_dir = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
110
- os.makedirs(ws_dir, exist_ok=True)
111
-
112
- log.info("✅ GOD MODE+ v4.0 Platform ready")
113
  log.info("🤖 Agents: Chat, Planner, Coding, Debug, Memory, Connector, Deploy, Workflow, Sandbox, UI")
114
- log.info("🔧 Tools: TerminalEngine, FileSystem, GitHub, DAG Engine, SelfRepair")
115
  log.info("🌐 AI Router: OpenAI → Groq → Cerebras → OpenRouter → Anthropic")
116
  yield
117
- log.info("🛑 Shutting down GOD MODE+ v4.0...")
118
  await task_engine.stop()
119
  log.info("✅ Shutdown complete")
120
 
121
 
122
  # ─── FastAPI App ───────────────────────────────────────────────────────────────
123
  app = FastAPI(
124
- title="🤖 GOD MODE+ AI Operating System v4.0",
125
- description="REAL Execution-First Autonomous AI Engineering Platform — Devin + Manus + Genspark",
126
- version="4.0.0",
127
  lifespan=lifespan,
128
  docs_url="/api/docs",
129
  redoc_url="/api/redoc",
@@ -138,10 +120,6 @@ app.state.task_engine = task_engine
138
  app.state.ai_router = ai_router
139
  app.state.orchestrator = orchestrator
140
  app.state.connector_manager = connector_manager
141
- app.state.terminal_engine = terminal_engine
142
- app.state.fs_tool = fs_tool
143
- app.state.github_tool = github_tool
144
- app.state.dag_engine = dag_engine
145
 
146
  # ─── Middleware ────────────────────────────────────────────────────────────────
147
  app.add_middleware(
@@ -171,10 +149,9 @@ app.include_router(memory.router, prefix="/api/v1/memory", tag
171
  app.include_router(github.router, prefix="/api/v1/github", tags=["github"])
172
  app.include_router(connectors.router, prefix="/api/v1/connectors", tags=["connectors"])
173
  app.include_router(agents_router.router, prefix="/api/v1/agents", tags=["agents"])
174
- app.include_router(execution_router.router, prefix="/api/v1/exec", tags=["execution"])
175
 
176
 
177
- # ─── WebSocket: Task Stream ────────────────────────────────────────────────────
178
  @app.websocket("/ws/tasks/{task_id}")
179
  async def ws_task(websocket: WebSocket, task_id: str):
180
  await ws_manager.connect(websocket, room=f"task:{task_id}")
@@ -188,7 +165,6 @@ async def ws_task(websocket: WebSocket, task_id: str):
188
  ws_manager.disconnect(websocket, room=f"task:{task_id}")
189
 
190
 
191
- # ─── WebSocket: Logs ──────────────────────────────────────────────────────────
192
  @app.websocket("/ws/logs")
193
  async def ws_logs(websocket: WebSocket):
194
  await ws_manager.connect(websocket, room="logs")
@@ -202,7 +178,6 @@ async def ws_logs(websocket: WebSocket):
202
  ws_manager.disconnect(websocket, room="logs")
203
 
204
 
205
- # ─── WebSocket: Chat ──────────────────────────────────────────────────────────
206
  @app.websocket("/ws/chat/{session_id}")
207
  async def ws_chat(websocket: WebSocket, session_id: str):
208
  await ws_manager.connect(websocket, room=f"chat:{session_id}")
@@ -213,6 +188,7 @@ async def ws_chat(websocket: WebSocket, session_id: str):
213
  if msg.get("type") == "ping":
214
  await websocket.send_json({"type": "pong", "timestamp": time.time()})
215
  elif msg.get("type") == "chat_message":
 
216
  asyncio.create_task(
217
  orchestrator.orchestrate(
218
  user_message=msg.get("content", ""),
@@ -221,6 +197,7 @@ async def ws_chat(websocket: WebSocket, session_id: str):
221
  )
222
  )
223
  elif msg.get("type") == "task_message":
 
224
  from core.models import TaskCreateRequest
225
  req = TaskCreateRequest(
226
  goal=msg.get("content", ""),
@@ -231,7 +208,6 @@ async def ws_chat(websocket: WebSocket, session_id: str):
231
  ws_manager.disconnect(websocket, room=f"chat:{session_id}")
232
 
233
 
234
- # ─── WebSocket: Agent Status ──────────────────────────────────────────────────
235
  @app.websocket("/ws/agent/status")
236
  async def ws_agent_status(websocket: WebSocket):
237
  await ws_manager.connect(websocket, room="agent_status")
@@ -250,112 +226,40 @@ async def ws_agent_status(websocket: WebSocket):
250
  ws_manager.disconnect(websocket, room="agent_status")
251
 
252
 
253
- # ─── WebSocket: Sandbox Terminal ──────────────────────────────────────────────
254
  @app.websocket("/ws/sandbox/{session_id}")
255
  async def ws_sandbox(websocket: WebSocket, session_id: str):
256
- """Live streaming sandbox terminal."""
257
  await ws_manager.connect(websocket, room=f"sandbox:{session_id}")
 
258
  try:
259
  while True:
260
  data = await websocket.receive_text()
261
  msg = json.loads(data)
262
-
263
  if msg.get("type") == "ping":
264
  await websocket.send_json({"type": "pong", "timestamp": time.time()})
265
-
266
- elif msg.get("type") == "execute":
267
  cmd = msg.get("command", "")
268
- task_id = msg.get("task_id", "")
269
-
270
- async def stream_line(line: str):
271
- try:
272
- await websocket.send_json({
273
- "type": "terminal_output",
274
- "line": line,
275
- "timestamp": time.time(),
276
- })
277
- except Exception:
278
- pass
279
-
280
- result = await terminal_engine.execute(
281
- cmd,
282
- session_id=session_id,
283
- task_id=task_id,
284
- stream_callback=stream_line,
285
- )
286
  await websocket.send_json({
287
- "type": "terminal_result",
288
  "command": cmd,
289
- "output": result.get("output", ""),
290
- "exit_code": result.get("exit_code", 0),
291
- "success": result.get("success", False),
292
- "duration": result.get("duration", 0),
293
  "timestamp": time.time(),
294
  })
295
-
296
- elif msg.get("type") == "kill":
297
- result = await terminal_engine.kill(session_id)
298
- await websocket.send_json({"type": "kill_result", **result})
299
-
300
- elif msg.get("type") == "write_file":
301
- filename = msg.get("filename", "")
302
- content = msg.get("content", "")
303
- result = await fs_tool.write_file(filename, content)
304
- await websocket.send_json({"type": "file_result", **result})
305
-
306
- elif msg.get("type") == "read_file":
307
- filename = msg.get("filename", "")
308
- result = await fs_tool.read_file(filename)
309
- await websocket.send_json({"type": "file_result", **result})
310
-
311
- elif msg.get("type") == "list_files":
312
- result = await fs_tool.list_dir(msg.get("path", ""))
313
- await websocket.send_json({"type": "file_list", **result})
314
-
315
- elif msg.get("type") == "tree":
316
- result = await fs_tool.tree(msg.get("path", ""))
317
- await websocket.send_json({"type": "tree_result", **result})
318
-
319
  except WebSocketDisconnect:
320
  ws_manager.disconnect(websocket, room=f"sandbox:{session_id}")
321
 
322
 
323
- # ─── WebSocket: DAG Progress ──────────────────────────────────────────────────
324
- @app.websocket("/ws/dag/{dag_id}")
325
- async def ws_dag(websocket: WebSocket, dag_id: str):
326
- """Real-time DAG execution progress."""
327
- await ws_manager.connect(websocket, room=f"dag:{dag_id}")
328
- try:
329
- while True:
330
- data = await websocket.receive_text()
331
- msg = json.loads(data)
332
- if msg.get("type") == "ping":
333
- await websocket.send_json({"type": "pong", "timestamp": time.time()})
334
- elif msg.get("type") == "get_dag":
335
- dag = dag_engine.get_dag(dag_id)
336
- if dag:
337
- await websocket.send_json({"type": "dag_state", "dag": dag.to_dict()})
338
- except WebSocketDisconnect:
339
- ws_manager.disconnect(websocket, room=f"dag:{dag_id}")
340
-
341
-
342
  # ─── Root ──────────────────────────────────────────────────────────────────────
343
  @app.get("/")
344
  async def root():
345
  cs = connector_manager.get_summary()
346
  return {
347
  "name": "🤖 GOD MODE+ AI Operating System",
348
- "version": "4.0.0",
349
  "status": "operational",
350
- "mode": "god_mode_plus_v4",
351
- "execution_engine": "REAL",
352
  "agents": orchestrator.get_status()["agents"],
353
- "tools": {
354
- "terminal": "TerminalEngine (streaming, self-repair)",
355
- "filesystem": "FileSystemTool (read/write/patch/search/tree)",
356
- "github": "GitHubTool (clone/commit/push/pr/issues)",
357
- "dag": "DAGEngine (parallel, dependency-aware)",
358
- },
359
  "connectors": {
360
  "connected": cs["connected"],
361
  "total": cs["total"],
@@ -368,7 +272,6 @@ async def root():
368
  "/ws/chat/{session_id}",
369
  "/ws/agent/status",
370
  "/ws/sandbox/{session_id}",
371
- "/ws/dag/{dag_id}",
372
  ],
373
  "phases_complete": [
374
  "Phase 1: God Agent Orchestrator",
@@ -377,12 +280,7 @@ async def root():
377
  "Phase 4: Autonomous Coding Engine",
378
  "Phase 5: Memory System",
379
  "Phase 6: Real-time Streaming",
380
- "Phase 7: Workflow Factory OS",
381
- "Phase 8: Terminal Execution Engine",
382
  "Phase 9: Multi-Model AI Router",
383
- "Phase 10: FileSystem Control",
384
- "Phase 11: GitHub Autonomy",
385
- "Phase 12: Task DAG Engine",
386
- "Phase 13: Self-Repair Loop",
387
  ],
388
  }
 
1
  """
2
+ 🚀 GOD MODE+ Autonomous AI Operating System
3
+ Devin + Manus + Genspark Style — Production-Grade Backend
4
+ Version: 3.0.0 Full God Mode Upgrade
5
  """
6
 
7
  import asyncio
 
24
 
25
  from api.routes import tasks, chat, memory, github, health
26
  from api.routes import connectors, agents as agents_router
 
27
  from api.websocket_manager import WebSocketManager
28
  from core.task_engine import TaskEngine
29
  from memory.db import init_db
 
43
  from agents.ui_agent import UIAgent
44
  from connectors.manager import ConnectorManager
45
 
 
 
 
 
 
 
46
  # ─── Structured Logging ────────────────────────────────────────────────────────
47
  structlog.configure(
48
  processors=[
 
63
  ai_router = AIRouter(ws_manager)
64
  connector_manager = ConnectorManager()
65
 
 
 
 
 
 
 
 
66
  # ─── Build God Agent Ecosystem ────────────────────────────────────────────────
67
  def build_orchestrator() -> GodAgentOrchestrator:
68
  orchestrator = GodAgentOrchestrator(ws_manager=ws_manager, ai_router=ai_router)
69
+
70
+ # Register all specialized agents
71
  orchestrator.register_agent("chat", ChatAgent(ws_manager, ai_router))
72
  orchestrator.register_agent("planner", PlannerAgent(ws_manager, ai_router))
73
  orchestrator.register_agent("coding", CodingAgent(ws_manager, ai_router))
 
78
  orchestrator.register_agent("workflow", WorkflowAgent(ws_manager, ai_router))
79
  orchestrator.register_agent("sandbox", SandboxAgent(ws_manager, ai_router))
80
  orchestrator.register_agent("ui", UIAgent(ws_manager, ai_router))
81
+
82
  log.info("🤖 God Agent Ecosystem initialized", agents=10)
83
  return orchestrator
84
 
 
85
  orchestrator = build_orchestrator()
86
 
87
 
88
  @asynccontextmanager
89
  async def lifespan(app: FastAPI):
90
+ """Startup + Shutdown lifecycle."""
91
+ log.info("🚀 Starting GOD MODE+ AI Operating System...")
92
  await init_db()
93
  await task_engine.start()
94
  asyncio.create_task(ws_manager.heartbeat_loop())
95
+ log.info("✅ GOD MODE+ Platform ready — All agents online")
 
 
 
 
 
96
  log.info("🤖 Agents: Chat, Planner, Coding, Debug, Memory, Connector, Deploy, Workflow, Sandbox, UI")
 
97
  log.info("🌐 AI Router: OpenAI → Groq → Cerebras → OpenRouter → Anthropic")
98
  yield
99
+ log.info("🛑 Shutting down...")
100
  await task_engine.stop()
101
  log.info("✅ Shutdown complete")
102
 
103
 
104
  # ─── FastAPI App ───────────────────────────────────────────────────────────────
105
  app = FastAPI(
106
+ title="🤖 GOD MODE+ AI Operating System",
107
+ description="Devin + Manus + Genspark Autonomous AI Engineering Platform",
108
+ version="3.0.0",
109
  lifespan=lifespan,
110
  docs_url="/api/docs",
111
  redoc_url="/api/redoc",
 
120
  app.state.ai_router = ai_router
121
  app.state.orchestrator = orchestrator
122
  app.state.connector_manager = connector_manager
 
 
 
 
123
 
124
  # ─── Middleware ────────────────────────────────────────────────────────────────
125
  app.add_middleware(
 
149
  app.include_router(github.router, prefix="/api/v1/github", tags=["github"])
150
  app.include_router(connectors.router, prefix="/api/v1/connectors", tags=["connectors"])
151
  app.include_router(agents_router.router, prefix="/api/v1/agents", tags=["agents"])
 
152
 
153
 
154
+ # ─── WebSocket Endpoints ───────────────────────────────────────────────────────
155
  @app.websocket("/ws/tasks/{task_id}")
156
  async def ws_task(websocket: WebSocket, task_id: str):
157
  await ws_manager.connect(websocket, room=f"task:{task_id}")
 
165
  ws_manager.disconnect(websocket, room=f"task:{task_id}")
166
 
167
 
 
168
  @app.websocket("/ws/logs")
169
  async def ws_logs(websocket: WebSocket):
170
  await ws_manager.connect(websocket, room="logs")
 
178
  ws_manager.disconnect(websocket, room="logs")
179
 
180
 
 
181
  @app.websocket("/ws/chat/{session_id}")
182
  async def ws_chat(websocket: WebSocket, session_id: str):
183
  await ws_manager.connect(websocket, room=f"chat:{session_id}")
 
188
  if msg.get("type") == "ping":
189
  await websocket.send_json({"type": "pong", "timestamp": time.time()})
190
  elif msg.get("type") == "chat_message":
191
+ # Route through God Agent Orchestrator
192
  asyncio.create_task(
193
  orchestrator.orchestrate(
194
  user_message=msg.get("content", ""),
 
197
  )
198
  )
199
  elif msg.get("type") == "task_message":
200
+ # Create autonomous task via task engine
201
  from core.models import TaskCreateRequest
202
  req = TaskCreateRequest(
203
  goal=msg.get("content", ""),
 
208
  ws_manager.disconnect(websocket, room=f"chat:{session_id}")
209
 
210
 
 
211
  @app.websocket("/ws/agent/status")
212
  async def ws_agent_status(websocket: WebSocket):
213
  await ws_manager.connect(websocket, room="agent_status")
 
226
  ws_manager.disconnect(websocket, room="agent_status")
227
 
228
 
 
229
  @app.websocket("/ws/sandbox/{session_id}")
230
  async def ws_sandbox(websocket: WebSocket, session_id: str):
231
+ """Live sandbox terminal stream."""
232
  await ws_manager.connect(websocket, room=f"sandbox:{session_id}")
233
+ sandbox = orchestrator.get_agent("sandbox")
234
  try:
235
  while True:
236
  data = await websocket.receive_text()
237
  msg = json.loads(data)
 
238
  if msg.get("type") == "ping":
239
  await websocket.send_json({"type": "pong", "timestamp": time.time()})
240
+ elif msg.get("type") == "execute" and sandbox:
 
241
  cmd = msg.get("command", "")
242
+ result = await sandbox.execute(cmd, session_id=session_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  await websocket.send_json({
244
+ "type": "terminal_output",
245
  "command": cmd,
246
+ "output": result,
 
 
 
247
  "timestamp": time.time(),
248
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  except WebSocketDisconnect:
250
  ws_manager.disconnect(websocket, room=f"sandbox:{session_id}")
251
 
252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  # ─── Root ──────────────────────────────────────────────────────────────────────
254
  @app.get("/")
255
  async def root():
256
  cs = connector_manager.get_summary()
257
  return {
258
  "name": "🤖 GOD MODE+ AI Operating System",
259
+ "version": "3.0.0",
260
  "status": "operational",
261
+ "mode": "god_mode_plus",
 
262
  "agents": orchestrator.get_status()["agents"],
 
 
 
 
 
 
263
  "connectors": {
264
  "connected": cs["connected"],
265
  "total": cs["total"],
 
272
  "/ws/chat/{session_id}",
273
  "/ws/agent/status",
274
  "/ws/sandbox/{session_id}",
 
275
  ],
276
  "phases_complete": [
277
  "Phase 1: God Agent Orchestrator",
 
280
  "Phase 4: Autonomous Coding Engine",
281
  "Phase 5: Memory System",
282
  "Phase 6: Real-time Streaming",
283
+ "Phase 7: Workflow Factor OS",
 
284
  "Phase 9: Multi-Model AI Router",
 
 
 
 
285
  ],
286
  }