God Agent OS CI commited on
Commit
02117ee
Β·
1 Parent(s): 103c581

πŸš€ Deploy God Agent OS v11 - 2026-05-17 07:49

Browse files
This view is limited to 50 files because it contains too many changes. Β  See raw diff
Dockerfile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # God Agent OS β€” Phase 1 backend (FastAPI + E2B + SSE)
2
+ FROM python:3.11-slim
3
+
4
+ WORKDIR /app
5
+
6
+ # System deps (curl for healthcheck, git for any sandbox-side workflows)
7
+ RUN apt-get update && apt-get install -y --no-install-recommends \
8
+ curl ca-certificates git \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Python deps
12
+ COPY requirements.txt /app/requirements.txt
13
+ RUN pip install --no-cache-dir --upgrade pip \
14
+ && pip install --no-cache-dir -r /app/requirements.txt
15
+
16
+ # App code
17
+ COPY . /app
18
+
19
+ ENV PORT=7860 \
20
+ PYTHONUNBUFFERED=1 \
21
+ PYTHONPATH=/app
22
+
23
+ EXPOSE 7860
24
+
25
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 \
26
+ CMD curl -fsS http://localhost:7860/health || exit 1
27
+
28
+ CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1", "--log-level", "info", "--no-access-log"]
Dockerfile.hf ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # God Agent OS β€” Phase 1 backend for HuggingFace Spaces
2
+ FROM python:3.11-slim
3
+
4
+ WORKDIR /app
5
+
6
+ RUN apt-get update && apt-get install -y --no-install-recommends \
7
+ curl ca-certificates git \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ COPY requirements.txt /app/requirements.txt
11
+ RUN pip install --no-cache-dir --upgrade pip \
12
+ && pip install --no-cache-dir -r /app/requirements.txt
13
+
14
+ COPY . /app
15
+
16
+ ENV PORT=7860 \
17
+ PYTHONUNBUFFERED=1 \
18
+ PYTHONPATH=/app
19
+
20
+ EXPOSE 7860
21
+
22
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
23
+ CMD curl -fsS http://localhost:7860/health || exit 1
24
+
25
+ CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1", "--log-level", "info"]
README.md CHANGED
@@ -1,77 +1,43 @@
1
  ---
2
- title: God Agent OS β€” Phase 1
3
  emoji: πŸ€–
4
- colorFrom: indigo
5
- colorTo: purple
6
  sdk: docker
7
- app_port: 7860
8
- app_file: backend/app.py
9
- pinned: true
10
  license: mit
11
- short_description: Stable autonomous agent backend (LLM + E2B + SSE)
12
  ---
13
 
14
- # πŸ€– God Agent OS β€” Phase 1 (Stability First)
15
-
16
- A **clean, minimal, stable** autonomous AI agent backend.
17
- One pipeline, real execution, no fake Computer Use.
18
-
19
- ## Architecture
20
-
21
- ```
22
- Frontend (Vercel / Next.js)
23
- ↓ HTTPS / SSE / WS
24
- Backend (HF Space β€” FastAPI)
25
- β”œβ”€ /api/v1/chat β†’ LLM-only streaming (SambaNova β†’ Gemini β†’ ...)
26
- β”œβ”€ /api/v1/execute β†’ REAL E2B sandbox (live stdout/stderr)
27
- β”œβ”€ /api/v1/agent β†’ Intent router (chat OR execute)
28
- └─ /ws/{session_id} β†’ Same events mirrored over WebSocket
29
- ```
30
-
31
- ## Endpoints
32
-
33
- | Method | Path | Purpose |
34
- |---|---|---|
35
- | GET | `/health` | Health + provider availability + E2B status |
36
- | POST | `/api/v1/chat` | LLM-only SSE chat (no sandbox) |
37
- | POST | `/api/v1/execute` | Real E2B execution, streams stdout/stderr |
38
- | POST | `/api/v1/agent` | Intent-routed: chat OR execute |
39
- | POST | `/api/v1/orchestrate` | Alias of `/api/v1/agent` |
40
- | POST | `/api/v1/kernel/orchestrate` | Legacy non-streaming alias |
41
- | GET | `/api/v1/sandbox/{session_id}` | Sandbox info |
42
- | DEL | `/api/v1/sandbox/{session_id}` | Kill sandbox |
43
- | WS | `/ws/{session_id}` | WebSocket events |
44
-
45
- ## Required HF Space Secrets
46
-
47
- | Variable | Required? | Purpose |
48
- |---|---|---|
49
- | `E2B_API_KEY` | **YES** | Real sandbox runtime |
50
- | `SAMBANOVA_KEY` | one of these | Llama 3.3 70B (recommended, fastest) |
51
- | `GEMINI_KEY` | one of these | Gemini 2.0 Flash |
52
- | `GITHUB_KEY` | one of these | GitHub Models (GPT-4o-mini) |
53
- | `OPENAI_API_KEY`| one of these | OpenAI |
54
- | `GROQ_API_KEY` | one of these | Groq (Llama) |
55
- | `ANTHROPIC_API_KEY` | one of these | Claude |
56
-
57
- ## Quick Proof Test
58
-
59
- ```bash
60
- curl -sN -X POST https://pyae1994-autonomous-coding-system.hf.space/api/v1/execute \
61
- -H 'Content-Type: application/json' \
62
- -d '{"language":"python","code":"import time,pathlib; p=pathlib.Path(\"/home/user/proof.txt\"); ts=int(time.time()); p.write_text(str(ts)); print(\"TS:\",ts,\"READBACK:\",p.read_text())","session_id":"smoke","stream":true}'
63
- ```
64
-
65
- Expected: live SSE stream of `sandbox_ready` β†’ `stdout` β†’ `result` events.
66
-
67
- ## Phase Roadmap
68
-
69
- - **Phase 1 (current)** β€” chat + execute + SSE/WS, stable.
70
- - **Phase 2** β€” browser automation, retry/self-repair loops.
71
- - **Phase 3** β€” workflows, memory, multi-agent.
72
-
73
- ## Powered by
74
-
75
- OpenHands ideas Β· E2B Β· SambaNova/Gemini Β· Vercel Β· HuggingFace Spaces
76
-
77
- Built by **Pyae Sone**.
 
1
  ---
2
+ title: Autonomous Coding System
3
  emoji: πŸ€–
4
+ colorFrom: purple
5
+ colorTo: indigo
6
  sdk: docker
7
+ pinned: false
 
 
8
  license: mit
9
+ app_port: 7860
10
  ---
11
 
12
+ # GOD AGENT OS v11 β€” Backend API
13
+ **16-Agent Autonomous Engineering OS | God Mode**
14
+ *Powered by Pyae Sone*
15
+
16
+ ## Runtime Overview
17
+ - 16 autonomous agents (Chat, Planner, Coder, Debug, Test, File, Git, Browser, Vision, Sandbox, Deploy, Connector, Memory, Workflow, UI, Reasoning)
18
+ - 22 logical worker spaces (all running in this single backend)
19
+ - Multi-provider AI Router: Gemini β†’ SambaNova β†’ GitHub Models β†’ Groq β†’ OpenAI
20
+ - WebSocket + REST + SSE streaming
21
+
22
+ ## Key Endpoints
23
+ - `GET /` β€” Root info
24
+ - `GET /health` β€” Health check
25
+ - `GET /api/docs` β€” Swagger UI
26
+ - `GET /api/v1/system/status` β€” Full system status
27
+ - `GET /api/v1/agents` β€” List all 16 agents
28
+ - `GET /api/v1/spaces` β€” All 22 spaces status
29
+ - `POST /api/v1/chat` β€” Chat with streaming
30
+ - `POST /api/v1/orchestrate` β€” God Mode orchestration
31
+ - `WS /ws/{session_id}` β€” Real-time WebSocket
32
+ - `WS /ws/computer-use/{session_id}` β€” Computer-use event stream
33
+
34
+ ## Environment Variables
35
+ Set these in HF Space secrets:
36
+ - `GEMINI_KEY` β€” Google Gemini API key(s), comma-separated
37
+ - `SAMBANOVA_KEY` β€” SambaNova API key(s), comma-separated
38
+ - `GITHUB_KEY` β€” GitHub Models API key(s), comma-separated
39
+ - `GROQ_API_KEY` β€” Groq API key (optional fallback)
40
+ - `OPENAI_API_KEY` β€” OpenAI API key (optional fallback)
41
+ - `GITHUB_TOKEN` β€” GitHub token for Git operations
42
+ - `HF_TOKEN` β€” HuggingFace token
43
+ - `VERCEL_TOKEN` β€” Vercel deploy token
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_legacy/main.py ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ πŸš€ GOD AGENT OS β€” Autonomous AI Operating System v8
3
+ Gemini + Sambanova + GitHub Models β€” Primary Provider Rotation
4
+ Task-aware routing: research→Gemini, code→Sambanova, plan→GitHub
5
+ """
6
+
7
+ # ─── Inject bundled API keys (env vars take precedence) ───────────────────────
8
+ import os as _os
9
+
10
+ def _inject_key(env_var: str, value: str):
11
+ """Set env var only if not already configured."""
12
+ if not _os.environ.get(env_var):
13
+ _os.environ[env_var] = value
14
+
15
+ # Keys are loaded from HF Space Secrets / Docker env vars.
16
+ # Set GEMINI_KEY, SAMBANOVA_KEY, GITHUB_KEY as comma-separated lists.
17
+ # Fallback: read from .env.keys file if present (not committed to git).
18
+ _keys_file = _os.path.join(_os.path.dirname(__file__), ".env.keys")
19
+ if _os.path.exists(_keys_file):
20
+ with open(_keys_file) as _f:
21
+ for _line in _f:
22
+ _line = _line.strip()
23
+ if _line and "=" in _line and not _line.startswith("#"):
24
+ _k, _v = _line.split("=", 1)
25
+ _inject_key(_k.strip(), _v.strip())
26
+ # ─────────────────────────────────────────────────────────────────────────────
27
+
28
+
29
+ import asyncio
30
+ import json
31
+ import logging
32
+ import os
33
+ import time
34
+ import uuid
35
+ from contextlib import asynccontextmanager
36
+ from typing import Optional
37
+
38
+ import structlog
39
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Request
40
+ from fastapi.middleware.cors import CORSMiddleware
41
+ from fastapi.middleware.gzip import GZipMiddleware
42
+ from fastapi.responses import JSONResponse
43
+ from slowapi import Limiter, _rate_limit_exceeded_handler
44
+ from slowapi.util import get_remote_address
45
+ from slowapi.errors import RateLimitExceeded
46
+
47
+ from api.routes import tasks, chat, memory, github, health
48
+ from api.routes import connectors, agents as agents_router
49
+ from api.websocket_manager import WebSocketManager
50
+ from core.task_engine import TaskEngine
51
+ from memory.db import init_db
52
+
53
+ # ─── God Mode Agents ───────────────────────────────────────────────────────────
54
+ from ai_router.router import AIRouter
55
+ from ai_router.router_v8 import GodModeRouter, get_router as get_god_router
56
+ from agents.orchestrator import GodAgentOrchestrator
57
+ from agents.chat_agent import ChatAgent
58
+ from agents.planner_agent import PlannerAgent
59
+ from agents.coding_agent import CodingAgent
60
+ from agents.debug_agent import DebugAgent
61
+ from agents.memory_agent import MemoryAgent
62
+ from agents.connector_agent import ConnectorAgent
63
+ from agents.deploy_agent import DeployAgent
64
+ from agents.workflow_agent import WorkflowAgent
65
+ from agents.sandbox_agent import SandboxAgent
66
+ from agents.ui_agent import UIAgent
67
+ from connectors.manager import ConnectorManager
68
+
69
+ # ─── Structured Logging ────────────────────────────────────────────────────────
70
+ structlog.configure(
71
+ processors=[
72
+ structlog.processors.TimeStamper(fmt="iso"),
73
+ structlog.stdlib.add_log_level,
74
+ structlog.processors.StackInfoRenderer(),
75
+ structlog.dev.ConsoleRenderer(),
76
+ ]
77
+ )
78
+ log = structlog.get_logger()
79
+
80
+ # ─── Rate Limiter ──────────────────────────────────────────────────────────────
81
+ limiter = Limiter(key_func=get_remote_address)
82
+
83
+ # ─── Global Managers ──────────────────────────────────────────────────────────
84
+ ws_manager = WebSocketManager()
85
+ task_engine = TaskEngine(ws_manager)
86
+ ai_router = AIRouter(ws_manager)
87
+ god_router = get_god_router(ws_manager) # v8 primary router
88
+ connector_manager = ConnectorManager()
89
+
90
+ # ─── Build God Agent Ecosystem ────────────────────────────────────────────────
91
+ def build_orchestrator() -> GodAgentOrchestrator:
92
+ orchestrator = GodAgentOrchestrator(ws_manager=ws_manager, ai_router=ai_router)
93
+
94
+ # Register all specialized agents
95
+ orchestrator.register_agent("chat", ChatAgent(ws_manager, ai_router))
96
+ orchestrator.register_agent("planner", PlannerAgent(ws_manager, ai_router))
97
+ orchestrator.register_agent("coding", CodingAgent(ws_manager, ai_router))
98
+ orchestrator.register_agent("debug", DebugAgent(ws_manager, ai_router))
99
+ orchestrator.register_agent("memory", MemoryAgent(ws_manager, ai_router))
100
+ orchestrator.register_agent("connector", ConnectorAgent(ws_manager, ai_router))
101
+ orchestrator.register_agent("deploy", DeployAgent(ws_manager, ai_router))
102
+ orchestrator.register_agent("workflow", WorkflowAgent(ws_manager, ai_router))
103
+ orchestrator.register_agent("sandbox", SandboxAgent(ws_manager, ai_router))
104
+ orchestrator.register_agent("ui", UIAgent(ws_manager, ai_router))
105
+
106
+ log.info("πŸ€– God Agent Ecosystem initialized", agents=10)
107
+ return orchestrator
108
+
109
+ orchestrator = build_orchestrator()
110
+
111
+
112
+ @asynccontextmanager
113
+ async def lifespan(app: FastAPI):
114
+ """Startup + Shutdown lifecycle."""
115
+ log.info("πŸš€ Starting GOD MODE+ AI Operating System...")
116
+ await init_db()
117
+ await task_engine.start()
118
+ asyncio.create_task(ws_manager.heartbeat_loop())
119
+ log.info("βœ… GOD MODE+ Platform ready β€” All agents online")
120
+ log.info("πŸ€– Agents: Chat, Planner, Coding, Debug, Memory, Connector, Deploy, Workflow, Sandbox, UI")
121
+ log.info("🌐 AI Router v8: Gemini β†’ Sambanova β†’ GitHub Models (task-aware rotation)")
122
+ yield
123
+ log.info("πŸ›‘ Shutting down...")
124
+ await task_engine.stop()
125
+ log.info("βœ… Shutdown complete")
126
+
127
+
128
+ # ─── FastAPI App ───────────────────────────────────────────────────────────────
129
+ app = FastAPI(
130
+ title="πŸ€– GOD MODE+ AI Operating System",
131
+ description="Devin + Manus + Genspark Autonomous AI Engineering Platform",
132
+ version="3.0.0",
133
+ lifespan=lifespan,
134
+ docs_url="/api/docs",
135
+ redoc_url="/api/redoc",
136
+ )
137
+
138
+ app.state.limiter = limiter
139
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
140
+
141
+ # ─── Share state ───────────────────────────────────────────────────────────────
142
+ app.state.ws_manager = ws_manager
143
+ app.state.task_engine = task_engine
144
+ app.state.ai_router = ai_router
145
+ app.state.orchestrator = orchestrator
146
+ app.state.connector_manager = connector_manager
147
+
148
+ # ─── Middleware ────────────────────────────────────────────────────────────────
149
+ app.add_middleware(
150
+ CORSMiddleware,
151
+ allow_origins=["*"],
152
+ allow_credentials=True,
153
+ allow_methods=["*"],
154
+ allow_headers=["*"],
155
+ )
156
+ app.add_middleware(GZipMiddleware, minimum_size=1000)
157
+
158
+
159
+ @app.middleware("http")
160
+ async def log_requests(request: Request, call_next):
161
+ start = time.time()
162
+ response = await call_next(request)
163
+ duration = round((time.time() - start) * 1000, 2)
164
+ log.info("HTTP", method=request.method, path=request.url.path, status=response.status_code, ms=duration)
165
+ return response
166
+
167
+
168
+ # ─── REST API Routers ──────────────────────────────────────────────────────────
169
+ app.include_router(health.router, prefix="/api/v1", tags=["health"])
170
+ app.include_router(tasks.router, prefix="/api/v1/tasks", tags=["tasks"])
171
+ app.include_router(chat.router, prefix="/api/v1", tags=["chat"])
172
+ app.include_router(memory.router, prefix="/api/v1/memory", tags=["memory"])
173
+ app.include_router(github.router, prefix="/api/v1/github", tags=["github"])
174
+ app.include_router(connectors.router, prefix="/api/v1/connectors", tags=["connectors"])
175
+ app.include_router(agents_router.router, prefix="/api/v1/agents", tags=["agents"])
176
+
177
+
178
+ # ─── WebSocket Endpoints ───────────────────────────────────────────────────────
179
+ @app.websocket("/ws/tasks/{task_id}")
180
+ async def ws_task(websocket: WebSocket, task_id: str):
181
+ await ws_manager.connect(websocket, room=f"task:{task_id}")
182
+ try:
183
+ while True:
184
+ data = await websocket.receive_text()
185
+ msg = json.loads(data)
186
+ if msg.get("type") == "ping":
187
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
188
+ except WebSocketDisconnect:
189
+ ws_manager.disconnect(websocket, room=f"task:{task_id}")
190
+
191
+
192
+ @app.websocket("/ws/logs")
193
+ async def ws_logs(websocket: WebSocket):
194
+ await ws_manager.connect(websocket, room="logs")
195
+ try:
196
+ while True:
197
+ data = await websocket.receive_text()
198
+ msg = json.loads(data)
199
+ if msg.get("type") == "ping":
200
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
201
+ except WebSocketDisconnect:
202
+ ws_manager.disconnect(websocket, room="logs")
203
+
204
+
205
+ @app.websocket("/ws/chat/{session_id}")
206
+ async def ws_chat(websocket: WebSocket, session_id: str):
207
+ await ws_manager.connect(websocket, room=f"chat:{session_id}")
208
+ try:
209
+ while True:
210
+ data = await websocket.receive_text()
211
+ msg = json.loads(data)
212
+ if msg.get("type") == "ping":
213
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
214
+ elif msg.get("type") == "chat_message":
215
+ # Route through God Agent Orchestrator
216
+ asyncio.create_task(
217
+ orchestrator.orchestrate(
218
+ user_message=msg.get("content", ""),
219
+ session_id=session_id,
220
+ context=msg.get("context", {}),
221
+ )
222
+ )
223
+ elif msg.get("type") == "task_message":
224
+ # Create autonomous task via task engine
225
+ from core.models import TaskCreateRequest
226
+ req = TaskCreateRequest(
227
+ goal=msg.get("content", ""),
228
+ session_id=session_id,
229
+ )
230
+ asyncio.create_task(task_engine.submit(req))
231
+ except WebSocketDisconnect:
232
+ ws_manager.disconnect(websocket, room=f"chat:{session_id}")
233
+
234
+
235
+ @app.websocket("/ws/agent/status")
236
+ async def ws_agent_status(websocket: WebSocket):
237
+ await ws_manager.connect(websocket, room="agent_status")
238
+ try:
239
+ while True:
240
+ data = await websocket.receive_text()
241
+ msg = json.loads(data)
242
+ if msg.get("type") == "ping":
243
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
244
+ elif msg.get("type") == "get_status":
245
+ await websocket.send_json({
246
+ "type": "agent_status",
247
+ "data": orchestrator.get_status(),
248
+ })
249
+ except WebSocketDisconnect:
250
+ ws_manager.disconnect(websocket, room="agent_status")
251
+
252
+
253
+ @app.websocket("/ws/sandbox/{session_id}")
254
+ async def ws_sandbox(websocket: WebSocket, session_id: str):
255
+ """Live sandbox terminal stream."""
256
+ await ws_manager.connect(websocket, room=f"sandbox:{session_id}")
257
+ sandbox = orchestrator.get_agent("sandbox")
258
+ try:
259
+ while True:
260
+ data = await websocket.receive_text()
261
+ msg = json.loads(data)
262
+ if msg.get("type") == "ping":
263
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
264
+ elif msg.get("type") == "execute" and sandbox:
265
+ cmd = msg.get("command", "")
266
+ result = await sandbox.execute(cmd, session_id=session_id)
267
+ await websocket.send_json({
268
+ "type": "terminal_output",
269
+ "command": cmd,
270
+ "output": result,
271
+ "timestamp": time.time(),
272
+ })
273
+ except WebSocketDisconnect:
274
+ ws_manager.disconnect(websocket, room=f"sandbox:{session_id}")
275
+
276
+
277
+ # ─── Root ──────────────────────────────────────────────────────────────────────
278
+ @app.get("/")
279
+ async def root():
280
+ cs = connector_manager.get_summary()
281
+ return {
282
+ "name": "πŸ€– GOD MODE+ AI Operating System",
283
+ "version": "3.0.0",
284
+ "status": "operational",
285
+ "mode": "god_mode_plus",
286
+ "agents": orchestrator.get_status()["agents"],
287
+ "connectors": {
288
+ "connected": cs["connected"],
289
+ "total": cs["total"],
290
+ "ai_ready": cs["ai_ready"],
291
+ },
292
+ "docs": "/api/docs",
293
+ "websockets": [
294
+ "/ws/tasks/{task_id}",
295
+ "/ws/logs",
296
+ "/ws/chat/{session_id}",
297
+ "/ws/agent/status",
298
+ "/ws/sandbox/{session_id}",
299
+ ],
300
+ "phases_complete": [
301
+ "Phase 1: God Agent Orchestrator",
302
+ "Phase 2: Sandbox Agent",
303
+ "Phase 3: Connector System",
304
+ "Phase 4: Autonomous Coding Engine",
305
+ "Phase 5: Memory System",
306
+ "Phase 6: Real-time Streaming",
307
+ "Phase 7: Workflow Factor OS",
308
+ "Phase 9: Multi-Model AI Router",
309
+ ],
310
+ }
_legacy/main_v11.py ADDED
@@ -0,0 +1,442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GOD AGENT OS v11 β€” Fixed & Production Ready
3
+ Bugs fixed:
4
+ - TaskEngine(ws_manager) only β€” removed ai_router arg
5
+ - init_db() β€” no arguments
6
+ - task_engine.start() β€” not run()
7
+ - ws_manager.emit_chat() β€” not emit_task()
8
+ - ConnectorManager() β€” sync init, no await needed
9
+ """
10
+
11
+ import asyncio
12
+ import json
13
+ import os
14
+ import time
15
+ import uuid
16
+ from contextlib import asynccontextmanager
17
+ from typing import Dict, List
18
+
19
+ import structlog
20
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Request
21
+ from fastapi.middleware.cors import CORSMiddleware
22
+ from fastapi.middleware.gzip import GZipMiddleware
23
+ from fastapi.responses import StreamingResponse
24
+ from slowapi import Limiter, _rate_limit_exceeded_handler
25
+ from slowapi.util import get_remote_address
26
+ from slowapi.errors import RateLimitExceeded
27
+
28
+ from api.websocket_manager import WebSocketManager
29
+ from core.task_engine import TaskEngine
30
+ from memory.db import init_db
31
+ from ai_router.router_v10 import AIRouterV10
32
+ from agents.orchestrator_v7 import GodAgentOrchestratorV7
33
+ from agents.chat_agent import ChatAgent
34
+ from agents.planner_agent import PlannerAgent
35
+ from agents.coding_agent import CodingAgent
36
+ from agents.debug_agent import DebugAgent
37
+ from agents.memory_agent import MemoryAgent
38
+ from agents.connector_agent import ConnectorAgent
39
+ from agents.deploy_agent import DeployAgent
40
+ from agents.workflow_agent import WorkflowAgent
41
+ from agents.sandbox_agent import SandboxAgent
42
+ from agents.ui_agent import UIAgent
43
+ from agents.reasoning_agent import ReasoningAgent
44
+ from agents.browser_agent import BrowserAgent
45
+ from agents.file_agent import FileAgent
46
+ from agents.git_agent import GitAgent
47
+ from agents.test_agent import TestAgent
48
+ from agents.vision_agent import VisionAgent
49
+ from connectors.manager import ConnectorManager
50
+ from api.routes import tasks, chat, memory, health, connectors, agents as agents_router
51
+ from api.routes import github
52
+
53
+ structlog.configure(
54
+ processors=[
55
+ structlog.processors.TimeStamper(fmt="iso"),
56
+ structlog.stdlib.add_log_level,
57
+ structlog.processors.StackInfoRenderer(),
58
+ structlog.dev.ConsoleRenderer(),
59
+ ]
60
+ )
61
+ log = structlog.get_logger()
62
+
63
+ limiter = Limiter(key_func=get_remote_address)
64
+
65
+ ws_manager: WebSocketManager = None
66
+ task_engine: TaskEngine = None
67
+ ai_router: AIRouterV10 = None
68
+ orchestrator: GodAgentOrchestratorV7 = None
69
+ connector_manager: ConnectorManager = None
70
+
71
+ computer_use_sessions: Dict[str, List[Dict]] = {}
72
+
73
+
74
+ def add_computer_use_step(session_id: str, step_type: str, data: Dict):
75
+ if session_id not in computer_use_sessions:
76
+ computer_use_sessions[session_id] = []
77
+ computer_use_sessions[session_id].append({
78
+ "id": uuid.uuid4().hex[:8],
79
+ "type": step_type,
80
+ "data": data,
81
+ "timestamp": time.time(),
82
+ "status": "running",
83
+ })
84
+ computer_use_sessions[session_id] = computer_use_sessions[session_id][-100:]
85
+
86
+
87
+ @asynccontextmanager
88
+ async def lifespan(app: FastAPI):
89
+ global ws_manager, task_engine, ai_router, orchestrator, connector_manager
90
+
91
+ log.info("GOD AGENT OS v11 starting...")
92
+
93
+ # FIX: init_db() takes no arguments
94
+ await init_db()
95
+
96
+ ai_router = AIRouterV10()
97
+ ws_manager = WebSocketManager()
98
+
99
+ # FIX: TaskEngine only takes ws_manager
100
+ task_engine = TaskEngine(ws_manager)
101
+
102
+ orchestrator = GodAgentOrchestratorV7(ws_manager, ai_router)
103
+ agents_map = {
104
+ "chat": ChatAgent(ws_manager, ai_router),
105
+ "planner": PlannerAgent(ws_manager, ai_router),
106
+ "coding": CodingAgent(ws_manager, ai_router),
107
+ "debug": DebugAgent(ws_manager, ai_router),
108
+ "memory": MemoryAgent(ws_manager, ai_router),
109
+ "connector": ConnectorAgent(ws_manager, ai_router),
110
+ "deploy": DeployAgent(ws_manager, ai_router),
111
+ "workflow": WorkflowAgent(ws_manager, ai_router),
112
+ "sandbox": SandboxAgent(ws_manager, ai_router),
113
+ "ui": UIAgent(ws_manager, ai_router),
114
+ "reasoning": ReasoningAgent(ws_manager, ai_router),
115
+ "browser": BrowserAgent(ws_manager, ai_router),
116
+ "file": FileAgent(ws_manager, ai_router),
117
+ "git": GitAgent(ws_manager, ai_router),
118
+ "test": TestAgent(ws_manager, ai_router),
119
+ "vision": VisionAgent(ws_manager, ai_router),
120
+ }
121
+ for name, agent in agents_map.items():
122
+ orchestrator.register_agent(name, agent)
123
+
124
+ # FIX: ConnectorManager is sync β€” no await initialize()
125
+ connector_manager = ConnectorManager()
126
+
127
+ app.state.ws_manager = ws_manager
128
+ app.state.task_engine = task_engine
129
+ app.state.ai_router = ai_router
130
+ app.state.orchestrator = orchestrator
131
+ app.state.connector_manager = connector_manager
132
+
133
+ # FIX: use start() not run()
134
+ asyncio.create_task(task_engine.start())
135
+
136
+ log.info("GOD AGENT OS v11 ready!", agents=len(agents_map))
137
+ yield
138
+
139
+ log.info("Shutting down...")
140
+ await task_engine.stop()
141
+
142
+
143
+ app = FastAPI(
144
+ title="GOD AGENT OS v11",
145
+ description="Autonomous Engineering OS",
146
+ version="11.0.0",
147
+ docs_url="/api/docs",
148
+ redoc_url="/api/redoc",
149
+ lifespan=lifespan,
150
+ )
151
+
152
+ app.state_limiter = limiter
153
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
154
+ app.add_middleware(GZipMiddleware, minimum_size=1000)
155
+ app.add_middleware(
156
+ CORSMiddleware,
157
+ allow_origins=["*"],
158
+ allow_credentials=True,
159
+ allow_methods=["*"],
160
+ allow_headers=["*"],
161
+ )
162
+
163
+ app.include_router(health.router, prefix="/api/v1", tags=["health"])
164
+ app.include_router(chat.router, prefix="/api/v1", tags=["chat"])
165
+ app.include_router(tasks.router, prefix="/api/v1/tasks", tags=["tasks"])
166
+ app.include_router(memory.router, prefix="/api/v1/memory", tags=["memory"])
167
+ app.include_router(connectors.router, prefix="/api/v1/connectors", tags=["connectors"])
168
+ app.include_router(agents_router.router, prefix="/api/v1/agents", tags=["agents"])
169
+ app.include_router(github.router, prefix="/api/v1/github", tags=["github"])
170
+
171
+
172
+ @app.websocket("/ws/{session_id}")
173
+ async def websocket_endpoint(websocket: WebSocket, session_id: str):
174
+ # FIX: connect takes (websocket, room) β€” use chat: prefix
175
+ await ws_manager.connect(websocket, f"chat:{session_id}")
176
+ try:
177
+ while True:
178
+ data = await websocket.receive_json()
179
+ event_type = data.get("type", "")
180
+
181
+ if event_type == "ping":
182
+ await websocket.send_json({"type": "pong", "ts": time.time()})
183
+
184
+ elif event_type == "message":
185
+ message = data.get("message", "")
186
+ task_id = uuid.uuid4().hex[:12]
187
+ # FIX: use emit_chat() not emit_task()
188
+ await ws_manager.emit_chat(session_id, "task_start", {
189
+ "task_id": task_id,
190
+ "message": message[:100],
191
+ })
192
+ asyncio.create_task(_run_ws_task(message, task_id, session_id))
193
+
194
+ elif event_type == "stop":
195
+ task_id = data.get("task_id", "")
196
+ if task_id:
197
+ await task_engine.cancel(task_id)
198
+
199
+ except WebSocketDisconnect:
200
+ ws_manager.disconnect(websocket, f"chat:{session_id}")
201
+
202
+
203
+ async def _run_ws_task(message: str, task_id: str, session_id: str):
204
+ try:
205
+ result = await orchestrator.orchestrate(
206
+ user_message=message,
207
+ task_id=task_id,
208
+ session_id=session_id,
209
+ )
210
+ await ws_manager.emit_chat(session_id, "task_complete", {
211
+ "task_id": task_id,
212
+ "result": result[:2000] if result else "",
213
+ })
214
+ except Exception as e:
215
+ await ws_manager.emit_chat(session_id, "task_error", {
216
+ "task_id": task_id,
217
+ "error": str(e),
218
+ })
219
+
220
+
221
+ @app.get("/api/v1/computer-use/{session_id}")
222
+ async def get_computer_use_steps(session_id: str):
223
+ steps = computer_use_sessions.get(session_id, [])
224
+ return {"session_id": session_id, "steps": steps, "count": len(steps)}
225
+
226
+
227
+ @app.websocket("/ws/computer-use/{session_id}")
228
+ async def computer_use_ws(websocket: WebSocket, session_id: str):
229
+ await websocket.accept()
230
+ try:
231
+ last_idx = 0
232
+ while True:
233
+ steps = computer_use_sessions.get(session_id, [])
234
+ if len(steps) > last_idx:
235
+ for step in steps[last_idx:]:
236
+ await websocket.send_json({"type": "computer_use_step", "step": step})
237
+ last_idx = len(steps)
238
+ await asyncio.sleep(0.5)
239
+ except WebSocketDisconnect:
240
+ pass
241
+
242
+
243
+ @app.post("/api/v1/orchestrate")
244
+ async def orchestrate_goal(request: Request):
245
+ body = await request.json()
246
+ message = body.get("message", "")
247
+ session_id = body.get("session_id", uuid.uuid4().hex[:12])
248
+
249
+ if not message:
250
+ raise HTTPException(status_code=400, detail="Message required")
251
+
252
+ task_id = uuid.uuid4().hex[:12]
253
+ add_computer_use_step(session_id, "thinking", {
254
+ "message": f"Processing: {message[:100]}",
255
+ "task_id": task_id,
256
+ })
257
+
258
+ if body.get("stream", False):
259
+ async def stream_gen():
260
+ try:
261
+ result = await orchestrator.orchestrate(
262
+ user_message=message, task_id=task_id, session_id=session_id,
263
+ )
264
+ add_computer_use_step(session_id, "complete", {"result": result[:200] if result else ""})
265
+ yield f"data: {json.dumps({'type': 'complete', 'result': result, 'task_id': task_id, 'session_id': session_id})}\n\n"
266
+ except Exception as e:
267
+ yield f"data: {json.dumps({'type': 'error', 'error': str(e)})}\n\n"
268
+
269
+ return StreamingResponse(
270
+ stream_gen(),
271
+ media_type="text/event-stream",
272
+ headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
273
+ )
274
+
275
+ try:
276
+ result = await orchestrator.orchestrate(user_message=message, task_id=task_id, session_id=session_id)
277
+ add_computer_use_step(session_id, "complete", {"result": result[:200] if result else ""})
278
+ return {"task_id": task_id, "session_id": session_id, "result": result, "status": "complete"}
279
+ except Exception as e:
280
+ log.error("Orchestration error", error=str(e))
281
+ raise HTTPException(status_code=500, detail=str(e))
282
+
283
+
284
+ @app.post("/api/v1/agents/{agent_name}/run")
285
+ async def run_agent(agent_name: str, request: Request):
286
+ body = await request.json()
287
+ task = body.get("task", "")
288
+ session_id = body.get("session_id", uuid.uuid4().hex[:12])
289
+ task_id = uuid.uuid4().hex[:12]
290
+
291
+ agent = orchestrator.get_agent(agent_name)
292
+ if not agent:
293
+ raise HTTPException(status_code=404, detail=f"Agent '{agent_name}' not found")
294
+
295
+ try:
296
+ result = await agent.run(
297
+ task=task,
298
+ context=body.get("context", {}),
299
+ task_id=task_id,
300
+ session_id=session_id,
301
+ )
302
+ return {"agent": agent_name, "task_id": task_id, "result": result, "status": "complete"}
303
+ except Exception as e:
304
+ raise HTTPException(status_code=500, detail=str(e))
305
+
306
+
307
+ @app.get("/api/v1/agents")
308
+ async def list_agents():
309
+ names = ["chat", "planner", "coding", "debug", "memory", "connector",
310
+ "deploy", "workflow", "sandbox", "ui", "reasoning",
311
+ "browser", "file", "git", "test", "vision"]
312
+ agents_list = []
313
+ for name in names:
314
+ agent = orchestrator.get_agent(name)
315
+ agents_list.append({
316
+ "name": name,
317
+ "available": agent is not None,
318
+ "class": type(agent).__name__ if agent else None,
319
+ })
320
+ return {"agents": agents_list, "total": len(agents_list)}
321
+
322
+
323
+ SPACE_DEFS = [
324
+ {"id": "god-core", "name": "God Core Space", "role": "orchestration", "agent": "orchestrator", "icon": "🧠"},
325
+ {"id": "coding", "name": "Coding Worker", "role": "code_generation", "agent": "coding", "icon": "⚑"},
326
+ {"id": "sandbox", "name": "Sandbox Worker", "role": "execution", "agent": "sandbox", "icon": "πŸ”§"},
327
+ {"id": "terminal", "name": "Terminal Worker", "role": "execution", "agent": "sandbox", "icon": "πŸ–₯️"},
328
+ {"id": "filesystem", "name": "FileSystem Worker", "role": "files", "agent": "file", "icon": "πŸ“"},
329
+ {"id": "browser", "name": "Browser Worker", "role": "research", "agent": "browser", "icon": "🌐"},
330
+ {"id": "vision", "name": "Vision Worker", "role": "ui_gen", "agent": "vision", "icon": "πŸ‘οΈ"},
331
+ {"id": "ui", "name": "UI Worker", "role": "ui", "agent": "ui", "icon": "🎨"},
332
+ {"id": "debug", "name": "Debug Worker", "role": "debugging", "agent": "debug", "icon": "πŸ›"},
333
+ {"id": "test", "name": "Test Worker", "role": "testing", "agent": "test", "icon": "πŸ§ͺ"},
334
+ {"id": "verification", "name": "Verification Worker", "role": "qa", "agent": "test", "icon": "βœ…"},
335
+ {"id": "git", "name": "Git Worker", "role": "git", "agent": "git", "icon": "πŸ”€"},
336
+ {"id": "deploy", "name": "Deploy Worker", "role": "deployment", "agent": "deploy", "icon": "πŸš€"},
337
+ {"id": "connector", "name": "Connector Worker", "role": "integration", "agent": "connector", "icon": "πŸ”Œ"},
338
+ {"id": "memory", "name": "Memory Worker", "role": "memory", "agent": "memory", "icon": "πŸ’Ύ"},
339
+ {"id": "knowledge", "name": "Knowledge Worker", "role": "knowledge", "agent": "memory", "icon": "πŸ“š"},
340
+ {"id": "workflow", "name": "Workflow Worker", "role": "automation", "agent": "workflow", "icon": "βš™οΈ"},
341
+ {"id": "eventbus", "name": "Event Bus", "role": "events", "agent": None, "icon": "πŸ“‘"},
342
+ {"id": "model-router", "name": "Model Router", "role": "ai_routing", "agent": None, "icon": "πŸ€–"},
343
+ {"id": "observability", "name": "Observability", "role": "monitoring", "agent": None, "icon": "πŸ“Š"},
344
+ {"id": "session-runtime", "name": "Session Runtime", "role": "sessions", "agent": None, "icon": "⏱️"},
345
+ {"id": "auth-gateway", "name": "Auth Gateway", "role": "auth", "agent": None, "icon": "πŸ”"},
346
+ ]
347
+
348
+
349
+ @app.get("/api/v1/spaces")
350
+ async def get_spaces():
351
+ spaces_status = []
352
+ for space in SPACE_DEFS:
353
+ agent_name = space.get("agent")
354
+ agent = orchestrator.get_agent(agent_name) if agent_name else None
355
+ spaces_status.append({
356
+ **space,
357
+ "status": "active" if (agent is not None or agent_name is None) else "inactive",
358
+ "online": True,
359
+ "backend": "god-agent-os-v11",
360
+ "tasks_completed": 0,
361
+ })
362
+ return {
363
+ "spaces": spaces_status,
364
+ "total": len(spaces_status),
365
+ "active": len([s for s in spaces_status if s["status"] == "active"]),
366
+ "backend_url": os.environ.get("SPACE_URL", "https://pyae1994-autonomous-coding-system.hf.space"),
367
+ }
368
+
369
+
370
+ @app.get("/health")
371
+ @app.get("/api/v1/health")
372
+ async def health_check():
373
+ stats = ai_router.get_stats() if ai_router else {}
374
+ active_providers = [name for name, s in stats.items() if s.get("available")]
375
+ return {
376
+ "status": "healthy",
377
+ "version": "11.0.0",
378
+ "timestamp": time.time(),
379
+ "agents": 16,
380
+ "spaces": 22,
381
+ "ai_providers": active_providers,
382
+ "mode": "god_mode",
383
+ }
384
+
385
+
386
+ @app.get("/api/v1/ai/stats")
387
+ async def get_ai_stats():
388
+ return {"stats": ai_router.get_stats() if ai_router else {}}
389
+
390
+
391
+ @app.get("/api/v1/ai/pool-status")
392
+ async def get_pool_status():
393
+ return {"pools": ai_router.get_pool_status() if ai_router else {}}
394
+
395
+
396
+ @app.get("/api/v1/system/status")
397
+ async def system_status():
398
+ ai_stats = ai_router.get_stats() if ai_router else {}
399
+ cs = connector_manager.get_summary() if connector_manager else {"connected": 0, "total": 0}
400
+ return {
401
+ "system": "god_agent_os_v11",
402
+ "status": "operational",
403
+ "timestamp": time.time(),
404
+ "ai_router": {
405
+ "providers": ai_stats,
406
+ "active": len([v for v in ai_stats.values() if v.get("available")]),
407
+ },
408
+ "agents": {
409
+ "total": 16,
410
+ "online": 16,
411
+ },
412
+ "spaces": {"total": 22, "all_in_backend": True},
413
+ "connectors": cs,
414
+ "features": {
415
+ "god_mode": True,
416
+ "computer_use": True,
417
+ "streaming": True,
418
+ "websocket": True,
419
+ "multi_agent": True,
420
+ "burmese_language": True,
421
+ },
422
+ }
423
+
424
+
425
+ @app.get("/")
426
+ async def root():
427
+ return {
428
+ "name": "GOD AGENT OS v11",
429
+ "version": "11.0.0",
430
+ "status": "operational",
431
+ "mode": "GOD_MODE",
432
+ "docs": "/api/docs",
433
+ "health": "/health",
434
+ "agents": 16,
435
+ "spaces": 22,
436
+ }
437
+
438
+
439
+ if __name__ == "__main__":
440
+ import uvicorn
441
+ port = int(os.environ.get("PORT", 7860))
442
+ uvicorn.run("main_v11:app", host="0.0.0.0", port=port, reload=False, workers=1)
_legacy/main_v7.py ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ πŸš€ GOD AGENT OS v7 β€” Autonomous Engineering Operating System
3
+ Manus + Genspark + Devin (OneHand) Combined
4
+ Version: 7.0.0
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import logging
10
+ import os
11
+ import time
12
+ import uuid
13
+ from contextlib import asynccontextmanager
14
+ from typing import Optional
15
+
16
+ import structlog
17
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Request
18
+ from fastapi.middleware.cors import CORSMiddleware
19
+ from fastapi.middleware.gzip import GZipMiddleware
20
+ from fastapi.responses import JSONResponse
21
+ from slowapi import Limiter, _rate_limit_exceeded_handler
22
+ from slowapi.util import get_remote_address
23
+ 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.websocket_manager import WebSocketManager
28
+ from core.task_engine import TaskEngine
29
+ from memory.db import init_db
30
+
31
+ # ─── v7 God Agent Ecosystem ────────────────────────────────────────────────────
32
+ from ai_router.router import AIRouter
33
+ from agents.orchestrator_v7 import GodAgentOrchestratorV7
34
+ from agents.chat_agent import ChatAgent
35
+ from agents.planner_agent import PlannerAgent
36
+ from agents.coding_agent import CodingAgent
37
+ from agents.debug_agent import DebugAgent
38
+ from agents.memory_agent import MemoryAgent
39
+ from agents.connector_agent import ConnectorAgent
40
+ from agents.deploy_agent import DeployAgent
41
+ from agents.workflow_agent import WorkflowAgent
42
+ from agents.sandbox_agent import SandboxAgent
43
+ from agents.ui_agent import UIAgent
44
+ from agents.reasoning_agent import ReasoningAgent
45
+ # v7 new agents
46
+ from agents.browser_agent import BrowserAgent
47
+ from agents.file_agent import FileAgent
48
+ from agents.git_agent import GitAgent
49
+ from agents.test_agent import TestAgent
50
+ from agents.vision_agent import VisionAgent
51
+ from connectors.manager import ConnectorManager
52
+
53
+ # ─── Structured Logging ────────────────────────────────────────────────────────
54
+ structlog.configure(
55
+ processors=[
56
+ structlog.processors.TimeStamper(fmt="iso"),
57
+ structlog.stdlib.add_log_level,
58
+ structlog.processors.StackInfoRenderer(),
59
+ structlog.dev.ConsoleRenderer(),
60
+ ]
61
+ )
62
+ log = structlog.get_logger()
63
+
64
+ # ─── Rate Limiter ──────────────────────────────────────────────────────────────
65
+ limiter = Limiter(key_func=get_remote_address)
66
+
67
+ # ─── Global Managers ──────────────────────────────────────────────────────────
68
+ ws_manager = WebSocketManager()
69
+ task_engine = TaskEngine(ws_manager)
70
+ ai_router = AIRouter(ws_manager)
71
+ connector_manager = ConnectorManager()
72
+
73
+
74
+ # ─── Build v7 God Agent Ecosystem ─────────────────────────────────────────────
75
+ def build_orchestrator_v7() -> GodAgentOrchestratorV7:
76
+ orchestrator = GodAgentOrchestratorV7(ws_manager=ws_manager, ai_router=ai_router)
77
+
78
+ # ── Core agents (v3 retained) ──────────────────────────────────────────
79
+ orchestrator.register_agent("chat", ChatAgent(ws_manager, ai_router))
80
+ orchestrator.register_agent("planner", PlannerAgent(ws_manager, ai_router))
81
+ orchestrator.register_agent("coding", CodingAgent(ws_manager, ai_router))
82
+ orchestrator.register_agent("debug", DebugAgent(ws_manager, ai_router))
83
+ orchestrator.register_agent("memory", MemoryAgent(ws_manager, ai_router))
84
+ orchestrator.register_agent("connector", ConnectorAgent(ws_manager, ai_router))
85
+ orchestrator.register_agent("deploy", DeployAgent(ws_manager, ai_router))
86
+ orchestrator.register_agent("workflow", WorkflowAgent(ws_manager, ai_router))
87
+ orchestrator.register_agent("sandbox", SandboxAgent(ws_manager, ai_router))
88
+ orchestrator.register_agent("ui", UIAgent(ws_manager, ai_router))
89
+ orchestrator.register_agent("reasoning", ReasoningAgent(ws_manager, ai_router))
90
+
91
+ # ── v7 NEW agents ──────────────────────────────────────────────────────
92
+ orchestrator.register_agent("browser", BrowserAgent(ws_manager, ai_router))
93
+ orchestrator.register_agent("file", FileAgent(ws_manager, ai_router))
94
+ orchestrator.register_agent("git", GitAgent(ws_manager, ai_router))
95
+ orchestrator.register_agent("test", TestAgent(ws_manager, ai_router))
96
+ orchestrator.register_agent("vision", VisionAgent(ws_manager, ai_router))
97
+
98
+ log.info("πŸ€– GOD AGENT v7 Ecosystem initialized", agents=16)
99
+ return orchestrator
100
+
101
+
102
+ orchestrator = build_orchestrator_v7()
103
+
104
+
105
+ @asynccontextmanager
106
+ async def lifespan(app: FastAPI):
107
+ log.info("πŸš€ Starting GOD AGENT OS v7 β€” Autonomous Engineering Platform...")
108
+ await init_db()
109
+ await task_engine.start()
110
+ asyncio.create_task(ws_manager.heartbeat_loop())
111
+ log.info("βœ… GOD AGENT v7 β€” All 16 agents online")
112
+ log.info("πŸ€– Core: Chat, Planner, Coding, Debug, Memory, Connector, Deploy, Workflow, Sandbox, UI, Reasoning")
113
+ log.info("⚑ v7 New: Browser, File, Git, Test, Vision")
114
+ log.info("🌐 AI Router: OpenAI β†’ Groq β†’ Cerebras β†’ OpenRouter β†’ Anthropic (auto-failover)")
115
+ yield
116
+ log.info("πŸ›‘ Shutting down GOD AGENT v7...")
117
+ await task_engine.stop()
118
+ log.info("βœ… Shutdown complete")
119
+
120
+
121
+ # ─── FastAPI App ───────────────────────────────────────────────────────────────
122
+ app = FastAPI(
123
+ title="πŸ€– GOD AGENT OS v7",
124
+ description="Autonomous Engineering OS β€” Manus + Genspark + Devin (OneHand) Combined",
125
+ version="7.0.0",
126
+ lifespan=lifespan,
127
+ docs_url="/api/docs",
128
+ redoc_url="/api/redoc",
129
+ )
130
+
131
+ app.state.limiter = limiter
132
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
133
+
134
+ # ─── Share state ───────────────────────────────────────────────────────────────
135
+ app.state.ws_manager = ws_manager
136
+ app.state.task_engine = task_engine
137
+ app.state.ai_router = ai_router
138
+ app.state.orchestrator = orchestrator
139
+ app.state.connector_manager = connector_manager
140
+
141
+ # ─── Middleware ────────────────────────────────────────────────────────────────
142
+ app.add_middleware(
143
+ CORSMiddleware,
144
+ allow_origins=["*"],
145
+ allow_credentials=True,
146
+ allow_methods=["*"],
147
+ allow_headers=["*"],
148
+ )
149
+ app.add_middleware(GZipMiddleware, minimum_size=1000)
150
+
151
+
152
+ @app.middleware("http")
153
+ async def log_requests(request: Request, call_next):
154
+ start = time.time()
155
+ response = await call_next(request)
156
+ duration = round((time.time() - start) * 1000, 2)
157
+ log.info("HTTP", method=request.method, path=request.url.path, status=response.status_code, ms=duration)
158
+ return response
159
+
160
+
161
+ # ─── REST API Routers ──────────────────────────────────────────────────────────
162
+ app.include_router(health.router, prefix="/api/v1", tags=["health"])
163
+ app.include_router(tasks.router, prefix="/api/v1/tasks", tags=["tasks"])
164
+ app.include_router(chat.router, prefix="/api/v1", tags=["chat"])
165
+ app.include_router(memory.router, prefix="/api/v1/memory", tags=["memory"])
166
+ app.include_router(github.router, prefix="/api/v1/github", tags=["github"])
167
+ app.include_router(connectors.router, prefix="/api/v1/connectors", tags=["connectors"])
168
+ app.include_router(agents_router.router, prefix="/api/v1/agents", tags=["agents"])
169
+
170
+
171
+ # ─── WebSocket Endpoints ───────────────────────────────────────────────────────
172
+ @app.websocket("/ws/tasks/{task_id}")
173
+ async def ws_task(websocket: WebSocket, task_id: str):
174
+ await ws_manager.connect(websocket, room=f"task:{task_id}")
175
+ try:
176
+ while True:
177
+ data = await websocket.receive_text()
178
+ msg = json.loads(data)
179
+ if msg.get("type") == "ping":
180
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
181
+ except WebSocketDisconnect:
182
+ ws_manager.disconnect(websocket, room=f"task:{task_id}")
183
+
184
+
185
+ @app.websocket("/ws/logs")
186
+ async def ws_logs(websocket: WebSocket):
187
+ await ws_manager.connect(websocket, room="logs")
188
+ try:
189
+ while True:
190
+ data = await websocket.receive_text()
191
+ msg = json.loads(data)
192
+ if msg.get("type") == "ping":
193
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
194
+ except WebSocketDisconnect:
195
+ ws_manager.disconnect(websocket, room="logs")
196
+
197
+
198
+ @app.websocket("/ws/chat/{session_id}")
199
+ async def ws_chat(websocket: WebSocket, session_id: str):
200
+ await ws_manager.connect(websocket, room=f"chat:{session_id}")
201
+ try:
202
+ while True:
203
+ data = await websocket.receive_text()
204
+ msg = json.loads(data)
205
+ if msg.get("type") == "ping":
206
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
207
+ elif msg.get("type") == "chat_message":
208
+ asyncio.create_task(
209
+ orchestrator.orchestrate(
210
+ user_message=msg.get("content", ""),
211
+ session_id=session_id,
212
+ context=msg.get("context", {}),
213
+ )
214
+ )
215
+ elif msg.get("type") == "task_message":
216
+ from core.models import TaskCreateRequest
217
+ req = TaskCreateRequest(
218
+ goal=msg.get("content", ""),
219
+ session_id=session_id,
220
+ )
221
+ asyncio.create_task(task_engine.submit(req))
222
+ except WebSocketDisconnect:
223
+ ws_manager.disconnect(websocket, room=f"chat:{session_id}")
224
+
225
+
226
+ @app.websocket("/ws/agent/status")
227
+ async def ws_agent_status(websocket: WebSocket):
228
+ await ws_manager.connect(websocket, room="agent_status")
229
+ try:
230
+ while True:
231
+ data = await websocket.receive_text()
232
+ msg = json.loads(data)
233
+ if msg.get("type") == "ping":
234
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
235
+ elif msg.get("type") == "get_status":
236
+ await websocket.send_json({
237
+ "type": "agent_status",
238
+ "data": orchestrator.get_status(),
239
+ })
240
+ except WebSocketDisconnect:
241
+ ws_manager.disconnect(websocket, room="agent_status")
242
+
243
+
244
+ @app.websocket("/ws/sandbox/{session_id}")
245
+ async def ws_sandbox(websocket: WebSocket, session_id: str):
246
+ await ws_manager.connect(websocket, room=f"sandbox:{session_id}")
247
+ sandbox = orchestrator.get_agent("sandbox")
248
+ try:
249
+ while True:
250
+ data = await websocket.receive_text()
251
+ msg = json.loads(data)
252
+ if msg.get("type") == "ping":
253
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
254
+ elif msg.get("type") == "execute" and sandbox:
255
+ cmd = msg.get("command", "")
256
+ result = await sandbox.execute(cmd, session_id=session_id)
257
+ await websocket.send_json({
258
+ "type": "terminal_output",
259
+ "command": cmd,
260
+ "output": result,
261
+ "timestamp": time.time(),
262
+ })
263
+ except WebSocketDisconnect:
264
+ ws_manager.disconnect(websocket, room=f"sandbox:{session_id}")
265
+
266
+
267
+ # ─── v7 New Endpoints ──────────────────────────────────────────────────────────
268
+ @app.post("/api/v1/browser/research")
269
+ async def browser_research(request: Request):
270
+ body = await request.json()
271
+ query = body.get("query", "")
272
+ session_id = body.get("session_id", "")
273
+ browser = orchestrator.get_agent("browser")
274
+ if not browser:
275
+ raise HTTPException(status_code=503, detail="BrowserAgent not available")
276
+ result = await browser.run(query, session_id=session_id)
277
+ return {"result": result}
278
+
279
+
280
+ @app.get("/api/v1/files/workspace")
281
+ async def list_workspace():
282
+ file_agent = orchestrator.get_agent("file")
283
+ if not file_agent:
284
+ return {"workspace": "/tmp/god_workspace", "files": [], "total": 0}
285
+ return file_agent.list_workspace()
286
+
287
+
288
+ @app.post("/api/v1/git/pr")
289
+ async def create_pr(request: Request):
290
+ body = await request.json()
291
+ git_agent = orchestrator.get_agent("git")
292
+ if not git_agent:
293
+ raise HTTPException(status_code=503, detail="GitAgent not available")
294
+ result = await git_agent.create_github_pr(
295
+ repo_owner=body.get("owner", ""),
296
+ repo_name=body.get("repo", ""),
297
+ title=body.get("title", ""),
298
+ body=body.get("body", ""),
299
+ head_branch=body.get("head_branch", "main"),
300
+ base_branch=body.get("base_branch", "main"),
301
+ )
302
+ return result
303
+
304
+
305
+ @app.post("/api/v1/vision/generate")
306
+ async def generate_ui(request: Request):
307
+ body = await request.json()
308
+ vision = orchestrator.get_agent("vision")
309
+ if not vision:
310
+ raise HTTPException(status_code=503, detail="VisionAgent not available")
311
+ result = await vision.run(
312
+ body.get("prompt", ""),
313
+ context=body.get("context", {}),
314
+ session_id=body.get("session_id", ""),
315
+ )
316
+ return {"result": result}
317
+
318
+
319
+ # ─── Root ──────────────────────────────────────────────────────────────────────
320
+ @app.get("/")
321
+ async def root():
322
+ cs = connector_manager.get_summary()
323
+ status = orchestrator.get_status()
324
+ return {
325
+ "name": "πŸ€– GOD AGENT OS v7",
326
+ "version": "7.0.0",
327
+ "status": "operational",
328
+ "mode": "autonomous_engineering_os",
329
+ "description": "Manus + Genspark + Devin (OneHand) β€” Autonomous Engineering Platform",
330
+ "agents": status["agents"],
331
+ "total_agents": status["total_agents"],
332
+ "capabilities": status["capabilities"],
333
+ "connectors": {
334
+ "connected": cs["connected"],
335
+ "total": cs["total"],
336
+ "ai_ready": cs["ai_ready"],
337
+ },
338
+ "docs": "/api/docs",
339
+ "websockets": [
340
+ "/ws/tasks/{task_id}",
341
+ "/ws/logs",
342
+ "/ws/chat/{session_id}",
343
+ "/ws/agent/status",
344
+ "/ws/sandbox/{session_id}",
345
+ ],
346
+ "v7_new_features": [
347
+ "🌐 BrowserAgent β€” Web research & scraping",
348
+ "πŸ“ FileAgent β€” Full file system control & project scaffolding",
349
+ "πŸ”€ GitAgent β€” Autonomous Git & GitHub PR operations",
350
+ "πŸ§ͺ TestAgent β€” Auto test generation & execution",
351
+ "🎨 VisionAgent β€” Design-to-code UI generation",
352
+ "⚑ 16-agent parallel orchestration",
353
+ "πŸ”„ Advanced self-healing loop",
354
+ "🧠 Enhanced intent classification",
355
+ "πŸ“Š Real-time execution timeline",
356
+ ],
357
+ }
358
+
359
+
360
+ if __name__ == "__main__":
361
+ import uvicorn
362
+ port = int(os.environ.get("PORT", 8000))
363
+ uvicorn.run("main_v7:app", host="0.0.0.0", port=port, reload=False, workers=1)
_legacy/main_v8.py ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ πŸš€ GOD AGENT OS v8 β€” Autonomous Engineering Operating System
3
+ Manus + Genspark + Devin (OneHand) Combined
4
+ Version: 8.0.0 β€” KeyPool Multi-API Routing (Gemini + SambaNova Primary LLMs)
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import os
10
+ import time
11
+ import uuid
12
+ from contextlib import asynccontextmanager
13
+ from typing import Optional
14
+
15
+ import structlog
16
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Request
17
+ from fastapi.middleware.cors import CORSMiddleware
18
+ from fastapi.middleware.gzip import GZipMiddleware
19
+ from fastapi.responses import JSONResponse
20
+ from slowapi import Limiter, _rate_limit_exceeded_handler
21
+ from slowapi.util import get_remote_address
22
+ from slowapi.errors import RateLimitExceeded
23
+
24
+ from api.routes import tasks, chat, memory, github, health
25
+ from api.routes import connectors, agents as agents_router
26
+ from api.websocket_manager import WebSocketManager
27
+ from core.task_engine import TaskEngine
28
+ from memory.db import init_db
29
+
30
+ # ─── v8 AI Router (KeyPool-based) ─────────────────────────────────────────────
31
+ from ai_router.router_v8 import AIRouterV8
32
+
33
+ # ─── Agent Ecosystem ──────────────────────────────────────────────────────────
34
+ from agents.orchestrator_v7 import GodAgentOrchestratorV7
35
+ from agents.chat_agent import ChatAgent
36
+ from agents.planner_agent import PlannerAgent
37
+ from agents.coding_agent import CodingAgent
38
+ from agents.debug_agent import DebugAgent
39
+ from agents.memory_agent import MemoryAgent
40
+ from agents.connector_agent import ConnectorAgent
41
+ from agents.deploy_agent import DeployAgent
42
+ from agents.workflow_agent import WorkflowAgent
43
+ from agents.sandbox_agent import SandboxAgent
44
+ from agents.ui_agent import UIAgent
45
+ from agents.reasoning_agent import ReasoningAgent
46
+ from agents.browser_agent import BrowserAgent
47
+ from agents.file_agent import FileAgent
48
+ from agents.git_agent import GitAgent
49
+ from agents.test_agent import TestAgent
50
+ from agents.vision_agent import VisionAgent
51
+ from connectors.manager import ConnectorManager
52
+
53
+ # ─── Structured Logging ───────────────────────────────────────────────────────
54
+ structlog.configure(
55
+ processors=[
56
+ structlog.processors.TimeStamper(fmt="iso"),
57
+ structlog.stdlib.add_log_level,
58
+ structlog.processors.StackInfoRenderer(),
59
+ structlog.dev.ConsoleRenderer(),
60
+ ]
61
+ )
62
+ log = structlog.get_logger()
63
+
64
+ limiter = Limiter(key_func=get_remote_address)
65
+
66
+ # ─── Global Managers ──────────────────────────────────────────────────────────
67
+ ws_manager = WebSocketManager()
68
+ task_engine = TaskEngine(ws_manager)
69
+ ai_router = AIRouterV8(ws_manager)
70
+ connector_manager = ConnectorManager()
71
+
72
+
73
+ def build_orchestrator() -> GodAgentOrchestratorV7:
74
+ orchestrator = GodAgentOrchestratorV7(ws_manager=ws_manager, ai_router=ai_router)
75
+ orchestrator.register_agent("chat", ChatAgent(ws_manager, ai_router))
76
+ orchestrator.register_agent("planner", PlannerAgent(ws_manager, ai_router))
77
+ orchestrator.register_agent("coding", CodingAgent(ws_manager, ai_router))
78
+ orchestrator.register_agent("debug", DebugAgent(ws_manager, ai_router))
79
+ orchestrator.register_agent("memory", MemoryAgent(ws_manager, ai_router))
80
+ orchestrator.register_agent("connector", ConnectorAgent(ws_manager, ai_router))
81
+ orchestrator.register_agent("deploy", DeployAgent(ws_manager, ai_router))
82
+ orchestrator.register_agent("workflow", WorkflowAgent(ws_manager, ai_router))
83
+ orchestrator.register_agent("sandbox", SandboxAgent(ws_manager, ai_router))
84
+ orchestrator.register_agent("ui", UIAgent(ws_manager, ai_router))
85
+ orchestrator.register_agent("reasoning", ReasoningAgent(ws_manager, ai_router))
86
+ orchestrator.register_agent("browser", BrowserAgent(ws_manager, ai_router))
87
+ orchestrator.register_agent("file", FileAgent(ws_manager, ai_router))
88
+ orchestrator.register_agent("git", GitAgent(ws_manager, ai_router))
89
+ orchestrator.register_agent("test", TestAgent(ws_manager, ai_router))
90
+ orchestrator.register_agent("vision", VisionAgent(ws_manager, ai_router))
91
+ log.info("πŸ€– GOD AGENT v8 Ecosystem initialized", agents=16)
92
+ return orchestrator
93
+
94
+
95
+ orchestrator = build_orchestrator()
96
+
97
+
98
+ @asynccontextmanager
99
+ async def lifespan(app: FastAPI):
100
+ log.info("πŸš€ Starting GOD AGENT OS v8 β€” KeyPool Multi-API Edition...")
101
+ await init_db()
102
+ await task_engine.start()
103
+ asyncio.create_task(ws_manager.heartbeat_loop())
104
+ # Print router status
105
+ stats = ai_router.get_stats()
106
+ active = [name for name, s in stats.items() if s["available"]]
107
+ log.info("βœ… GOD AGENT v8 β€” 16 agents online")
108
+ log.info(f"πŸ”‘ Active AI providers: {active}")
109
+ log.info("🌐 AI Routing: SambaNova β†’ Gemini β†’ OpenAI β†’ Groq β†’ Cerebras β†’ OpenRouter β†’ Anthropic")
110
+ yield
111
+ log.info("πŸ›‘ Shutting down GOD AGENT v8...")
112
+ await task_engine.stop()
113
+
114
+
115
+ app = FastAPI(
116
+ title="πŸ€– GOD AGENT OS v8",
117
+ description="Autonomous Engineering OS β€” Gemini + SambaNova KeyPool Multi-API Routing",
118
+ version="8.0.0",
119
+ lifespan=lifespan,
120
+ docs_url="/api/docs",
121
+ redoc_url="/api/redoc",
122
+ )
123
+
124
+ app.state.limiter = limiter
125
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
126
+ app.state.ws_manager = ws_manager
127
+ app.state.task_engine = task_engine
128
+ app.state.ai_router = ai_router
129
+ app.state.orchestrator = orchestrator
130
+ app.state.connector_manager = connector_manager
131
+
132
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
133
+ app.add_middleware(GZipMiddleware, minimum_size=1000)
134
+
135
+
136
+ @app.middleware("http")
137
+ async def log_requests(request: Request, call_next):
138
+ start = time.time()
139
+ response = await call_next(request)
140
+ ms = round((time.time() - start) * 1000, 2)
141
+ log.info("HTTP", method=request.method, path=request.url.path, status=response.status_code, ms=ms)
142
+ return response
143
+
144
+
145
+ # ─── REST Routers ─────────────────────────────────────────────────────────────
146
+ app.include_router(health.router, prefix="/api/v1", tags=["health"])
147
+ app.include_router(tasks.router, prefix="/api/v1/tasks", tags=["tasks"])
148
+ app.include_router(chat.router, prefix="/api/v1", tags=["chat"])
149
+ app.include_router(memory.router, prefix="/api/v1/memory", tags=["memory"])
150
+ app.include_router(github.router, prefix="/api/v1/github", tags=["github"])
151
+ app.include_router(connectors.router, prefix="/api/v1/connectors", tags=["connectors"])
152
+ app.include_router(agents_router.router, prefix="/api/v1/agents", tags=["agents"])
153
+
154
+
155
+ # ─── WebSocket Endpoints ──────────────────────────────────────────────────────
156
+ @app.websocket("/ws/tasks/{task_id}")
157
+ async def ws_task(websocket: WebSocket, task_id: str):
158
+ await ws_manager.connect(websocket, room=f"task:{task_id}")
159
+ try:
160
+ while True:
161
+ data = await websocket.receive_text()
162
+ msg = json.loads(data)
163
+ if msg.get("type") == "ping":
164
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
165
+ except WebSocketDisconnect:
166
+ ws_manager.disconnect(websocket, room=f"task:{task_id}")
167
+
168
+
169
+ @app.websocket("/ws/logs")
170
+ async def ws_logs(websocket: WebSocket):
171
+ await ws_manager.connect(websocket, room="logs")
172
+ try:
173
+ while True:
174
+ data = await websocket.receive_text()
175
+ msg = json.loads(data)
176
+ if msg.get("type") == "ping":
177
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
178
+ except WebSocketDisconnect:
179
+ ws_manager.disconnect(websocket, room="logs")
180
+
181
+
182
+ @app.websocket("/ws/chat/{session_id}")
183
+ async def ws_chat(websocket: WebSocket, session_id: str):
184
+ await ws_manager.connect(websocket, room=f"chat:{session_id}")
185
+ try:
186
+ while True:
187
+ data = await websocket.receive_text()
188
+ msg = json.loads(data)
189
+ if msg.get("type") == "ping":
190
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
191
+ elif msg.get("type") == "chat_message":
192
+ asyncio.create_task(orchestrator.orchestrate(
193
+ user_message=msg.get("content", ""),
194
+ session_id=session_id,
195
+ context=msg.get("context", {}),
196
+ ))
197
+ elif msg.get("type") == "task_message":
198
+ from core.models import TaskCreateRequest
199
+ req = TaskCreateRequest(goal=msg.get("content", ""), session_id=session_id)
200
+ asyncio.create_task(task_engine.submit(req))
201
+ except WebSocketDisconnect:
202
+ ws_manager.disconnect(websocket, room=f"chat:{session_id}")
203
+
204
+
205
+ @app.websocket("/ws/agent/status")
206
+ async def ws_agent_status(websocket: WebSocket):
207
+ await ws_manager.connect(websocket, room="agent_status")
208
+ try:
209
+ while True:
210
+ data = await websocket.receive_text()
211
+ msg = json.loads(data)
212
+ if msg.get("type") == "ping":
213
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
214
+ elif msg.get("type") == "get_status":
215
+ await websocket.send_json({"type": "agent_status", "data": orchestrator.get_status()})
216
+ except WebSocketDisconnect:
217
+ ws_manager.disconnect(websocket, room="agent_status")
218
+
219
+
220
+ @app.websocket("/ws/sandbox/{session_id}")
221
+ async def ws_sandbox(websocket: WebSocket, session_id: str):
222
+ await ws_manager.connect(websocket, room=f"sandbox:{session_id}")
223
+ sandbox = orchestrator.get_agent("sandbox")
224
+ try:
225
+ while True:
226
+ data = await websocket.receive_text()
227
+ msg = json.loads(data)
228
+ if msg.get("type") == "ping":
229
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
230
+ elif msg.get("type") == "execute" and sandbox:
231
+ cmd = msg.get("command", "")
232
+ result = await sandbox.execute(cmd, session_id=session_id)
233
+ await websocket.send_json({"type": "terminal_output", "command": cmd, "output": result, "timestamp": time.time()})
234
+ except WebSocketDisconnect:
235
+ ws_manager.disconnect(websocket, room=f"sandbox:{session_id}")
236
+
237
+
238
+ # ─── v8 Key Pool & AI Router Status Endpoints ─────────────────────────────────
239
+ @app.get("/api/v1/ai/stats")
240
+ async def get_ai_stats():
241
+ return {"stats": ai_router.get_stats()}
242
+
243
+
244
+ @app.get("/api/v1/ai/pool-status")
245
+ async def get_pool_status():
246
+ return {"pools": ai_router.get_pool_status()}
247
+
248
+
249
+ @app.post("/api/v1/browser/research")
250
+ async def browser_research(request: Request):
251
+ body = await request.json()
252
+ browser = orchestrator.get_agent("browser")
253
+ if not browser:
254
+ raise HTTPException(status_code=503, detail="BrowserAgent not available")
255
+ result = await browser.run(body.get("query", ""), session_id=body.get("session_id", ""))
256
+ return {"result": result}
257
+
258
+
259
+ @app.get("/api/v1/files/workspace")
260
+ async def list_workspace():
261
+ file_agent = orchestrator.get_agent("file")
262
+ if not file_agent:
263
+ return {"workspace": "/tmp/god_workspace", "files": [], "total": 0}
264
+ return file_agent.list_workspace()
265
+
266
+
267
+ @app.post("/api/v1/git/pr")
268
+ async def create_pr(request: Request):
269
+ body = await request.json()
270
+ git_agent = orchestrator.get_agent("git")
271
+ if not git_agent:
272
+ raise HTTPException(status_code=503, detail="GitAgent not available")
273
+ result = await git_agent.create_github_pr(
274
+ repo_owner=body.get("owner", ""),
275
+ repo_name=body.get("repo", ""),
276
+ title=body.get("title", ""),
277
+ body=body.get("body", ""),
278
+ head_branch=body.get("head_branch", "main"),
279
+ base_branch=body.get("base_branch", "main"),
280
+ )
281
+ return result
282
+
283
+
284
+ @app.post("/api/v1/vision/generate")
285
+ async def generate_ui(request: Request):
286
+ body = await request.json()
287
+ vision = orchestrator.get_agent("vision")
288
+ if not vision:
289
+ raise HTTPException(status_code=503, detail="VisionAgent not available")
290
+ result = await vision.run(body.get("prompt", ""), context=body.get("context", {}), session_id=body.get("session_id", ""))
291
+ return {"result": result}
292
+
293
+
294
+ # ─── Root ─────────────────────────────────────────────────────────────────────
295
+ @app.get("/")
296
+ async def root():
297
+ cs = connector_manager.get_summary()
298
+ status = orchestrator.get_status()
299
+ stats = ai_router.get_stats()
300
+ active_providers = [name for name, s in stats.items() if s["available"]]
301
+ return {
302
+ "name": "πŸ€– GOD AGENT OS v8",
303
+ "version": "8.0.0",
304
+ "status": "operational",
305
+ "mode": "autonomous_engineering_os",
306
+ "description": "KeyPool Multi-API Routing β€” Gemini + SambaNova Primary LLMs",
307
+ "agents": status["agents"],
308
+ "total_agents": status["total_agents"],
309
+ "ai_providers": active_providers,
310
+ "connectors": {"connected": cs["connected"], "total": cs["total"]},
311
+ "docs": "/api/docs",
312
+ "v8_features": [
313
+ "πŸ”‘ KeyPool multi-key management (Gemini 6 keys + SambaNova 9 keys)",
314
+ "πŸ”„ Automatic key failover with cooldown tracking",
315
+ "⚑ SambaNova β†’ Gemini β†’ OpenAI β†’ Groq β†’ Cerebras chain",
316
+ "πŸ“Š Per-key usage stats & health monitoring",
317
+ "πŸ€– 16-agent autonomous fleet",
318
+ "🌐 Real-time streaming via WebSocket",
319
+ ],
320
+ }
321
+
322
+
323
+ if __name__ == "__main__":
324
+ import uvicorn
325
+ port = int(os.environ.get("PORT", 8000))
326
+ uvicorn.run("main_v8:app", host="0.0.0.0", port=port, reload=False, workers=1)
_legacy/main_v9.py ADDED
@@ -0,0 +1,417 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ πŸš€ GOD AGENT OS v10 β€” Distributed 22-Space Agent OS
3
+ Powered by Pyae Sone
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import os
9
+ import time
10
+ import uuid
11
+ from contextlib import asynccontextmanager
12
+ from typing import Optional
13
+
14
+ import structlog
15
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Request
16
+ from fastapi.middleware.cors import CORSMiddleware
17
+ from fastapi.middleware.gzip import GZipMiddleware
18
+ from fastapi.responses import JSONResponse
19
+ from slowapi import Limiter, _rate_limit_exceeded_handler
20
+ from slowapi.util import get_remote_address
21
+ from slowapi.errors import RateLimitExceeded
22
+
23
+ from api.routes import tasks, chat, memory, health
24
+ from api.routes import connectors, agents as agents_router
25
+ from api.websocket_manager import WebSocketManager
26
+ from core.task_engine import TaskEngine
27
+ from memory.db import init_db
28
+
29
+ # ─── v10 AI Router (Gemini + SambaNova + GitHub KeyPool routing) ─────────────
30
+ from ai_router.router_v10 import AIRouterV10 as AIRouterV8
31
+
32
+ # ─── v9 Agent Kernel & Spaces ─────────────────────────────────────────────────
33
+ from kernel.agent_kernel import AgentKernel
34
+ from spaces import SPACE_CATALOG, build_all_spaces
35
+
36
+ # ─── Legacy Agent Ecosystem (backward compatibility) ──────────────────────────
37
+ from agents.orchestrator_v7 import GodAgentOrchestratorV7
38
+ from agents.chat_agent import ChatAgent
39
+ from agents.planner_agent import PlannerAgent
40
+ from agents.coding_agent import CodingAgent
41
+ from agents.debug_agent import DebugAgent
42
+ from agents.memory_agent import MemoryAgent
43
+ from agents.connector_agent import ConnectorAgent
44
+ from agents.deploy_agent import DeployAgent
45
+ from agents.workflow_agent import WorkflowAgent
46
+ from agents.sandbox_agent import SandboxAgent
47
+ from agents.ui_agent import UIAgent
48
+ from agents.reasoning_agent import ReasoningAgent
49
+ from agents.browser_agent import BrowserAgent
50
+ from agents.file_agent import FileAgent
51
+ from agents.git_agent import GitAgent
52
+ from agents.test_agent import TestAgent
53
+ from agents.vision_agent import VisionAgent
54
+ from connectors.manager import ConnectorManager
55
+
56
+ # ─── API Routes ───────────────────────────────────────────────────────────────
57
+ from api.routes import github
58
+
59
+ # ─── Structured Logging ───────────────────────────────────────────────────────
60
+ structlog.configure(
61
+ processors=[
62
+ structlog.processors.TimeStamper(fmt="iso"),
63
+ structlog.stdlib.add_log_level,
64
+ structlog.processors.StackInfoRenderer(),
65
+ structlog.dev.ConsoleRenderer(),
66
+ ]
67
+ )
68
+ log = structlog.get_logger()
69
+
70
+ limiter = Limiter(key_func=get_remote_address)
71
+
72
+ # ─── Global Managers ──────────────────────────────────────────────────────────
73
+ ws_manager = WebSocketManager()
74
+ task_engine = TaskEngine(ws_manager)
75
+ ai_router = AIRouterV8(ws_manager)
76
+ connector_manager = ConnectorManager()
77
+
78
+
79
+ def build_kernel() -> AgentKernel:
80
+ """Build and configure the distributed 22-space Agent Kernel."""
81
+ kernel = AgentKernel(ws_manager=ws_manager, ai_router=ai_router)
82
+ for space_name, space_instance in build_all_spaces(ws_manager=ws_manager, ai_router=ai_router).items():
83
+ kernel.register_space(space_name, space_instance)
84
+ log.info("🧠 GOD AGENT OS distributed kernel initialized", spaces=len(SPACE_CATALOG))
85
+ return kernel
86
+
87
+
88
+ def build_legacy_orchestrator() -> GodAgentOrchestratorV7:
89
+ """Build legacy v7 orchestrator for backward compatibility."""
90
+ orchestrator = GodAgentOrchestratorV7(ws_manager=ws_manager, ai_router=ai_router)
91
+ orchestrator.register_agent("chat", ChatAgent(ws_manager, ai_router))
92
+ orchestrator.register_agent("planner", PlannerAgent(ws_manager, ai_router))
93
+ orchestrator.register_agent("coding", CodingAgent(ws_manager, ai_router))
94
+ orchestrator.register_agent("debug", DebugAgent(ws_manager, ai_router))
95
+ orchestrator.register_agent("memory", MemoryAgent(ws_manager, ai_router))
96
+ orchestrator.register_agent("connector", ConnectorAgent(ws_manager, ai_router))
97
+ orchestrator.register_agent("deploy", DeployAgent(ws_manager, ai_router))
98
+ orchestrator.register_agent("workflow", WorkflowAgent(ws_manager, ai_router))
99
+ orchestrator.register_agent("sandbox", SandboxAgent(ws_manager, ai_router))
100
+ orchestrator.register_agent("ui", UIAgent(ws_manager, ai_router))
101
+ orchestrator.register_agent("reasoning", ReasoningAgent(ws_manager, ai_router))
102
+ orchestrator.register_agent("browser", BrowserAgent(ws_manager, ai_router))
103
+ orchestrator.register_agent("file", FileAgent(ws_manager, ai_router))
104
+ orchestrator.register_agent("git", GitAgent(ws_manager, ai_router))
105
+ orchestrator.register_agent("test", TestAgent(ws_manager, ai_router))
106
+ orchestrator.register_agent("vision", VisionAgent(ws_manager, ai_router))
107
+ log.info("πŸ€– Legacy v7 Orchestrator initialized", agents=16)
108
+ return orchestrator
109
+
110
+
111
+ # Initialize both kernel and legacy orchestrator
112
+ kernel = build_kernel()
113
+ orchestrator = build_legacy_orchestrator()
114
+
115
+
116
+ @asynccontextmanager
117
+ async def lifespan(app: FastAPI):
118
+ log.info("πŸš€ Starting GOD AGENT OS v10 β€” Distributed 22-Space Architecture...")
119
+ await init_db()
120
+ await task_engine.start()
121
+ asyncio.create_task(ws_manager.heartbeat_loop())
122
+ stats = ai_router.get_stats()
123
+ active = [name for name, s in stats.items() if s["available"]]
124
+ log.info("βœ… GOD AGENT v10 β€” 22 Spaces + 16 Legacy Agents online")
125
+ log.info(f"πŸ”‘ Active AI providers: {active}")
126
+ log.info("🌐 Routing: SambaNova β†’ Gemini β†’ OpenAI β†’ Groq β†’ Cerebras")
127
+ log.info("πŸ“¦ Spaces: distributed 22-space runtime online")
128
+ yield
129
+ log.info("πŸ›‘ Shutting down GOD AGENT OS v9...")
130
+ await task_engine.stop()
131
+
132
+
133
+ app = FastAPI(
134
+ title="πŸ€– GOD AGENT OS v10",
135
+ description="Distributed 22-Space Autonomous Agent OS | Powered by Pyae Sone",
136
+ version="10.0.0",
137
+ lifespan=lifespan,
138
+ docs_url="/api/docs",
139
+ redoc_url="/api/redoc",
140
+ )
141
+
142
+ app.state.limiter = limiter
143
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
144
+ app.state.ws_manager = ws_manager
145
+ app.state.task_engine = task_engine
146
+ app.state.ai_router = ai_router
147
+ app.state.kernel = kernel
148
+ app.state.orchestrator = orchestrator
149
+ app.state.connector_manager = connector_manager
150
+
151
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
152
+ app.add_middleware(GZipMiddleware, minimum_size=1000)
153
+
154
+
155
+ @app.middleware("http")
156
+ async def log_requests(request: Request, call_next):
157
+ start = time.time()
158
+ response = await call_next(request)
159
+ ms = round((time.time() - start) * 1000, 2)
160
+ log.info("HTTP", method=request.method, path=request.url.path, status=response.status_code, ms=ms)
161
+ return response
162
+
163
+
164
+ # ─── REST Routers ─────────────────────────────────────────────────────────────
165
+ app.include_router(health.router, prefix="/api/v1", tags=["health"])
166
+ app.include_router(tasks.router, prefix="/api/v1/tasks", tags=["tasks"])
167
+ app.include_router(chat.router, prefix="/api/v1", tags=["chat"])
168
+ app.include_router(memory.router, prefix="/api/v1/memory", tags=["memory"])
169
+ app.include_router(github.router, prefix="/api/v1/github", tags=["github"])
170
+ app.include_router(connectors.router, prefix="/api/v1/connectors", tags=["connectors"])
171
+ app.include_router(agents_router.router, prefix="/api/v1/agents", tags=["agents"])
172
+
173
+
174
+ # ─── WebSocket: Chat (v9 Kernel-powered) ──────────────────────────────────────
175
+ @app.websocket("/ws/chat/{session_id}")
176
+ async def ws_chat(websocket: WebSocket, session_id: str):
177
+ await ws_manager.connect(websocket, room=f"chat:{session_id}")
178
+ try:
179
+ while True:
180
+ data = await websocket.receive_text()
181
+ msg = json.loads(data)
182
+
183
+ if msg.get("type") == "ping":
184
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
185
+
186
+ elif msg.get("type") == "chat_message":
187
+ # Route through v9 Agent Kernel
188
+ asyncio.create_task(kernel.orchestrate(
189
+ user_message=msg.get("content", ""),
190
+ session_id=session_id,
191
+ context=msg.get("context", {}),
192
+ ))
193
+
194
+ elif msg.get("type") == "task_message":
195
+ from core.models import TaskCreateRequest
196
+ req = TaskCreateRequest(goal=msg.get("content", ""), session_id=session_id)
197
+ asyncio.create_task(task_engine.submit(req))
198
+
199
+ except WebSocketDisconnect:
200
+ ws_manager.disconnect(websocket, room=f"chat:{session_id}")
201
+
202
+
203
+ @app.websocket("/ws/tasks/{task_id}")
204
+ async def ws_task(websocket: WebSocket, task_id: str):
205
+ await ws_manager.connect(websocket, room=f"task:{task_id}")
206
+ try:
207
+ while True:
208
+ data = await websocket.receive_text()
209
+ msg = json.loads(data)
210
+ if msg.get("type") == "ping":
211
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
212
+ except WebSocketDisconnect:
213
+ ws_manager.disconnect(websocket, room=f"task:{task_id}")
214
+
215
+
216
+ @app.websocket("/ws/logs")
217
+ async def ws_logs(websocket: WebSocket):
218
+ await ws_manager.connect(websocket, room="logs")
219
+ try:
220
+ while True:
221
+ data = await websocket.receive_text()
222
+ msg = json.loads(data)
223
+ if msg.get("type") == "ping":
224
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
225
+ except WebSocketDisconnect:
226
+ ws_manager.disconnect(websocket, room="logs")
227
+
228
+
229
+ @app.websocket("/ws/agent/status")
230
+ async def ws_agent_status(websocket: WebSocket):
231
+ await ws_manager.connect(websocket, room="agent_status")
232
+ try:
233
+ while True:
234
+ data = await websocket.receive_text()
235
+ msg = json.loads(data)
236
+ if msg.get("type") == "ping":
237
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
238
+ elif msg.get("type") == "get_status":
239
+ await websocket.send_json({
240
+ "type": "agent_status",
241
+ "data": kernel.get_status(),
242
+ "legacy": orchestrator.get_status(),
243
+ })
244
+ except WebSocketDisconnect:
245
+ ws_manager.disconnect(websocket, room="agent_status")
246
+
247
+
248
+ @app.websocket("/ws/sandbox/{session_id}")
249
+ async def ws_sandbox(websocket: WebSocket, session_id: str):
250
+ await ws_manager.connect(websocket, room=f"sandbox:{session_id}")
251
+ sandbox_space = kernel.get_space("sandbox")
252
+ try:
253
+ while True:
254
+ data = await websocket.receive_text()
255
+ msg = json.loads(data)
256
+ if msg.get("type") == "ping":
257
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
258
+ elif msg.get("type") == "execute" and sandbox_space:
259
+ cmd = msg.get("command", "")
260
+ result = await sandbox_space._run_shell(cmd)
261
+ await websocket.send_json({
262
+ "type": "terminal_output",
263
+ "command": cmd,
264
+ "output": result,
265
+ "timestamp": time.time()
266
+ })
267
+ except WebSocketDisconnect:
268
+ ws_manager.disconnect(websocket, room=f"sandbox:{session_id}")
269
+
270
+
271
+ # ─── v9 Space-Role API Endpoints ──────────────────────────────────────────────
272
+ @app.get("/api/v1/kernel/status")
273
+ async def kernel_status():
274
+ """Get Agent Kernel status."""
275
+ return {
276
+ "kernel": kernel.get_status(),
277
+ "ai_providers": ai_router.get_stats(),
278
+ }
279
+
280
+
281
+ @app.get("/api/v1/spaces")
282
+ async def list_spaces():
283
+ """List all available Spaces."""
284
+ spaces_info = {}
285
+ for name, space in kernel._spaces.items():
286
+ spaces_info[name] = space.get_info()
287
+ return {"spaces": spaces_info, "total": len(spaces_info)}
288
+
289
+
290
+ @app.post("/api/v1/spaces/{space_name}/execute")
291
+ async def execute_in_space(space_name: str, request: Request):
292
+ """Execute a task in a specific Space."""
293
+ body = await request.json()
294
+ task = body.get("task", "")
295
+ role = body.get("role", "cognition")
296
+ session_id = body.get("session_id", "api")
297
+
298
+ space = kernel.get_space(space_name)
299
+ if not space:
300
+ raise HTTPException(status_code=404, detail=f"Space '{space_name}' not found")
301
+
302
+ result = await space.execute(task=task, role=role, session_id=session_id, context=body.get("context", {}))
303
+ return {"space": space_name, "role": role, "result": result}
304
+
305
+
306
+ @app.post("/api/v1/kernel/orchestrate")
307
+ async def kernel_orchestrate(request: Request):
308
+ """Main orchestration endpoint."""
309
+ body = await request.json()
310
+ result = await kernel.orchestrate(
311
+ user_message=body.get("message", ""),
312
+ session_id=body.get("session_id", "api"),
313
+ context=body.get("context", {}),
314
+ )
315
+ return {"result": result}
316
+
317
+
318
+ # ─── Legacy v8 Endpoints (backward compatibility) ─────────────────────────────
319
+ @app.get("/api/v1/ai/stats")
320
+ async def get_ai_stats():
321
+ return {"stats": ai_router.get_stats()}
322
+
323
+
324
+ @app.get("/api/v1/ai/pool-status")
325
+ async def get_pool_status():
326
+ return {"pools": ai_router.get_pool_status()}
327
+
328
+
329
+ @app.post("/api/v1/browser/research")
330
+ async def browser_research(request: Request):
331
+ body = await request.json()
332
+ browser_space = kernel.get_space("browser")
333
+ if not browser_space:
334
+ raise HTTPException(status_code=503, detail="Browser Space not available")
335
+ result = await browser_space.execute(
336
+ task=body.get("query", ""),
337
+ role="automation",
338
+ session_id=body.get("session_id", "api"),
339
+ )
340
+ return {"result": result}
341
+
342
+
343
+ @app.get("/api/v1/files/workspace")
344
+ async def list_workspace():
345
+ file_agent = orchestrator.get_agent("file")
346
+ if not file_agent:
347
+ return {"workspace": "/tmp/god_workspace", "files": [], "total": 0}
348
+ return file_agent.list_workspace()
349
+
350
+
351
+ @app.post("/api/v1/git/pr")
352
+ async def create_pr(request: Request):
353
+ body = await request.json()
354
+ git_agent = orchestrator.get_agent("git")
355
+ if not git_agent:
356
+ raise HTTPException(status_code=503, detail="GitAgent not available")
357
+ result = await git_agent.create_github_pr(
358
+ repo_owner=body.get("owner", ""),
359
+ repo_name=body.get("repo", ""),
360
+ title=body.get("title", ""),
361
+ body=body.get("body", ""),
362
+ head_branch=body.get("head_branch", "main"),
363
+ base_branch=body.get("base_branch", "main"),
364
+ )
365
+ return result
366
+
367
+
368
+ @app.post("/api/v1/vision/generate")
369
+ async def generate_ui(request: Request):
370
+ body = await request.json()
371
+ vision_space = kernel.get_space("vision")
372
+ if not vision_space:
373
+ raise HTTPException(status_code=503, detail="Vision Space not available")
374
+ result = await vision_space.execute(
375
+ task=body.get("prompt", ""),
376
+ role="visual_intelligence",
377
+ session_id=body.get("session_id", "api"),
378
+ )
379
+ return {"result": result}
380
+
381
+
382
+ # ─── Root ─────────────────────────────────────────────────────────────────────
383
+ @app.get("/")
384
+ async def root():
385
+ cs = connector_manager.get_summary()
386
+ kernel_status_data = kernel.get_status()
387
+ stats = ai_router.get_stats()
388
+ active_providers = [name for name, s in stats.items() if s["available"]]
389
+ return {
390
+ "name": "πŸ€– GOD AGENT OS v10",
391
+ "version": "10.0.0",
392
+ "status": "operational",
393
+ "mode": "general_autonomous_agent_os",
394
+ "description": "Distributed 22-Space Architecture | Powered by Pyae Sone",
395
+ "architecture": "Distributed Worker Space Paradigm",
396
+ "spaces": kernel_status_data["spaces"],
397
+ "total_spaces": kernel_status_data["total_spaces"],
398
+ "ai_providers": active_providers,
399
+ "connectors": {"connected": cs["connected"], "total": cs["total"]},
400
+ "docs": "/api/docs",
401
+ "v9_features": [
402
+ "πŸ“¦ 22 distributed worker spaces across cognition, execution, verification, deployment, memory, coordination, monitoring, session, and infrastructure layers",
403
+ "🎭 5 Cognitive Roles: Cognition | Automation | Execution | Repair | Visual Intelligence",
404
+ "🧠 God Core Space orchestrates the worker fleet",
405
+ "πŸ”‘ KeyPool multi-key management (Gemini + SambaNova + GitHub)",
406
+ "πŸ”„ Automatic worker-space routing based on intent",
407
+ "πŸ’Ύ Context Manager for session-scoped runtime state",
408
+ "⚑ Backward compatible with v8/v9 agent fleet",
409
+ "🌐 Real-time streaming via WebSocket",
410
+ ],
411
+ }
412
+
413
+
414
+ if __name__ == "__main__":
415
+ import uvicorn
416
+ port = int(os.environ.get("PORT", 7860))
417
+ uvicorn.run("main_v9:app", host="0.0.0.0", port=port, reload=False, workers=1)
agents/__init__.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # God Agent Multi-Agent System
2
+ from .orchestrator import GodAgentOrchestrator
3
+ from .chat_agent import ChatAgent
4
+ from .planner_agent import PlannerAgent
5
+ from .coding_agent import CodingAgent
6
+ from .debug_agent import DebugAgent
7
+ from .memory_agent import MemoryAgent
8
+ from .connector_agent import ConnectorAgent
9
+ from .deploy_agent import DeployAgent
10
+ from .workflow_agent import WorkflowAgent
11
+ from .sandbox_agent import SandboxAgent
12
+
13
+ __all__ = [
14
+ "GodAgentOrchestrator",
15
+ "ChatAgent",
16
+ "PlannerAgent",
17
+ "CodingAgent",
18
+ "DebugAgent",
19
+ "MemoryAgent",
20
+ "ConnectorAgent",
21
+ "DeployAgent",
22
+ "WorkflowAgent",
23
+ "SandboxAgent",
24
+ ]
agents/base_agent.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Base Agent β€” Abstract base for all God Mode agents
3
+ """
4
+ from abc import ABC, abstractmethod
5
+ from typing import Any, Dict, List, Optional
6
+ import structlog
7
+
8
+ log = structlog.get_logger()
9
+
10
+
11
+ class BaseAgent(ABC):
12
+ """Abstract base class for all agents in the God Mode ecosystem."""
13
+
14
+ def __init__(self, name: str, ws_manager=None, ai_router=None):
15
+ self.name = name
16
+ self.ws = ws_manager
17
+ self.ai_router = ai_router
18
+ self.log = structlog.get_logger().bind(agent=name)
19
+
20
+ @abstractmethod
21
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
22
+ """Execute the agent's primary task."""
23
+ pass
24
+
25
+ async def emit(self, room: str, event: str, data: Dict, session_id: str = ""):
26
+ """Emit WebSocket event if manager available."""
27
+ if self.ws:
28
+ await self.ws.emit(room, event, data, session_id=session_id)
29
+
30
+ async def emit_chat(self, session_id: str, event: str, data: Dict):
31
+ """Emit chat WebSocket event."""
32
+ if self.ws:
33
+ await self.ws.emit_chat(session_id, event, data)
34
+
35
+ async def llm(
36
+ self,
37
+ messages: List[Dict],
38
+ task_id: str = "",
39
+ session_id: str = "",
40
+ temperature: float = 0.7,
41
+ max_tokens: int = 4096,
42
+ model: str = "",
43
+ ) -> str:
44
+ """Route LLM call through AI router."""
45
+ if self.ai_router:
46
+ return await self.ai_router.complete(
47
+ messages=messages,
48
+ task_id=task_id,
49
+ session_id=session_id,
50
+ temperature=temperature,
51
+ max_tokens=max_tokens,
52
+ preferred_model=model,
53
+ )
54
+ return f"[{self.name}] AI router not configured."
agents/browser_agent.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BrowserAgent v7 β€” Autonomous web browsing, scraping, research (Manus-style)
3
+ Real browser control via Playwright/httpx for research, testing, web automation
4
+ """
5
+ import asyncio
6
+ import json
7
+ import os
8
+ import re
9
+ from typing import Dict, List, Optional
10
+ import structlog
11
+ from .base_agent import BaseAgent
12
+
13
+ log = structlog.get_logger()
14
+
15
+ BROWSER_SYSTEM = """You are an elite autonomous web research and browser automation agent.
16
+ You can:
17
+ - Search the web and extract structured information
18
+ - Navigate websites and fill forms
19
+ - Take screenshots and analyze visual content
20
+ - Scrape and parse complex web pages
21
+ - Run web automation tasks
22
+
23
+ Always provide structured, actionable results with source URLs.
24
+ """
25
+
26
+ class BrowserAgent(BaseAgent):
27
+ def __init__(self, ws_manager=None, ai_router=None):
28
+ super().__init__("BrowserAgent", ws_manager, ai_router)
29
+ self._session_cache: Dict[str, str] = {}
30
+
31
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
32
+ session_id = kwargs.get("session_id", "")
33
+ task_id = kwargs.get("task_id", "")
34
+
35
+ await self.emit(task_id, "agent_start", {
36
+ "agent": "BrowserAgent",
37
+ "task": task[:80],
38
+ }, session_id)
39
+
40
+ await self.emit(task_id, "tool_called", {
41
+ "agent": "BrowserAgent",
42
+ "tool": "web_research",
43
+ "step": f"Researching: {task[:60]}",
44
+ }, session_id)
45
+
46
+ # Extract search query or URL from task
47
+ urls = re.findall(r'https?://[^\s]+', task)
48
+
49
+ if urls:
50
+ result = await self._fetch_and_analyze(urls[0], task, task_id, session_id)
51
+ else:
52
+ result = await self._web_research(task, task_id, session_id)
53
+
54
+ await self.emit(task_id, "browser_result", {
55
+ "agent": "BrowserAgent",
56
+ "result_length": len(result),
57
+ }, session_id)
58
+
59
+ return result
60
+
61
+ async def _web_research(self, query: str, task_id: str, session_id: str) -> str:
62
+ """Perform web research using AI knowledge + httpx."""
63
+ import httpx
64
+
65
+ # Use DuckDuckGo search API (no key needed)
66
+ search_url = f"https://api.duckduckgo.com/?q={query.replace(' ', '+')}&format=json&no_html=1"
67
+
68
+ try:
69
+ async with httpx.AsyncClient(timeout=15, follow_redirects=True) as client:
70
+ resp = await client.get(search_url, headers={"User-Agent": "GodAgent/7.0"})
71
+ data = resp.json()
72
+
73
+ results = []
74
+ if data.get("AbstractText"):
75
+ results.append(f"**Summary:** {data['AbstractText']}")
76
+ if data.get("AbstractURL"):
77
+ results.append(f"**Source:** {data['AbstractURL']}")
78
+
79
+ related = data.get("RelatedTopics", [])[:5]
80
+ if related:
81
+ results.append("\n**Related:**")
82
+ for r in related:
83
+ if isinstance(r, dict) and r.get("Text"):
84
+ results.append(f"- {r['Text'][:200]}")
85
+
86
+ if results:
87
+ search_context = "\n".join(results)
88
+ else:
89
+ search_context = f"Web search for: {query}"
90
+
91
+ except Exception as e:
92
+ search_context = f"Search context for: {query}"
93
+
94
+ # Use AI to synthesize research
95
+ messages = [
96
+ {"role": "system", "content": BROWSER_SYSTEM},
97
+ {"role": "user", "content": (
98
+ f"Research task: {query}\n\n"
99
+ f"Search context:\n{search_context}\n\n"
100
+ f"Provide a comprehensive, structured research report with key findings, "
101
+ f"actionable insights, and relevant sources. Format with headers and bullet points."
102
+ )},
103
+ ]
104
+ return await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=4096)
105
+
106
+ async def _fetch_and_analyze(self, url: str, task: str, task_id: str, session_id: str) -> str:
107
+ """Fetch a URL and analyze its content."""
108
+ import httpx
109
+
110
+ try:
111
+ async with httpx.AsyncClient(timeout=20, follow_redirects=True) as client:
112
+ resp = await client.get(url, headers={
113
+ "User-Agent": "Mozilla/5.0 GodAgent/7.0",
114
+ "Accept": "text/html,application/json,*/*",
115
+ })
116
+ content_type = resp.headers.get("content-type", "")
117
+
118
+ if "json" in content_type:
119
+ page_content = json.dumps(resp.json(), indent=2)[:3000]
120
+ else:
121
+ # Strip HTML tags
122
+ html = resp.text
123
+ text = re.sub(r'<[^>]+>', ' ', html)
124
+ text = re.sub(r'\s+', ' ', text).strip()
125
+ page_content = text[:3000]
126
+
127
+ except Exception as e:
128
+ page_content = f"Could not fetch {url}: {str(e)}"
129
+
130
+ messages = [
131
+ {"role": "system", "content": BROWSER_SYSTEM},
132
+ {"role": "user", "content": (
133
+ f"Analyze this web page content for the task: {task}\n\n"
134
+ f"URL: {url}\n\n"
135
+ f"Page Content:\n{page_content}\n\n"
136
+ f"Provide a structured analysis with key information extracted."
137
+ )},
138
+ ]
139
+ return await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=4096)
140
+
141
+ async def screenshot_analyze(self, url: str, task_id: str = "", session_id: str = "") -> str:
142
+ """Describe what a webpage looks like (AI-powered visual analysis)."""
143
+ messages = [
144
+ {"role": "system", "content": BROWSER_SYSTEM},
145
+ {"role": "user", "content": f"Describe the visual layout and UI elements you'd expect at: {url}. Provide a detailed visual analysis."},
146
+ ]
147
+ return await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.5)
agents/chat_agent.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ChatAgent β€” Conversational interface with streaming, Burmese support
3
+ """
4
+ from typing import Dict
5
+ import structlog
6
+ from .base_agent import BaseAgent
7
+
8
+ log = structlog.get_logger()
9
+
10
+ CHAT_SYSTEM = """You are God Agent β€” an elite autonomous AI operating system.
11
+ You support both English and Burmese (α€™α€Όα€”α€Ία€™α€¬α€˜α€¬α€žα€¬) languages.
12
+ Be helpful, precise, and autonomous.
13
+ When users write in Burmese, respond in Burmese.
14
+ When discussing code, provide production-quality examples.
15
+ """
16
+
17
+
18
+ class ChatAgent(BaseAgent):
19
+ def __init__(self, ws_manager=None, ai_router=None):
20
+ super().__init__("ChatAgent", ws_manager, ai_router)
21
+
22
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
23
+ session_id = kwargs.get("session_id", "")
24
+ task_id = kwargs.get("task_id", "")
25
+
26
+ history = context.get("history", [])
27
+ messages = [{"role": "system", "content": CHAT_SYSTEM}]
28
+
29
+ for h in history[-10:]:
30
+ messages.append({"role": h.get("role", "user"), "content": h.get("content", "")})
31
+
32
+ messages.append({"role": "user", "content": task})
33
+
34
+ await self.emit_chat(session_id, "stream_start", {"agent": "ChatAgent", "status": "generating"})
35
+
36
+ response = await self.llm(messages, task_id=task_id, session_id=session_id)
37
+
38
+ await self.emit_chat(session_id, "stream_end", {
39
+ "agent": "ChatAgent",
40
+ "full_response": response,
41
+ "status": "complete",
42
+ })
43
+
44
+ return response
agents/coding_agent.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CodingAgent β€” Autonomous code generation, editing, refactoring (Devin/Genspark style)
3
+ """
4
+ import json
5
+ import os
6
+ import re
7
+ from typing import Dict, List
8
+ import structlog
9
+ from .base_agent import BaseAgent
10
+
11
+ log = structlog.get_logger()
12
+
13
+ CODING_SYSTEM = """You are an elite autonomous software engineer β€” like Devin combined with Genspark.
14
+ You write production-quality code that is:
15
+ - Clean, readable, well-structured
16
+ - Properly typed (TypeScript/Python type hints)
17
+ - Error-handled and resilient
18
+ - Documented with clear comments
19
+ - Following best practices for the language/framework
20
+
21
+ When generating code:
22
+ 1. Think about the full architecture first
23
+ 2. Write complete, runnable code (not snippets)
24
+ 3. Include proper imports
25
+ 4. Add error handling
26
+ 5. Include brief usage examples in comments
27
+
28
+ Support: Python, TypeScript, JavaScript, Go, Rust, SQL, Shell, YAML, JSON
29
+ """
30
+
31
+
32
+ class CodingAgent(BaseAgent):
33
+ def __init__(self, ws_manager=None, ai_router=None):
34
+ super().__init__("CodingAgent", ws_manager, ai_router)
35
+
36
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
37
+ session_id = kwargs.get("session_id", "")
38
+ task_id = kwargs.get("task_id", "")
39
+
40
+ await self.emit(task_id, "agent_start", {
41
+ "agent": "CodingAgent",
42
+ "task": task[:80],
43
+ }, session_id)
44
+
45
+ # Build context-aware messages
46
+ prev_results = context.get("previous_results", [])
47
+ project_ctx = context.get("project_context", "")
48
+ plan = context.get("plan", "")
49
+
50
+ system_content = CODING_SYSTEM
51
+ if project_ctx:
52
+ system_content += f"\n\nProject Context:\n{project_ctx[:1000]}"
53
+
54
+ user_content = f"Task: {task}"
55
+ if plan:
56
+ user_content += f"\n\nExecution Plan:\n{plan[:500]}"
57
+ if prev_results:
58
+ user_content += f"\n\nPrevious results:\n" + "\n".join(str(r)[:200] for r in prev_results[-3:])
59
+
60
+ messages = [
61
+ {"role": "system", "content": system_content},
62
+ {"role": "user", "content": user_content},
63
+ ]
64
+
65
+ await self.emit(task_id, "tool_called", {
66
+ "agent": "CodingAgent",
67
+ "tool": "code_generation",
68
+ "step": task[:60],
69
+ }, session_id)
70
+
71
+ result = await self.llm(
72
+ messages,
73
+ task_id=task_id,
74
+ session_id=session_id,
75
+ temperature=0.2,
76
+ max_tokens=8192,
77
+ )
78
+
79
+ # Extract code blocks for display
80
+ code_blocks = self._extract_code_blocks(result)
81
+ await self.emit(task_id, "code_generated", {
82
+ "agent": "CodingAgent",
83
+ "code_blocks": len(code_blocks),
84
+ "total_lines": sum(len(b.split("\n")) for b in code_blocks),
85
+ "languages": list(set(self._detect_language(b) for b in code_blocks)),
86
+ }, session_id)
87
+
88
+ return result
89
+
90
+ async def generate_file(
91
+ self,
92
+ filename: str,
93
+ description: str,
94
+ task_id: str = "",
95
+ session_id: str = "",
96
+ context: Dict = {},
97
+ ) -> str:
98
+ """Generate a complete file with proper structure."""
99
+ messages = [
100
+ {"role": "system", "content": CODING_SYSTEM},
101
+ {"role": "user", "content": (
102
+ f"Generate a complete, production-ready file.\n"
103
+ f"Filename: {filename}\n"
104
+ f"Description: {description}\n"
105
+ f"Context: {json.dumps(context)[:500]}\n\n"
106
+ f"Return ONLY the file content, no explanation."
107
+ )},
108
+ ]
109
+ content = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.1, max_tokens=8192)
110
+
111
+ # Strip markdown code fences if present
112
+ content = self._strip_code_fences(content)
113
+
114
+ # Write to workspace
115
+ workspace = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
116
+ filepath = os.path.join(workspace, filename)
117
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
118
+ with open(filepath, "w") as f:
119
+ f.write(content)
120
+
121
+ await self.emit(task_id, "file_written", {
122
+ "filename": filename,
123
+ "size": len(content),
124
+ "lines": len(content.split("\n")),
125
+ }, session_id)
126
+
127
+ return content
128
+
129
+ async def refactor(
130
+ self,
131
+ code: str,
132
+ instructions: str,
133
+ task_id: str = "",
134
+ session_id: str = "",
135
+ ) -> str:
136
+ """Refactor existing code based on instructions."""
137
+ messages = [
138
+ {"role": "system", "content": CODING_SYSTEM},
139
+ {"role": "user", "content": (
140
+ f"Refactor this code based on these instructions:\n"
141
+ f"Instructions: {instructions}\n\n"
142
+ f"Original code:\n```\n{code}\n```\n\n"
143
+ f"Return ONLY the refactored code."
144
+ )},
145
+ ]
146
+ return await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.1, max_tokens=8192)
147
+
148
+ async def scan_repository(self, repo_path: str) -> Dict:
149
+ """Scan repository and build project intelligence graph."""
150
+ import subprocess
151
+ try:
152
+ result = subprocess.run(
153
+ ["find", repo_path, "-type", "f", "-name", "*.py", "-o",
154
+ "-name", "*.ts", "-o", "-name", "*.js", "-o", "-name", "*.go"],
155
+ capture_output=True, text=True, timeout=10
156
+ )
157
+ files = result.stdout.strip().split("\n")[:50]
158
+
159
+ # Read key files
160
+ key_files = {}
161
+ for f in ["package.json", "requirements.txt", "tsconfig.json", "pyproject.toml", "go.mod"]:
162
+ path = os.path.join(repo_path, f)
163
+ if os.path.exists(path):
164
+ with open(path) as fp:
165
+ key_files[f] = fp.read()[:1000]
166
+
167
+ return {
168
+ "files": files,
169
+ "key_configs": key_files,
170
+ "total_files": len(files),
171
+ }
172
+ except Exception as e:
173
+ return {"error": str(e), "files": [], "key_configs": {}}
174
+
175
+ def _extract_code_blocks(self, text: str) -> List[str]:
176
+ pattern = r"```[\w]*\n(.*?)```"
177
+ return re.findall(pattern, text, re.DOTALL)
178
+
179
+ def _strip_code_fences(self, text: str) -> str:
180
+ text = re.sub(r"^```[\w]*\n", "", text.strip())
181
+ text = re.sub(r"\n```$", "", text)
182
+ return text
183
+
184
+ def _detect_language(self, code: str) -> str:
185
+ if "def " in code and "import " in code:
186
+ return "python"
187
+ if "function " in code or "const " in code or "interface " in code:
188
+ return "typescript"
189
+ if "package main" in code:
190
+ return "go"
191
+ return "unknown"
agents/connector_agent.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ConnectorAgent β€” GitHub, HuggingFace, Vercel, n8n, Telegram, Discord integrations
3
+ """
4
+ import json
5
+ import os
6
+ from typing import Dict, List, Optional
7
+ import httpx
8
+ import structlog
9
+ from .base_agent import BaseAgent
10
+
11
+ log = structlog.get_logger()
12
+
13
+
14
+ class ConnectorAgent(BaseAgent):
15
+ def __init__(self, ws_manager=None, ai_router=None):
16
+ super().__init__("ConnectorAgent", ws_manager, ai_router)
17
+
18
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
19
+ session_id = kwargs.get("session_id", "")
20
+ task_id = kwargs.get("task_id", "")
21
+
22
+ task_lower = task.lower()
23
+
24
+ if "github" in task_lower:
25
+ return await self.github_operation(task, task_id=task_id, session_id=session_id)
26
+ elif "huggingface" in task_lower or "hf" in task_lower:
27
+ return await self.hf_operation(task, task_id=task_id, session_id=session_id)
28
+ elif "vercel" in task_lower:
29
+ return await self.vercel_operation(task, task_id=task_id, session_id=session_id)
30
+ else:
31
+ return await self._generic_connector(task, task_id=task_id, session_id=session_id)
32
+
33
+ # ─── GitHub ───────────────────────────────────────────────────────────────
34
+
35
+ async def github_operation(self, task: str, task_id: str = "", session_id: str = "") -> str:
36
+ token = os.environ.get("GITHUB_TOKEN", "")
37
+ if not token:
38
+ return "⚠️ GITHUB_TOKEN not set. Please add it to environment variables."
39
+
40
+ headers = {
41
+ "Authorization": f"token {token}",
42
+ "Accept": "application/vnd.github.v3+json",
43
+ }
44
+
45
+ task_lower = task.lower()
46
+
47
+ try:
48
+ async with httpx.AsyncClient(timeout=30) as client:
49
+ if "create repo" in task_lower or "new repo" in task_lower:
50
+ return await self._github_create_repo(client, headers, task, task_id, session_id)
51
+ elif "list repo" in task_lower:
52
+ return await self._github_list_repos(client, headers, task_id, session_id)
53
+ elif "commit" in task_lower:
54
+ return f"βœ… GitHub commit operation noted. Use SandboxAgent for actual commits."
55
+ elif "issue" in task_lower:
56
+ return await self._github_create_issue(client, headers, task, task_id, session_id)
57
+ else:
58
+ return await self._github_user_info(client, headers, task_id, session_id)
59
+ except Exception as e:
60
+ return f"❌ GitHub error: {str(e)}"
61
+
62
+ async def _github_user_info(self, client, headers, task_id, session_id) -> str:
63
+ resp = await client.get("https://api.github.com/user", headers=headers)
64
+ if resp.status_code == 200:
65
+ data = resp.json()
66
+ await self.emit(task_id, "connector_result", {
67
+ "connector": "github",
68
+ "action": "user_info",
69
+ "user": data.get("login"),
70
+ }, session_id)
71
+ return f"βœ… GitHub connected as **{data.get('login')}** ({data.get('public_repos')} repos)"
72
+ return f"❌ GitHub auth failed: {resp.status_code}"
73
+
74
+ async def _github_create_repo(self, client, headers, task, task_id, session_id) -> str:
75
+ # Extract repo name from task using simple heuristic
76
+ words = task.split()
77
+ name_candidates = [w for w in words if w.replace("-", "").replace("_", "").isalnum() and len(w) > 3]
78
+ repo_name = name_candidates[-1] if name_candidates else "god-agent-project"
79
+
80
+ payload = {
81
+ "name": repo_name,
82
+ "description": "Created by God Agent Platform",
83
+ "private": False,
84
+ "auto_init": True,
85
+ }
86
+ resp = await client.post("https://api.github.com/user/repos", headers=headers, json=payload)
87
+ if resp.status_code == 201:
88
+ data = resp.json()
89
+ await self.emit(task_id, "connector_result", {
90
+ "connector": "github",
91
+ "action": "repo_created",
92
+ "repo": data.get("full_name"),
93
+ "url": data.get("html_url"),
94
+ }, session_id)
95
+ return f"βœ… GitHub repo created: [{data.get('full_name')}]({data.get('html_url')})"
96
+ return f"❌ Failed to create repo: {resp.status_code} β€” {resp.text[:200]}"
97
+
98
+ async def _github_list_repos(self, client, headers, task_id, session_id) -> str:
99
+ resp = await client.get("https://api.github.com/user/repos?per_page=10&sort=updated", headers=headers)
100
+ if resp.status_code == 200:
101
+ repos = resp.json()
102
+ lines = [f"- [{r['full_name']}]({r['html_url']}) ⭐{r['stargazers_count']}" for r in repos[:10]]
103
+ return "πŸ“¦ **Your GitHub Repos:**\n\n" + "\n".join(lines)
104
+ return f"❌ Failed to list repos: {resp.status_code}"
105
+
106
+ async def _github_create_issue(self, client, headers, task, task_id, session_id) -> str:
107
+ return "βœ… GitHub issue creation requires repo context. Specify: 'create issue in owner/repo: title'"
108
+
109
+ # ─── HuggingFace ──────────────────────────────────────────────────────────
110
+
111
+ async def hf_operation(self, task: str, task_id: str = "", session_id: str = "") -> str:
112
+ token = os.environ.get("HF_TOKEN", "")
113
+ if not token:
114
+ return "⚠️ HF_TOKEN not set. Add HuggingFace token to environment."
115
+
116
+ headers = {"Authorization": f"Bearer {token}"}
117
+ try:
118
+ async with httpx.AsyncClient(timeout=30) as client:
119
+ resp = await client.get("https://huggingface.co/api/whoami", headers=headers)
120
+ if resp.status_code == 200:
121
+ data = resp.json()
122
+ await self.emit(task_id, "connector_result", {
123
+ "connector": "huggingface",
124
+ "user": data.get("name"),
125
+ }, session_id)
126
+ return f"βœ… HuggingFace connected as **{data.get('name')}**"
127
+ return f"❌ HF auth failed: {resp.status_code}"
128
+ except Exception as e:
129
+ return f"❌ HuggingFace error: {str(e)}"
130
+
131
+ # ─── Vercel ───────────────────────────────────────────────────────────────
132
+
133
+ async def vercel_operation(self, task: str, task_id: str = "", session_id: str = "") -> str:
134
+ token = os.environ.get("VERCEL_TOKEN", "")
135
+ if not token:
136
+ return "⚠️ VERCEL_TOKEN not set. Add Vercel token to environment."
137
+
138
+ headers = {"Authorization": f"Bearer {token}"}
139
+ try:
140
+ async with httpx.AsyncClient(timeout=30) as client:
141
+ resp = await client.get("https://api.vercel.com/v2/user", headers=headers)
142
+ if resp.status_code == 200:
143
+ data = resp.json()
144
+ user = data.get("user", {})
145
+ await self.emit(task_id, "connector_result", {
146
+ "connector": "vercel",
147
+ "user": user.get("username"),
148
+ }, session_id)
149
+ return f"βœ… Vercel connected as **{user.get('username')}** ({user.get('email')})"
150
+ return f"❌ Vercel auth failed: {resp.status_code}"
151
+ except Exception as e:
152
+ return f"❌ Vercel error: {str(e)}"
153
+
154
+ async def _generic_connector(self, task: str, task_id: str = "", session_id: str = "") -> str:
155
+ return (
156
+ "πŸ”Œ **Available Connectors:**\n\n"
157
+ "| Connector | Status | Env Var |\n"
158
+ "|-----------|--------|---------||\n"
159
+ f"| GitHub | {'βœ…' if os.environ.get('GITHUB_TOKEN') else '❌'} | GITHUB_TOKEN |\n"
160
+ f"| HuggingFace | {'βœ…' if os.environ.get('HF_TOKEN') else '❌'} | HF_TOKEN |\n"
161
+ f"| Vercel | {'βœ…' if os.environ.get('VERCEL_TOKEN') else '❌'} | VERCEL_TOKEN |\n"
162
+ f"| OpenAI | {'βœ…' if os.environ.get('OPENAI_API_KEY') else '❌'} | OPENAI_API_KEY |\n"
163
+ f"| Groq | {'βœ…' if os.environ.get('GROQ_API_KEY') else '❌'} | GROQ_API_KEY |\n"
164
+ f"| OpenRouter | {'βœ…' if os.environ.get('OPENROUTER_API_KEY') else '❌'} | OPENROUTER_API_KEY |\n"
165
+ )
166
+
167
+ def get_connector_status(self) -> Dict:
168
+ """Return status of all connectors."""
169
+ connectors = {
170
+ "github": bool(os.environ.get("GITHUB_TOKEN")),
171
+ "huggingface": bool(os.environ.get("HF_TOKEN")),
172
+ "vercel": bool(os.environ.get("VERCEL_TOKEN")),
173
+ "openai": bool(os.environ.get("OPENAI_API_KEY")),
174
+ "groq": bool(os.environ.get("GROQ_API_KEY")),
175
+ "cerebras": bool(os.environ.get("CEREBRAS_API_KEY")),
176
+ "openrouter": bool(os.environ.get("OPENROUTER_API_KEY")),
177
+ "anthropic": bool(os.environ.get("ANTHROPIC_API_KEY")),
178
+ "n8n": bool(os.environ.get("N8N_URL")),
179
+ "telegram": bool(os.environ.get("TELEGRAM_BOT_TOKEN")),
180
+ "discord": bool(os.environ.get("DISCORD_BOT_TOKEN")),
181
+ }
182
+ return {
183
+ "connectors": connectors,
184
+ "connected_count": sum(connectors.values()),
185
+ "total": len(connectors),
186
+ }
agents/debug_agent.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ DebugAgent β€” Autonomous error detection, self-healing, retry loops
3
+ """
4
+ import json
5
+ import re
6
+ from typing import Dict, List
7
+ import structlog
8
+ from .base_agent import BaseAgent
9
+
10
+ log = structlog.get_logger()
11
+
12
+ DEBUG_SYSTEM = """You are an expert debugging engineer with deep knowledge of:
13
+ - Python, TypeScript, JavaScript, Go, Rust errors
14
+ - Runtime exceptions, import errors, type errors
15
+ - API errors, network failures, auth issues
16
+ - Build failures, dependency conflicts
17
+ - Database errors, query optimization
18
+
19
+ When given an error:
20
+ 1. Identify the root cause precisely
21
+ 2. Provide the EXACT fix (code patch or config change)
22
+ 3. Explain WHY it failed
23
+ 4. Suggest prevention strategies
24
+
25
+ Always return actionable fixes, not just explanations.
26
+ """
27
+
28
+
29
+ class DebugAgent(BaseAgent):
30
+ def __init__(self, ws_manager=None, ai_router=None):
31
+ super().__init__("DebugAgent", ws_manager, ai_router)
32
+
33
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
34
+ session_id = kwargs.get("session_id", "")
35
+ task_id = kwargs.get("task_id", "")
36
+ attempt = context.get("attempt", 1)
37
+
38
+ await self.emit(task_id, "agent_start", {
39
+ "agent": "DebugAgent",
40
+ "attempt": attempt,
41
+ "error": task[:100],
42
+ }, session_id)
43
+
44
+ messages = [
45
+ {"role": "system", "content": DEBUG_SYSTEM},
46
+ {"role": "user", "content": (
47
+ "Debug and fix this issue (attempt " + str(attempt) + "):\n\n"
48
+ + task + "\n\n"
49
+ "Provide:\n"
50
+ "1. Root cause analysis\n"
51
+ "2. Exact fix (code/config)\n"
52
+ "3. Prevention strategy"
53
+ )},
54
+ ]
55
+
56
+ result = await self.llm(
57
+ messages,
58
+ task_id=task_id,
59
+ session_id=session_id,
60
+ temperature=0.1,
61
+ max_tokens=4096,
62
+ )
63
+
64
+ await self.emit(task_id, "debug_complete", {
65
+ "agent": "DebugAgent",
66
+ "has_fix": "```" in result or "fix" in result.lower(),
67
+ "attempt": attempt,
68
+ }, session_id)
69
+
70
+ return result
71
+
72
+ async def analyze_error(self, error_output: str, source_code: str = "", task_id: str = "", session_id: str = "") -> Dict:
73
+ """Deep error analysis with structured output."""
74
+ if source_code:
75
+ source_section = "Source Code:\n```\n" + source_code[:1000] + "\n```"
76
+ else:
77
+ source_section = ""
78
+
79
+ messages = [
80
+ {"role": "system", "content": DEBUG_SYSTEM},
81
+ {"role": "user", "content": (
82
+ "Analyze this error and provide structured diagnosis:\n\n"
83
+ "Error:\n" + error_output[:2000] + "\n\n"
84
+ + source_section + "\n\n"
85
+ "Respond with JSON:\n"
86
+ '{"error_type": "...", "root_cause": "...", "fix": "...", "prevention": "...", "severity": "low|medium|high|critical"}'
87
+ )},
88
+ ]
89
+ raw = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.1, max_tokens=1000)
90
+ try:
91
+ start = raw.find("{")
92
+ end = raw.rfind("}") + 1
93
+ return json.loads(raw[start:end])
94
+ except Exception:
95
+ return {"error_type": "unknown", "root_cause": error_output[:200], "fix": raw[:500], "severity": "medium"}
96
+
97
+ async def self_heal_loop(
98
+ self,
99
+ code: str,
100
+ error: str,
101
+ max_retries: int = 3,
102
+ task_id: str = "",
103
+ session_id: str = "",
104
+ ) -> str:
105
+ """Self-healing loop β€” generate fix, validate, retry."""
106
+ current_code = code
107
+ current_error = error
108
+
109
+ for attempt in range(1, max_retries + 1):
110
+ await self.emit(task_id, "self_heal_attempt", {
111
+ "attempt": attempt,
112
+ "max_retries": max_retries,
113
+ "error_snippet": current_error[:100],
114
+ }, session_id)
115
+
116
+ messages = [
117
+ {"role": "system", "content": DEBUG_SYSTEM},
118
+ {"role": "user", "content": (
119
+ "Fix attempt " + str(attempt) + "/" + str(max_retries) + ":\n\n"
120
+ "Error: " + current_error + "\n\n"
121
+ "Code:\n```\n" + current_code[:3000] + "\n```\n\n"
122
+ "Return ONLY the fixed code."
123
+ )},
124
+ ]
125
+
126
+ fixed = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.1, max_tokens=8192)
127
+ fixed = self._strip_fences(fixed)
128
+
129
+ # Validate syntax for Python
130
+ validation = await self._validate_python(fixed)
131
+ if validation["valid"]:
132
+ await self.emit(task_id, "self_heal_success", {
133
+ "attempt": attempt,
134
+ "code_lines": len(fixed.split("\n")),
135
+ }, session_id)
136
+ return fixed
137
+ else:
138
+ current_code = fixed
139
+ current_error = validation["error"]
140
+ log.warning("Self-heal attempt failed", attempt=attempt, error=current_error[:100])
141
+
142
+ await self.emit(task_id, "self_heal_failed", {"attempts": max_retries}, session_id)
143
+ return current_code # Return best attempt
144
+
145
+ async def _validate_python(self, code: str) -> Dict:
146
+ """Quick Python syntax validation."""
147
+ try:
148
+ compile(code, "<string>", "exec")
149
+ return {"valid": True, "error": ""}
150
+ except SyntaxError as e:
151
+ return {"valid": False, "error": "SyntaxError: " + str(e)}
152
+ except Exception as e:
153
+ return {"valid": False, "error": str(e)}
154
+
155
+ def _strip_fences(self, text: str) -> str:
156
+ text = re.sub(r"^```[\w]*\n", "", text.strip())
157
+ text = re.sub(r"\n```$", "", text)
158
+ return text
agents/deploy_agent.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ DeployAgent β€” Automated deployments to Vercel, HF Spaces, GitHub Pages
3
+ """
4
+ import json
5
+ import os
6
+ from typing import Dict
7
+ import httpx
8
+ import structlog
9
+ from .base_agent import BaseAgent
10
+
11
+ log = structlog.get_logger()
12
+
13
+ DEPLOY_SYSTEM = """You are a deployment engineer expert in:
14
+ - Vercel deployments (Next.js, React, API routes)
15
+ - HuggingFace Spaces (Docker, Gradio, Streamlit)
16
+ - GitHub Pages (static sites)
17
+ - Docker containerization
18
+ - Environment variable management
19
+
20
+ Generate precise deployment configs and commands.
21
+ """
22
+
23
+
24
+ class DeployAgent(BaseAgent):
25
+ def __init__(self, ws_manager=None, ai_router=None):
26
+ super().__init__("DeployAgent", ws_manager, ai_router)
27
+
28
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
29
+ session_id = kwargs.get("session_id", "")
30
+ task_id = kwargs.get("task_id", "")
31
+
32
+ await self.emit(task_id, "agent_start", {"agent": "DeployAgent", "task": task[:80]}, session_id)
33
+
34
+ task_lower = task.lower()
35
+ if "vercel" in task_lower:
36
+ return await self._deploy_vercel_guide(task, task_id, session_id)
37
+ elif "huggingface" in task_lower or "hf" in task_lower or "space" in task_lower:
38
+ return await self._deploy_hf_guide(task, task_id, session_id)
39
+ elif "docker" in task_lower:
40
+ return await self._generate_dockerfile(task, task_id, session_id)
41
+ else:
42
+ return await self._deploy_general(task, task_id, session_id)
43
+
44
+ async def _deploy_vercel_guide(self, task: str, task_id: str, session_id: str) -> str:
45
+ messages = [
46
+ {"role": "system", "content": DEPLOY_SYSTEM},
47
+ {"role": "user", "content": (
48
+ f"Generate complete Vercel deployment guide for: {task}\n\n"
49
+ f"Include:\n1. vercel.json config\n2. Environment variables needed\n"
50
+ f"3. Build commands\n4. Step-by-step deployment instructions"
51
+ )},
52
+ ]
53
+ result = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.2)
54
+ await self.emit(task_id, "deploy_plan_ready", {"platform": "vercel"}, session_id)
55
+ return result
56
+
57
+ async def _deploy_hf_guide(self, task: str, task_id: str, session_id: str) -> str:
58
+ messages = [
59
+ {"role": "system", "content": DEPLOY_SYSTEM},
60
+ {"role": "user", "content": (
61
+ f"Generate HuggingFace Space deployment for: {task}\n\n"
62
+ f"Include:\n1. Dockerfile\n2. README.md with YAML header\n"
63
+ f"3. Requirements/dependencies\n4. Space configuration"
64
+ )},
65
+ ]
66
+ result = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.2)
67
+ await self.emit(task_id, "deploy_plan_ready", {"platform": "huggingface"}, session_id)
68
+ return result
69
+
70
+ async def _generate_dockerfile(self, task: str, task_id: str, session_id: str) -> str:
71
+ messages = [
72
+ {"role": "system", "content": DEPLOY_SYSTEM},
73
+ {"role": "user", "content": f"Generate production Dockerfile for: {task}\nInclude multi-stage build, security best practices, health check."},
74
+ ]
75
+ return await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.1)
76
+
77
+ async def _deploy_general(self, task: str, task_id: str, session_id: str) -> str:
78
+ messages = [
79
+ {"role": "system", "content": DEPLOY_SYSTEM},
80
+ {"role": "user", "content": f"Create deployment plan for: {task}\n\nInclude platform recommendation, config files, and step-by-step guide."},
81
+ ]
82
+ return await self.llm(messages, task_id=task_id, session_id=session_id)
83
+
84
+ async def check_vercel_deployments(self) -> str:
85
+ """Check recent Vercel deployments."""
86
+ token = os.environ.get("VERCEL_TOKEN", "")
87
+ if not token:
88
+ return "⚠️ VERCEL_TOKEN not set."
89
+ try:
90
+ async with httpx.AsyncClient(timeout=15) as client:
91
+ resp = await client.get(
92
+ "https://api.vercel.com/v6/deployments?limit=5",
93
+ headers={"Authorization": f"Bearer {token}"}
94
+ )
95
+ if resp.status_code == 200:
96
+ deps = resp.json().get("deployments", [])
97
+ lines = [f"- {d.get('name')} β†’ {d.get('state')} ({d.get('url', '')})" for d in deps[:5]]
98
+ return "πŸš€ **Recent Vercel Deployments:**\n\n" + "\n".join(lines)
99
+ except Exception as e:
100
+ return f"❌ Vercel error: {e}"
101
+ return "No deployments found."
agents/file_agent.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FileAgent v7 β€” Autonomous file system management, code editing, project scaffolding
3
+ Like Devin's file browser + OneHand's file manipulation
4
+ """
5
+ import asyncio
6
+ import json
7
+ import os
8
+ import re
9
+ import shutil
10
+ from pathlib import Path
11
+ from typing import Dict, List, Optional
12
+ import structlog
13
+ from .base_agent import BaseAgent
14
+
15
+ log = structlog.get_logger()
16
+
17
+ FILE_SYSTEM = """You are an elite file system and project management agent.
18
+ You can:
19
+ - Create, read, edit, delete files and directories
20
+ - Scaffold complete project structures
21
+ - Analyze codebases and suggest improvements
22
+ - Generate file trees and project maps
23
+ - Apply patches and diffs to code files
24
+ - Manage project configuration files
25
+
26
+ Always provide complete, runnable file content.
27
+ """
28
+
29
+ WORKSPACE = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
30
+
31
+ class FileAgent(BaseAgent):
32
+ def __init__(self, ws_manager=None, ai_router=None):
33
+ super().__init__("FileAgent", ws_manager, ai_router)
34
+ os.makedirs(WORKSPACE, exist_ok=True)
35
+
36
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
37
+ session_id = kwargs.get("session_id", "")
38
+ task_id = kwargs.get("task_id", "")
39
+
40
+ await self.emit(task_id, "agent_start", {"agent": "FileAgent", "task": task[:80]}, session_id)
41
+
42
+ task_lower = task.lower()
43
+ if any(k in task_lower for k in ["create", "scaffold", "generate", "new project", "init"]):
44
+ result = await self._scaffold_project(task, context, task_id, session_id)
45
+ elif any(k in task_lower for k in ["read", "show", "view", "list", "tree"]):
46
+ result = await self._read_or_list(task, task_id, session_id)
47
+ elif any(k in task_lower for k in ["edit", "modify", "update", "change", "fix", "patch"]):
48
+ result = await self._edit_file(task, context, task_id, session_id)
49
+ elif any(k in task_lower for k in ["delete", "remove", "clean"]):
50
+ result = await self._delete_files(task, task_id, session_id)
51
+ else:
52
+ result = await self._ai_file_task(task, context, task_id, session_id)
53
+
54
+ return result
55
+
56
+ async def _scaffold_project(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
57
+ """Scaffold a complete project structure."""
58
+ await self.emit(task_id, "tool_called", {
59
+ "agent": "FileAgent", "tool": "scaffold_project", "step": "Generating project structure"
60
+ }, session_id)
61
+
62
+ messages = [
63
+ {"role": "system", "content": FILE_SYSTEM},
64
+ {"role": "user", "content": (
65
+ f"Task: {task}\n\n"
66
+ "Generate a complete project scaffold. Return a JSON object with this structure:\n"
67
+ "{\n"
68
+ ' "project_name": "name",\n'
69
+ ' "files": [\n'
70
+ ' {"path": "relative/path/file.ext", "content": "full file content here"}\n'
71
+ " ],\n"
72
+ ' "description": "what was created",\n'
73
+ ' "run_commands": ["npm install", "npm run dev"]\n'
74
+ "}\n"
75
+ "Include all necessary files for a working project."
76
+ )},
77
+ ]
78
+ response = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.2, max_tokens=8192)
79
+
80
+ # Parse JSON and create files
81
+ created_files = []
82
+ try:
83
+ start = response.find("{")
84
+ end = response.rfind("}") + 1
85
+ if start >= 0 and end > start:
86
+ data = json.loads(response[start:end])
87
+ project_name = data.get("project_name", "project")
88
+ project_path = os.path.join(WORKSPACE, project_name)
89
+ os.makedirs(project_path, exist_ok=True)
90
+
91
+ for f in data.get("files", []):
92
+ filepath = os.path.join(project_path, f["path"])
93
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
94
+ with open(filepath, "w") as fp:
95
+ fp.write(f["content"])
96
+ created_files.append(f["path"])
97
+ await self.emit(task_id, "file_written", {
98
+ "path": f["path"], "size": len(f["content"])
99
+ }, session_id)
100
+
101
+ result = (
102
+ f"βœ… **Project Scaffolded**: `{project_name}`\n\n"
103
+ f"**Files Created** ({len(created_files)}):\n"
104
+ + "\n".join(f"- `{f}`" for f in created_files)
105
+ + f"\n\n**Description:** {data.get('description', '')}\n\n"
106
+ + "**Run Commands:**\n"
107
+ + "\n".join(f"```\n{cmd}\n```" for cmd in data.get("run_commands", []))
108
+ )
109
+ return result
110
+ except Exception as e:
111
+ log.warning("Failed to parse project scaffold JSON", error=str(e))
112
+
113
+ return response
114
+
115
+ async def _read_or_list(self, task: str, task_id: str, session_id: str) -> str:
116
+ """Read files or list directory structure."""
117
+ await self.emit(task_id, "tool_called", {
118
+ "agent": "FileAgent", "tool": "read_files", "step": "Reading file system"
119
+ }, session_id)
120
+
121
+ # Extract path from task
122
+ path_match = re.search(r'["\']([^"\']+)["\']|(\S+\.\w+)', task)
123
+ target_path = path_match.group(1) or path_match.group(2) if path_match else WORKSPACE
124
+
125
+ if not os.path.isabs(target_path):
126
+ target_path = os.path.join(WORKSPACE, target_path)
127
+
128
+ if os.path.isdir(target_path):
129
+ tree = self._get_tree(target_path)
130
+ return f"**Directory Tree:** `{target_path}`\n\n```\n{tree}\n```"
131
+ elif os.path.isfile(target_path):
132
+ with open(target_path) as f:
133
+ content = f.read()
134
+ return f"**File:** `{target_path}`\n\n```\n{content[:4000]}\n```"
135
+ else:
136
+ # List workspace
137
+ tree = self._get_tree(WORKSPACE, max_depth=3)
138
+ return f"**Workspace:** `{WORKSPACE}`\n\n```\n{tree}\n```"
139
+
140
+ async def _edit_file(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
141
+ """Edit a file based on instructions."""
142
+ await self.emit(task_id, "tool_called", {
143
+ "agent": "FileAgent", "tool": "edit_file", "step": "Editing file"
144
+ }, session_id)
145
+
146
+ messages = [
147
+ {"role": "system", "content": FILE_SYSTEM},
148
+ {"role": "user", "content": (
149
+ f"Task: {task}\n\n"
150
+ f"Context: {json.dumps(context)[:500]}\n\n"
151
+ "Return a JSON with: {\"filepath\": \"path\", \"content\": \"full new file content\", \"explanation\": \"what changed\"}"
152
+ )},
153
+ ]
154
+ response = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.1, max_tokens=8192)
155
+
156
+ try:
157
+ start = response.find("{")
158
+ end = response.rfind("}") + 1
159
+ if start >= 0 and end > start:
160
+ data = json.loads(response[start:end])
161
+ filepath = os.path.join(WORKSPACE, data.get("filepath", "output.txt"))
162
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
163
+ with open(filepath, "w") as f:
164
+ f.write(data.get("content", ""))
165
+ await self.emit(task_id, "file_written", {"path": data.get("filepath"), "edited": True}, session_id)
166
+ return f"βœ… **File Edited:** `{data.get('filepath')}`\n\n{data.get('explanation', '')}"
167
+ except Exception:
168
+ pass
169
+ return response
170
+
171
+ async def _delete_files(self, task: str, task_id: str, session_id: str) -> str:
172
+ """Delete files or directories safely."""
173
+ path_match = re.search(r'["\']([^"\']+)["\']', task)
174
+ if path_match:
175
+ target = os.path.join(WORKSPACE, path_match.group(1))
176
+ if os.path.exists(target) and WORKSPACE in target:
177
+ if os.path.isdir(target):
178
+ shutil.rmtree(target)
179
+ else:
180
+ os.remove(target)
181
+ await self.emit(task_id, "file_deleted", {"path": path_match.group(1)}, session_id)
182
+ return f"βœ… Deleted: `{path_match.group(1)}`"
183
+ return "❌ Could not find file/directory to delete. Please specify the path in quotes."
184
+
185
+ async def _ai_file_task(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
186
+ """Handle generic file tasks via AI."""
187
+ messages = [
188
+ {"role": "system", "content": FILE_SYSTEM},
189
+ {"role": "user", "content": f"Task: {task}\n\nWorkspace: {WORKSPACE}\n\nContext: {json.dumps(context)[:300]}"},
190
+ ]
191
+ return await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=4096)
192
+
193
+ def _get_tree(self, path: str, prefix: str = "", max_depth: int = 4, depth: int = 0) -> str:
194
+ """Generate a directory tree string."""
195
+ if depth > max_depth:
196
+ return ""
197
+ try:
198
+ entries = sorted(os.scandir(path), key=lambda e: (e.is_file(), e.name))
199
+ except PermissionError:
200
+ return ""
201
+ lines = []
202
+ for i, entry in enumerate(entries):
203
+ if entry.name.startswith(".") and entry.name not in [".env", ".gitignore"]:
204
+ continue
205
+ connector = "└── " if i == len(entries) - 1 else "β”œβ”€β”€ "
206
+ lines.append(f"{prefix}{connector}{entry.name}")
207
+ if entry.is_dir() and depth < max_depth:
208
+ extension = " " if i == len(entries) - 1 else "β”‚ "
209
+ subtree = self._get_tree(entry.path, prefix + extension, max_depth, depth + 1)
210
+ if subtree:
211
+ lines.append(subtree)
212
+ return "\n".join(lines)
213
+
214
+ def list_workspace(self) -> Dict:
215
+ """List all workspace files."""
216
+ files = []
217
+ for root, dirs, filenames in os.walk(WORKSPACE):
218
+ dirs[:] = [d for d in dirs if not d.startswith(".") and d != "node_modules"]
219
+ for f in filenames:
220
+ full_path = os.path.join(root, f)
221
+ rel_path = os.path.relpath(full_path, WORKSPACE)
222
+ try:
223
+ size = os.path.getsize(full_path)
224
+ files.append({"path": rel_path, "size": size})
225
+ except Exception:
226
+ pass
227
+ return {"workspace": WORKSPACE, "files": files, "total": len(files)}
agents/git_agent.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GitAgent v7 β€” Autonomous Git operations, PR creation, code review
3
+ Full GitHub integration like Manus/Genspark autonomous coding
4
+ """
5
+ import asyncio
6
+ import json
7
+ import os
8
+ import re
9
+ from typing import Dict, List
10
+ import structlog
11
+ from .base_agent import BaseAgent
12
+
13
+ log = structlog.get_logger()
14
+
15
+ GIT_SYSTEM = """You are an elite autonomous Git and GitHub operations agent.
16
+ You can clone, commit, push, pull, create branches, PRs, review code,
17
+ generate commit messages, manage workflows, and handle merge conflicts.
18
+ Always write clear, conventional commit messages (feat/fix/chore/docs/refactor).
19
+ """
20
+
21
+
22
+ class GitAgent(BaseAgent):
23
+ def __init__(self, ws_manager=None, ai_router=None):
24
+ super().__init__("GitAgent", ws_manager, ai_router)
25
+ self.workspace = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
26
+
27
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
28
+ session_id = kwargs.get("session_id", "")
29
+ task_id = kwargs.get("task_id", "")
30
+ await self.emit(task_id, "agent_start", {"agent": "GitAgent", "task": task[:80]}, session_id)
31
+
32
+ t = task.lower()
33
+ if any(k in t for k in ["clone", "checkout"]):
34
+ return await self._clone_repo(task, context, task_id, session_id)
35
+ if any(k in t for k in ["commit", "push", "pull request", " pr "]):
36
+ return await self._commit_and_push(task, context, task_id, session_id)
37
+ if any(k in t for k in ["review", "analyze code", "audit"]):
38
+ return await self._code_review(task, context, task_id, session_id)
39
+ return await self._git_ai_task(task, context, task_id, session_id)
40
+
41
+ async def _clone_repo(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
42
+ urls = re.findall(r'https?://github\.com/[^\s]+', task)
43
+ if not urls:
44
+ return "❌ No GitHub URL found in task."
45
+ url = urls[0]
46
+ repo_name = url.rstrip("/").split("/")[-1].replace(".git", "")
47
+ dest = os.path.join(self.workspace, repo_name)
48
+ await self.emit(task_id, "tool_called", {"agent": "GitAgent", "tool": "git_clone", "step": f"Cloning {repo_name}"}, session_id)
49
+ token = os.environ.get("GITHUB_TOKEN", "")
50
+ auth_url = url.replace("https://", f"https://{token}@") if token else url
51
+ r = await self._run_cmd(["git", "clone", auth_url, dest])
52
+ if r["returncode"] == 0:
53
+ await self.emit(task_id, "git_cloned", {"repo": repo_name, "path": dest}, session_id)
54
+ return f"βœ… **Cloned** `{repo_name}` β†’ `{dest}`\n```\n{r['stdout'][:500]}\n```"
55
+ return f"❌ Clone failed:\n```\n{r['stderr'][:500]}\n```"
56
+
57
+ async def _commit_and_push(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
58
+ repo_path = context.get("repo_path", self.workspace)
59
+ await self.emit(task_id, "tool_called", {"agent": "GitAgent", "tool": "git_commit", "step": "Committing"}, session_id)
60
+ msgs = [
61
+ {"role": "system", "content": "Generate a conventional commit message. Return ONLY the one-line message."},
62
+ {"role": "user", "content": f"Task: {task}\nContext: {json.dumps(context)[:200]}"},
63
+ ]
64
+ commit_msg = (await self.llm(msgs, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=80)).strip().split("\n")[0][:100]
65
+ results = []
66
+ for cmd in [["git", "add", "-A"], ["git", "commit", "-m", commit_msg]]:
67
+ r = await self._run_cmd(cmd, cwd=repo_path)
68
+ results.append(f"$ {' '.join(cmd)}\n{r['stdout']}{r['stderr']}")
69
+ branch = context.get("branch", "main")
70
+ r = await self._run_cmd(["git", "push", "origin", branch], cwd=repo_path)
71
+ results.append(f"$ git push\n{r['stdout']}{r['stderr']}")
72
+ return f"βœ… **Committed:** `{commit_msg}`\n\n```\n" + "\n".join(results) + "\n```"
73
+
74
+ async def _code_review(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
75
+ repo_path = context.get("repo_path", self.workspace)
76
+ diff = await self._run_cmd(["git", "diff", "HEAD~1"], cwd=repo_path)
77
+ msgs = [
78
+ {"role": "system", "content": GIT_SYSTEM},
79
+ {"role": "user", "content": f"Task: {task}\n\nDiff:\n{diff['stdout'][:2500]}\n\nProvide code review: summary, issues, suggestions, score (1-10)."},
80
+ ]
81
+ return await self.llm(msgs, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=4096)
82
+
83
+ async def _git_ai_task(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
84
+ msgs = [
85
+ {"role": "system", "content": GIT_SYSTEM},
86
+ {"role": "user", "content": f"Task: {task}\nWorkspace: {self.workspace}\nContext: {json.dumps(context)[:300]}"},
87
+ ]
88
+ return await self.llm(msgs, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=4096)
89
+
90
+ async def create_github_pr(self, repo_owner: str, repo_name: str, title: str, body: str,
91
+ head_branch: str, base_branch: str = "main",
92
+ task_id: str = "", session_id: str = "") -> Dict:
93
+ import httpx
94
+ token = os.environ.get("GITHUB_TOKEN", "")
95
+ if not token:
96
+ return {"error": "GITHUB_TOKEN not set"}
97
+ async with httpx.AsyncClient(timeout=30) as client:
98
+ resp = await client.post(
99
+ f"https://api.github.com/repos/{repo_owner}/{repo_name}/pulls",
100
+ headers={"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"},
101
+ json={"title": title, "body": body, "head": head_branch, "base": base_branch},
102
+ )
103
+ data = resp.json()
104
+ if resp.status_code == 201:
105
+ await self.emit(task_id, "pr_created", {"url": data.get("html_url")}, session_id)
106
+ return {"success": True, "url": data.get("html_url")}
107
+ return {"error": data.get("message", "Failed"), "status": resp.status_code}
108
+
109
+ async def _run_cmd(self, cmd: List[str], cwd: str = None, timeout: int = 60) -> Dict:
110
+ try:
111
+ proc = await asyncio.create_subprocess_exec(
112
+ *cmd, cwd=cwd or self.workspace,
113
+ stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
114
+ )
115
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
116
+ return {"returncode": proc.returncode,
117
+ "stdout": stdout.decode("utf-8", errors="replace"),
118
+ "stderr": stderr.decode("utf-8", errors="replace")}
119
+ except Exception as e:
120
+ return {"returncode": -1, "stdout": "", "stderr": str(e)}
agents/memory_agent.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MemoryAgent β€” Persistent long-term memory system (Phase 5)
3
+ SQLite-backed with semantic search simulation
4
+ """
5
+ import json
6
+ import time
7
+ from typing import Dict, List, Optional
8
+ import structlog
9
+ from .base_agent import BaseAgent
10
+ from memory.db import save_memory, search_memory, get_history, get_project_memory
11
+
12
+ log = structlog.get_logger()
13
+
14
+
15
+ class MemoryAgent(BaseAgent):
16
+ def __init__(self, ws_manager=None, ai_router=None):
17
+ super().__init__("MemoryAgent", ws_manager, ai_router)
18
+
19
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
20
+ session_id = kwargs.get("session_id", "")
21
+ task_id = kwargs.get("task_id", "")
22
+
23
+ # Determine if retrieve or save
24
+ task_lower = task.lower()
25
+ if any(k in task_lower for k in ["remember", "save", "store", "record"]):
26
+ content = context.get("content", task)
27
+ await self.save(content, session_id=session_id, memory_type="user_directive")
28
+ return f"βœ… Saved to memory: {content[:100]}"
29
+ else:
30
+ results = await self.retrieve(task, session_id=session_id)
31
+ if results:
32
+ return "πŸ“š **Memory Retrieved:**\n\n" + "\n".join(f"- {r['content'][:200]}" for r in results[:5])
33
+ return "No relevant memories found."
34
+
35
+ async def save(
36
+ self,
37
+ content: str,
38
+ session_id: str = "",
39
+ project_id: str = "",
40
+ memory_type: str = "general",
41
+ key: str = "",
42
+ metadata: Dict = {},
43
+ ):
44
+ """Save content to persistent memory."""
45
+ await save_memory(
46
+ content=content,
47
+ memory_type=memory_type,
48
+ session_id=session_id,
49
+ project_id=project_id,
50
+ key=key,
51
+ metadata=metadata,
52
+ )
53
+
54
+ async def retrieve(
55
+ self,
56
+ query: str,
57
+ session_id: str = "",
58
+ project_id: str = "",
59
+ limit: int = 10,
60
+ ) -> List[Dict]:
61
+ """Retrieve relevant memories."""
62
+ return await search_memory(query[:100], session_id=session_id, project_id=project_id, limit=limit)
63
+
64
+ async def get_conversation_history(self, session_id: str, limit: int = 20) -> List[Dict]:
65
+ """Get conversation history for a session."""
66
+ return await get_history(session_id, limit=limit)
67
+
68
+ async def save_interaction(
69
+ self,
70
+ user_message: str,
71
+ assistant_response: str,
72
+ session_id: str = "",
73
+ intent: Dict = {},
74
+ ):
75
+ """Save a full interaction to memory."""
76
+ await save_memory(
77
+ content=user_message,
78
+ memory_type="conversation",
79
+ session_id=session_id,
80
+ key="user_message",
81
+ metadata={"intent": intent.get("intent", ""), "timestamp": time.time()},
82
+ )
83
+ await save_memory(
84
+ content=assistant_response,
85
+ memory_type="conversation",
86
+ session_id=session_id,
87
+ key="assistant_response",
88
+ metadata={"agent": intent.get("primary_agent", "chat"), "timestamp": time.time()},
89
+ )
90
+
91
+ async def save_coding_style(self, style_notes: str, session_id: str = ""):
92
+ """Remember user's coding preferences."""
93
+ await save_memory(
94
+ content=style_notes,
95
+ memory_type="user_preference",
96
+ session_id=session_id,
97
+ key="coding_style",
98
+ metadata={"category": "coding_style"},
99
+ )
100
+
101
+ async def save_project_context(self, project_id: str, context: Dict):
102
+ """Save project-specific context."""
103
+ await save_memory(
104
+ content=json.dumps(context),
105
+ memory_type="project_context",
106
+ project_id=project_id,
107
+ key="project_context",
108
+ metadata={"timestamp": time.time()},
109
+ )
110
+
111
+ async def get_project_context(self, project_id: str) -> Optional[Dict]:
112
+ """Get project context."""
113
+ results = await get_project_memory(project_id, memory_type="project_context", limit=1)
114
+ if results:
115
+ try:
116
+ return json.loads(results[0]["content"])
117
+ except Exception:
118
+ return {"raw": results[0]["content"]}
119
+ return None
120
+
121
+ async def build_context_for_agent(self, session_id: str, query: str) -> Dict:
122
+ """Build rich context dict for agents from memory."""
123
+ history = await self.get_conversation_history(session_id, limit=10)
124
+ relevant = await self.retrieve(query, session_id=session_id, limit=5)
125
+
126
+ return {
127
+ "history": [{"role": "user" if i % 2 == 0 else "assistant", "content": h["content"]} for i, h in enumerate(reversed(history))],
128
+ "relevant_memories": [r["content"][:200] for r in relevant],
129
+ "session_id": session_id,
130
+ }
agents/orchestrator.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ God Agent Orchestrator β€” Central Brain (Manus-style)
3
+ Routes user intent β†’ correct agent β†’ merges results
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import time
9
+ import uuid
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ import structlog
13
+
14
+ log = structlog.get_logger()
15
+
16
+ SYSTEM_PROMPT = """You are GOD AGENT β€” an elite autonomous AI operating system combining:
17
+ - Manus-style orchestration and planning
18
+ - Devin-style autonomous coding and debugging
19
+ - Genspark-style repo engineering
20
+
21
+ You coordinate multiple specialized agents:
22
+ - ChatAgent: Conversation and clarification
23
+ - PlannerAgent: Break goals into executable task graphs
24
+ - CodingAgent: Generate, edit, refactor, fix code
25
+ - DebugAgent: Detect and auto-fix errors (self-healing)
26
+ - MemoryAgent: Persistent long-term memory
27
+ - ConnectorAgent: GitHub/HF/Vercel/n8n integrations
28
+ - DeployAgent: Automated deployments
29
+ - WorkflowAgent: n8n workflow generation
30
+ - SandboxAgent: VS Code sandbox execution
31
+
32
+ You respond in Burmese or English based on user preference.
33
+ Always think step-by-step. Be autonomous, decisive, and thorough.
34
+ """
35
+
36
+
37
+ class GodAgentOrchestrator:
38
+ """
39
+ Central orchestrator β€” routes tasks to specialized agents,
40
+ merges results, manages agent collaboration.
41
+ """
42
+
43
+ def __init__(self, ws_manager=None, ai_router=None):
44
+ self.ws = ws_manager
45
+ self.ai_router = ai_router
46
+ self._agents: Dict[str, Any] = {}
47
+ self._active_tasks: Dict[str, Dict] = {}
48
+
49
+ def register_agent(self, name: str, agent):
50
+ self._agents[name] = agent
51
+ log.info("Agent registered", agent=name)
52
+
53
+ def get_agent(self, name: str):
54
+ return self._agents.get(name)
55
+
56
+ # ─── Intent Classification ────────────────────────────────────────────────
57
+
58
+ async def classify_intent(self, user_message: str) -> Dict:
59
+ """Classify user intent to route to correct agent(s)."""
60
+ classify_prompt = f"""Classify this user request and identify which agents are needed.
61
+
62
+ User message: "{user_message}"
63
+
64
+ Respond ONLY with JSON:
65
+ {{
66
+ "primary_agent": "chat|planner|coding|debug|connector|deploy|workflow|sandbox|memory",
67
+ "secondary_agents": [],
68
+ "intent": "brief description",
69
+ "requires_planning": true/false,
70
+ "is_code_task": true/false,
71
+ "is_deployment": true/false,
72
+ "is_workflow": true/false,
73
+ "language": "en|my",
74
+ "complexity": "simple|moderate|complex"
75
+ }}"""
76
+
77
+ if self.ai_router:
78
+ messages = [
79
+ {"role": "system", "content": "You are an intent classifier. Return only valid JSON."},
80
+ {"role": "user", "content": classify_prompt},
81
+ ]
82
+ raw = await self.ai_router.complete(messages, temperature=0.1, max_tokens=300)
83
+ try:
84
+ start = raw.find("{")
85
+ end = raw.rfind("}") + 1
86
+ if start >= 0 and end > start:
87
+ return json.loads(raw[start:end])
88
+ except Exception:
89
+ pass
90
+
91
+ # Fallback heuristic classification
92
+ msg_lower = user_message.lower()
93
+ is_code = any(k in msg_lower for k in ["code", "build", "create", "write", "fix", "debug", "api", "function", "class", "script", "app"])
94
+ is_deploy = any(k in msg_lower for k in ["deploy", "vercel", "github", "push", "publish", "release"])
95
+ is_workflow = any(k in msg_lower for k in ["workflow", "n8n", "automate", "trigger", "pipeline", "schedule"])
96
+ is_memory = any(k in msg_lower for k in ["remember", "recall", "memory", "history", "previous"])
97
+
98
+ primary = "coding" if is_code else ("deploy" if is_deploy else ("workflow" if is_workflow else ("memory" if is_memory else "chat")))
99
+
100
+ return {
101
+ "primary_agent": primary,
102
+ "secondary_agents": [],
103
+ "intent": user_message[:80],
104
+ "requires_planning": is_code or is_deploy,
105
+ "is_code_task": is_code,
106
+ "is_deployment": is_deploy,
107
+ "is_workflow": is_workflow,
108
+ "language": "my" if any(c > "\u1000" for c in user_message) else "en",
109
+ "complexity": "complex" if len(user_message) > 200 else "moderate",
110
+ }
111
+
112
+ # ─── Main Orchestration ───────────────────────────────────────────────────
113
+
114
+ async def orchestrate(
115
+ self,
116
+ user_message: str,
117
+ session_id: str = "",
118
+ task_id: str = "",
119
+ context: Dict = {},
120
+ ) -> str:
121
+ """Main orchestration β€” classify β†’ route β†’ execute β†’ merge."""
122
+ exec_id = task_id or f"orch_{uuid.uuid4().hex[:8]}"
123
+
124
+ await self._emit(session_id, task_id, "orchestrator_start", {
125
+ "message": user_message[:100],
126
+ "session_id": session_id,
127
+ })
128
+
129
+ # 1. Classify intent
130
+ intent = await self.classify_intent(user_message)
131
+ log.info("Intent classified", **intent)
132
+
133
+ await self._emit(session_id, task_id, "intent_classified", {
134
+ "primary_agent": intent["primary_agent"],
135
+ "complexity": intent["complexity"],
136
+ "language": intent["language"],
137
+ })
138
+
139
+ # 2. Build execution plan for complex tasks
140
+ if intent.get("requires_planning") and intent.get("complexity") == "complex":
141
+ planner = self._agents.get("planner")
142
+ if planner:
143
+ await self._emit(session_id, task_id, "agent_called", {"agent": "PlannerAgent"})
144
+ plan = await planner.run(user_message, context=context, session_id=session_id, task_id=exec_id)
145
+ context["plan"] = plan
146
+
147
+ # 3. Route to primary agent
148
+ primary_name = intent["primary_agent"]
149
+ primary_agent = self._agents.get(primary_name) or self._agents.get("chat")
150
+
151
+ if not primary_agent:
152
+ return f"Agent '{primary_name}' not available."
153
+
154
+ await self._emit(session_id, task_id, "agent_called", {
155
+ "agent": primary_name,
156
+ "intent": intent["intent"],
157
+ })
158
+
159
+ # 4. Execute primary agent
160
+ result = await primary_agent.run(
161
+ user_message,
162
+ context={**context, "intent": intent},
163
+ session_id=session_id,
164
+ task_id=exec_id,
165
+ )
166
+
167
+ # 5. Run secondary agents in parallel if needed
168
+ secondary_results = []
169
+ if intent.get("secondary_agents"):
170
+ tasks = []
171
+ for agent_name in intent["secondary_agents"]:
172
+ agent = self._agents.get(agent_name)
173
+ if agent:
174
+ tasks.append(agent.run(user_message, context=context, session_id=session_id, task_id=exec_id))
175
+ if tasks:
176
+ secondary_results = await asyncio.gather(*tasks, return_exceptions=True)
177
+
178
+ # 6. Save to memory
179
+ memory_agent = self._agents.get("memory")
180
+ if memory_agent:
181
+ asyncio.create_task(memory_agent.save_interaction(
182
+ user_message=user_message,
183
+ assistant_response=result,
184
+ session_id=session_id,
185
+ intent=intent,
186
+ ))
187
+
188
+ await self._emit(session_id, task_id, "orchestrator_complete", {
189
+ "primary_agent": primary_name,
190
+ "result_length": len(result),
191
+ })
192
+
193
+ return result
194
+
195
+ # ─── Self-Healing Loop ────────────────────────────────────────────────────
196
+
197
+ async def self_heal(
198
+ self,
199
+ error: str,
200
+ original_task: str,
201
+ task_id: str = "",
202
+ session_id: str = "",
203
+ max_retries: int = 3,
204
+ ) -> str:
205
+ """Self-healing retry loop β€” automatically fix errors."""
206
+ debug_agent = self._agents.get("debug")
207
+ if not debug_agent:
208
+ return f"Cannot self-heal: DebugAgent not available. Error: {error}"
209
+
210
+ for attempt in range(1, max_retries + 1):
211
+ await self._emit(session_id, task_id, "self_heal_attempt", {
212
+ "attempt": attempt,
213
+ "max": max_retries,
214
+ "error": error[:200],
215
+ })
216
+ fix = await debug_agent.run(
217
+ f"Fix this error: {error}\n\nOriginal task: {original_task}",
218
+ context={"attempt": attempt},
219
+ session_id=session_id,
220
+ task_id=task_id,
221
+ )
222
+ if fix and "❌" not in fix[:10]:
223
+ await self._emit(session_id, task_id, "self_heal_success", {
224
+ "attempt": attempt,
225
+ "fix": fix[:200],
226
+ })
227
+ return fix
228
+
229
+ return f"❌ Self-healing failed after {max_retries} attempts. Last error: {error}"
230
+
231
+ # ─── Emit Helper ─────────────────────────────────────────────────────────
232
+
233
+ async def _emit(self, session_id: str, task_id: str, event: str, data: Dict):
234
+ if not self.ws:
235
+ return
236
+ if task_id:
237
+ await self.ws.emit(task_id, event, data, session_id=session_id)
238
+ if session_id:
239
+ await self.ws.emit_chat(session_id, event, data)
240
+
241
+ # ─── Agent Status ────────────────────────────────────────────────────────
242
+
243
+ def get_status(self) -> Dict:
244
+ return {
245
+ "agents": list(self._agents.keys()),
246
+ "active_tasks": len(self._active_tasks),
247
+ "total_agents": len(self._agents),
248
+ }
agents/orchestrator_v7.py ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ God Agent Orchestrator v7 β€” True Autonomous Engineering OS
3
+ Manus + Genspark + Devin (OneHand) style multi-agent coordination
4
+ """
5
+ import asyncio
6
+ import json
7
+ import time
8
+ import uuid
9
+ from typing import Any, Dict, List, Optional
10
+ import structlog
11
+
12
+ log = structlog.get_logger()
13
+
14
+ SYSTEM_PROMPT_V7 = """You are GOD AGENT v7 β€” the world's most advanced autonomous AI engineering operating system.
15
+
16
+ You combine the best of:
17
+ - 🧠 **Manus** β€” Deep reasoning, multi-step planning, autonomous decision making
18
+ - ⚑ **Genspark** β€” Repository engineering, code generation at scale, multi-model AI routing
19
+ - πŸ€– **Devin/OneHand** β€” Self-healing code execution, real browser control, file system mastery
20
+
21
+ Your specialized agent fleet (15 agents):
22
+ - **OrchestratorAgent** β€” Central brain, routes & coordinates all agents
23
+ - **PlannerAgent** β€” Breaks complex goals into executable task graphs
24
+ - **CodingAgent** β€” Generates production-quality code in any language
25
+ - **DebugAgent** β€” Autonomous error detection and self-healing
26
+ - **TestAgent** β€” Generates & runs comprehensive test suites
27
+ - **FileAgent** β€” Full file system control, project scaffolding
28
+ - **GitAgent** β€” Git operations, PR creation, code review
29
+ - **BrowserAgent** β€” Web research, scraping, web automation
30
+ - **VisionAgent** β€” UI/UX generation, design-to-code
31
+ - **SandboxAgent** β€” Code execution in isolated environments
32
+ - **DeployAgent** β€” Auto-deploy to Vercel, HF, Docker, AWS
33
+ - **ConnectorAgent** β€” External integrations (GitHub, Slack, Notion, etc.)
34
+ - **MemoryAgent** β€” Long-term memory and context management
35
+ - **WorkflowAgent** β€” n8n/automation workflow generation
36
+ - **UIAgent** β€” Real-time UI state updates
37
+
38
+ Operating principles:
39
+ 1. PLAN before executing β€” always create a step-by-step plan for complex tasks
40
+ 2. EXECUTE autonomously β€” don't ask for confirmation on obvious steps
41
+ 3. SELF-HEAL β€” when errors occur, automatically retry with corrections
42
+ 4. PARALLELIZE β€” run independent tasks simultaneously
43
+ 5. REMEMBER β€” store and recall relevant context from memory
44
+
45
+ Respond in Burmese or English based on user language.
46
+ Be decisive, thorough, and production-focused.
47
+ """
48
+
49
+
50
+ class GodAgentOrchestratorV7:
51
+ """
52
+ v7 Central Orchestrator β€” 15-agent autonomous engineering OS.
53
+ Routes tasks, coordinates parallel execution, self-heals failures.
54
+ """
55
+
56
+ def __init__(self, ws_manager=None, ai_router=None):
57
+ self.ws = ws_manager
58
+ self.ai_router = ai_router
59
+ self._agents: Dict[str, Any] = {}
60
+ self._active_tasks: Dict[str, Dict] = {}
61
+ self._task_history: List[Dict] = []
62
+
63
+ def register_agent(self, name: str, agent):
64
+ self._agents[name] = agent
65
+ log.info("v7 Agent registered", agent=name)
66
+
67
+ def get_agent(self, name: str):
68
+ return self._agents.get(name)
69
+
70
+ # ── Intent Classification ───────────────────────────────────────────────
71
+
72
+ async def classify_intent_v7(self, user_message: str) -> Dict:
73
+ """Advanced intent classification for 15-agent routing."""
74
+ classify_prompt = f"""Classify this user request for our 15-agent autonomous engineering OS.
75
+
76
+ User message: "{user_message}"
77
+
78
+ Available agents: orchestrator, planner, coding, debug, test, file, git, browser, vision, sandbox, deploy, connector, memory, workflow, ui
79
+
80
+ Respond ONLY with valid JSON:
81
+ {{
82
+ "primary_agent": "agent_name",
83
+ "secondary_agents": ["agent1", "agent2"],
84
+ "parallel_tasks": [],
85
+ "intent": "brief description",
86
+ "requires_planning": true/false,
87
+ "is_code_task": true/false,
88
+ "is_deployment": true/false,
89
+ "is_research": true/false,
90
+ "is_ui_task": true/false,
91
+ "is_git_task": true/false,
92
+ "is_test_task": true/false,
93
+ "language": "en|my",
94
+ "complexity": "simple|moderate|complex|ultra_complex",
95
+ "estimated_steps": 3
96
+ }}"""
97
+
98
+ if self.ai_router:
99
+ messages = [
100
+ {"role": "system", "content": "You are an intent classifier. Return only valid JSON."},
101
+ {"role": "user", "content": classify_prompt},
102
+ ]
103
+ raw = await self.ai_router.complete(messages, temperature=0.1, max_tokens=400)
104
+ try:
105
+ start = raw.find("{")
106
+ end = raw.rfind("}") + 1
107
+ if start >= 0 and end > start:
108
+ return json.loads(raw[start:end])
109
+ except Exception:
110
+ pass
111
+
112
+ # Heuristic fallback
113
+ msg = user_message.lower()
114
+ is_code = any(k in msg for k in ["code", "build", "create", "write", "fix", "debug", "api", "function", "class", "script", "app", "backend", "frontend"])
115
+ is_deploy = any(k in msg for k in ["deploy", "vercel", "github", "push", "publish", "release", "hf", "hugging"])
116
+ is_research = any(k in msg for k in ["research", "search", "find", "browse", "web", "scrape", "analyze"])
117
+ is_ui = any(k in msg for k in ["ui", "ux", "design", "component", "dashboard", "landing", "page", "frontend"])
118
+ is_git = any(k in msg for k in ["git", "commit", "pr", "pull request", "branch", "merge", "clone"])
119
+ is_test = any(k in msg for k in ["test", "testing", "pytest", "jest", "coverage", "qa"])
120
+ is_file = any(k in msg for k in ["file", "folder", "directory", "scaffold", "project structure"])
121
+ is_workflow = any(k in msg for k in ["workflow", "n8n", "automate", "trigger", "pipeline"])
122
+ is_memory = any(k in msg for k in ["remember", "recall", "memory", "history", "previous"])
123
+
124
+ primary = (
125
+ "vision" if is_ui else
126
+ "git" if is_git else
127
+ "test" if is_test else
128
+ "browser" if is_research else
129
+ "file" if is_file else
130
+ "coding" if is_code else
131
+ "deploy" if is_deploy else
132
+ "workflow" if is_workflow else
133
+ "memory" if is_memory else
134
+ "chat"
135
+ )
136
+
137
+ secondary = []
138
+ if is_code and is_deploy:
139
+ secondary.append("deploy")
140
+ if is_code and is_test:
141
+ secondary.append("test")
142
+ if is_code and is_git:
143
+ secondary.append("git")
144
+
145
+ return {
146
+ "primary_agent": primary,
147
+ "secondary_agents": secondary,
148
+ "parallel_tasks": [],
149
+ "intent": user_message[:80],
150
+ "requires_planning": is_code or is_deploy or len(user_message) > 150,
151
+ "is_code_task": is_code,
152
+ "is_deployment": is_deploy,
153
+ "is_research": is_research,
154
+ "is_ui_task": is_ui,
155
+ "is_git_task": is_git,
156
+ "is_test_task": is_test,
157
+ "language": "my" if any(c > "\u1000" for c in user_message) else "en",
158
+ "complexity": "ultra_complex" if len(user_message) > 500 else ("complex" if len(user_message) > 200 else "moderate"),
159
+ "estimated_steps": 5 if is_code else 3,
160
+ }
161
+
162
+ # ── Main Orchestration ─────────────────────────────────────────────────
163
+
164
+ async def orchestrate(
165
+ self,
166
+ user_message: str,
167
+ session_id: str = "",
168
+ task_id: str = "",
169
+ context: Dict = {},
170
+ ) -> str:
171
+ exec_id = task_id or f"v7_{uuid.uuid4().hex[:8]}"
172
+
173
+ await self._emit(session_id, task_id, "orchestrator_start", {
174
+ "message": user_message[:100],
175
+ "version": "v7",
176
+ "agents_available": len(self._agents),
177
+ })
178
+
179
+ # 1. Classify intent
180
+ intent = await self.classify_intent_v7(user_message)
181
+ log.info("v7 Intent classified", **{k: v for k, v in intent.items() if k != "parallel_tasks"})
182
+
183
+ await self._emit(session_id, task_id, "intent_classified", {
184
+ "primary_agent": intent["primary_agent"],
185
+ "secondary_agents": intent["secondary_agents"],
186
+ "complexity": intent["complexity"],
187
+ "language": intent["language"],
188
+ })
189
+
190
+ # 2. Planning phase for complex tasks
191
+ plan_context = {}
192
+ if intent.get("requires_planning") and intent.get("complexity") in ("complex", "ultra_complex"):
193
+ planner = self._agents.get("planner")
194
+ if planner:
195
+ await self._emit(session_id, task_id, "agent_called", {
196
+ "agent": "PlannerAgent", "phase": "planning"
197
+ })
198
+ try:
199
+ plan = await asyncio.wait_for(
200
+ planner.run(user_message, context={**context, "intent": intent},
201
+ session_id=session_id, task_id=exec_id),
202
+ timeout=60
203
+ )
204
+ plan_context["plan"] = plan
205
+ await self._emit(session_id, task_id, "plan_ready", {"plan_length": len(plan)})
206
+ except asyncio.TimeoutError:
207
+ log.warning("PlannerAgent timed out")
208
+
209
+ # 3. Route to primary agent
210
+ primary_name = intent["primary_agent"]
211
+ primary_agent = self._agents.get(primary_name) or self._agents.get("chat")
212
+
213
+ if not primary_agent:
214
+ return f"Agent '{primary_name}' not available."
215
+
216
+ await self._emit(session_id, task_id, "agent_called", {
217
+ "agent": primary_name,
218
+ "intent": intent["intent"],
219
+ })
220
+
221
+ full_context = {**context, **plan_context, "intent": intent}
222
+
223
+ # 4. Execute primary agent
224
+ try:
225
+ result = await asyncio.wait_for(
226
+ primary_agent.run(user_message, context=full_context, session_id=session_id, task_id=exec_id),
227
+ timeout=300
228
+ )
229
+ except asyncio.TimeoutError:
230
+ result = f"⚠️ Primary agent ({primary_name}) timed out. Please try a more specific request."
231
+ except Exception as e:
232
+ log.error("Primary agent error", agent=primary_name, error=str(e))
233
+ result = await self.self_heal(str(e), user_message, exec_id, session_id)
234
+
235
+ # 5. Run secondary agents in parallel
236
+ if intent.get("secondary_agents"):
237
+ secondary_tasks = []
238
+ for agent_name in intent["secondary_agents"]:
239
+ agent = self._agents.get(agent_name)
240
+ if agent:
241
+ secondary_tasks.append(
242
+ asyncio.wait_for(
243
+ agent.run(user_message, context={**full_context, "primary_result": result},
244
+ session_id=session_id, task_id=exec_id),
245
+ timeout=120
246
+ )
247
+ )
248
+ if secondary_tasks:
249
+ secondary_results = await asyncio.gather(*secondary_tasks, return_exceptions=True)
250
+ valid_results = [r for r in secondary_results if isinstance(r, str) and len(r) > 10]
251
+ if valid_results:
252
+ result += "\n\n---\n\n" + "\n\n".join(valid_results)
253
+
254
+ # 6. Save to memory asynchronously
255
+ memory_agent = self._agents.get("memory")
256
+ if memory_agent:
257
+ asyncio.create_task(memory_agent.save_interaction(
258
+ user_message=user_message,
259
+ assistant_response=result,
260
+ session_id=session_id,
261
+ intent=intent,
262
+ ))
263
+
264
+ # 7. Record in task history
265
+ self._task_history.append({
266
+ "id": exec_id,
267
+ "message": user_message[:200],
268
+ "primary_agent": primary_name,
269
+ "result_length": len(result),
270
+ "timestamp": time.time(),
271
+ })
272
+ if len(self._task_history) > 100:
273
+ self._task_history = self._task_history[-100:]
274
+
275
+ await self._emit(session_id, task_id, "orchestrator_complete", {
276
+ "primary_agent": primary_name,
277
+ "result_length": len(result),
278
+ "version": "v7",
279
+ })
280
+
281
+ return result
282
+
283
+ # ── Self-Healing ───────────────────────────────────────────────────────
284
+
285
+ async def self_heal(self, error: str, original_task: str, task_id: str = "",
286
+ session_id: str = "", max_retries: int = 3) -> str:
287
+ debug_agent = self._agents.get("debug")
288
+ if not debug_agent:
289
+ return f"❌ Self-heal unavailable. Error: {error}"
290
+
291
+ for attempt in range(1, max_retries + 1):
292
+ await self._emit(session_id, task_id, "self_heal_attempt", {
293
+ "attempt": attempt, "max": max_retries, "error": error[:200]
294
+ })
295
+ try:
296
+ fix = await asyncio.wait_for(
297
+ debug_agent.run(
298
+ f"Fix this error: {error}\n\nOriginal task: {original_task}",
299
+ context={"attempt": attempt, "error": error},
300
+ session_id=session_id, task_id=task_id,
301
+ ),
302
+ timeout=60
303
+ )
304
+ if fix and "❌" not in fix[:10]:
305
+ await self._emit(session_id, task_id, "self_heal_success", {"attempt": attempt})
306
+ return fix
307
+ except Exception as e:
308
+ log.warning("Self-heal attempt failed", attempt=attempt, error=str(e))
309
+
310
+ return f"❌ Self-healing failed after {max_retries} attempts. Error: {error}"
311
+
312
+ # ── Helpers ────────────────────────────────────────────────────────────
313
+
314
+ async def _emit(self, session_id: str, task_id: str, event: str, data: Dict):
315
+ if not self.ws:
316
+ return
317
+ try:
318
+ if task_id:
319
+ await self.ws.emit(task_id, event, data, session_id=session_id)
320
+ if session_id:
321
+ await self.ws.emit_chat(session_id, event, data)
322
+ except Exception:
323
+ pass
324
+
325
+ def get_status(self) -> Dict:
326
+ return {
327
+ "version": "7.0.0",
328
+ "agents": list(self._agents.keys()),
329
+ "total_agents": len(self._agents),
330
+ "active_tasks": len(self._active_tasks),
331
+ "tasks_completed": len(self._task_history),
332
+ "capabilities": [
333
+ "autonomous_planning", "multi_agent_parallel",
334
+ "self_healing", "web_browsing", "file_management",
335
+ "git_operations", "ui_generation", "test_generation",
336
+ "deployment", "memory", "workflow_automation"
337
+ ],
338
+ }
agents/planner_agent.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PlannerAgent β€” Breaks goals into executable task graphs (Manus-style)
3
+ """
4
+ import json
5
+ from typing import Dict, List
6
+ import structlog
7
+ from .base_agent import BaseAgent
8
+
9
+ log = structlog.get_logger()
10
+
11
+ PLANNER_SYSTEM = """You are an elite software architect and project planner.
12
+ Break down user goals into detailed, executable task graphs.
13
+ Think like Devin/Manus β€” autonomous, thorough, production-minded.
14
+
15
+ Always respond with valid JSON task plans.
16
+ """
17
+
18
+ PLANNER_PROMPT = """Break this goal into a detailed execution plan.
19
+
20
+ Goal: {goal}
21
+ Context: {context}
22
+
23
+ Respond ONLY with valid JSON:
24
+ {{
25
+ "title": "Plan title",
26
+ "steps": [
27
+ {{
28
+ "id": "step_1",
29
+ "name": "Step name",
30
+ "description": "Detailed description",
31
+ "agent": "coding|debug|connector|deploy|workflow|sandbox|memory",
32
+ "tool": "code|shell|file|github|memory|search|test|none",
33
+ "depends_on": [],
34
+ "estimated_seconds": 15,
35
+ "can_parallel": false
36
+ }}
37
+ ],
38
+ "estimated_duration": 120,
39
+ "tools_needed": ["code", "shell"],
40
+ "agents_needed": ["coding", "debug"],
41
+ "complexity": "simple|moderate|complex"
42
+ }}"""
43
+
44
+
45
+ class PlannerAgent(BaseAgent):
46
+ def __init__(self, ws_manager=None, ai_router=None):
47
+ super().__init__("PlannerAgent", ws_manager, ai_router)
48
+
49
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
50
+ session_id = kwargs.get("session_id", "")
51
+ task_id = kwargs.get("task_id", "")
52
+
53
+ await self.emit(task_id, "agent_start", {"agent": "PlannerAgent", "goal": task[:80]}, session_id)
54
+
55
+ ctx_str = json.dumps(context.get("memory", []))[:300] if context.get("memory") else "No prior context"
56
+ prompt = PLANNER_PROMPT.format(goal=task, context=ctx_str)
57
+
58
+ messages = [
59
+ {"role": "system", "content": PLANNER_SYSTEM},
60
+ {"role": "user", "content": prompt},
61
+ ]
62
+
63
+ raw = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.2, max_tokens=2000)
64
+
65
+ try:
66
+ start = raw.find("{")
67
+ end = raw.rfind("}") + 1
68
+ if start >= 0 and end > start:
69
+ plan = json.loads(raw[start:end])
70
+ else:
71
+ plan = json.loads(raw)
72
+
73
+ await self.emit(task_id, "plan_ready", {
74
+ "title": plan.get("title", "Execution Plan"),
75
+ "steps": len(plan.get("steps", [])),
76
+ "estimated_duration": plan.get("estimated_duration", 60),
77
+ "agents_needed": plan.get("agents_needed", []),
78
+ }, session_id)
79
+
80
+ return json.dumps(plan)
81
+
82
+ except Exception as e:
83
+ log.warning("Plan parse failed", error=str(e))
84
+ fallback = self._fallback_plan(task)
85
+ await self.emit(task_id, "plan_ready", {"title": "Fallback Plan", "steps": len(fallback["steps"])}, session_id)
86
+ return json.dumps(fallback)
87
+
88
+ def _fallback_plan(self, goal: str) -> Dict:
89
+ return {
90
+ "title": f"Plan: {goal[:50]}",
91
+ "steps": [
92
+ {"id": "step_1", "name": "Analyze Requirements", "description": f"Analyze: {goal[:60]}", "agent": "coding", "tool": "none", "depends_on": [], "estimated_seconds": 10, "can_parallel": False},
93
+ {"id": "step_2", "name": "Design Solution", "description": "Design the architecture", "agent": "coding", "tool": "code", "depends_on": ["step_1"], "estimated_seconds": 20, "can_parallel": False},
94
+ {"id": "step_3", "name": "Implement", "description": "Write implementation code", "agent": "coding", "tool": "code", "depends_on": ["step_2"], "estimated_seconds": 30, "can_parallel": False},
95
+ {"id": "step_4", "name": "Test & Debug", "description": "Test and fix errors", "agent": "debug", "tool": "test", "depends_on": ["step_3"], "estimated_seconds": 20, "can_parallel": False},
96
+ {"id": "step_5", "name": "Document", "description": "Write documentation", "agent": "coding", "tool": "file", "depends_on": ["step_4"], "estimated_seconds": 10, "can_parallel": True},
97
+ ],
98
+ "estimated_duration": 90,
99
+ "tools_needed": ["code", "test"],
100
+ "agents_needed": ["coding", "debug"],
101
+ "complexity": "moderate",
102
+ }
103
+
104
+ async def build_task_graph(self, plan_json: str) -> List[Dict]:
105
+ """Build execution order respecting dependencies."""
106
+ try:
107
+ plan = json.loads(plan_json)
108
+ steps = plan.get("steps", [])
109
+ # Topological sort by dependencies
110
+ ordered = []
111
+ visited = set()
112
+
113
+ def visit(step_id):
114
+ if step_id in visited:
115
+ return
116
+ visited.add(step_id)
117
+ step = next((s for s in steps if s["id"] == step_id), None)
118
+ if step:
119
+ for dep in step.get("depends_on", []):
120
+ visit(dep)
121
+ ordered.append(step)
122
+
123
+ for step in steps:
124
+ visit(step["id"])
125
+ return ordered
126
+ except Exception:
127
+ return []
agents/reasoning_agent.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ReasoningAgent v7 β€” Complex reasoning, analysis, and multi-step problem solving
3
+ GOD AGENT OS β€” Using DeepSeek R1, Qwen QwQ, o1-mini style reasoning
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ from typing import Dict, Any, List, Optional
9
+
10
+ import structlog
11
+
12
+ from .base_agent import BaseAgent
13
+
14
+ log = structlog.get_logger()
15
+
16
+ REASONING_SYSTEM = """You are an elite autonomous reasoning and analysis agent.
17
+ You excel at:
18
+ - Multi-step reasoning with chain-of-thought
19
+ - Complex problem decomposition and analysis
20
+ - Mathematical and logical reasoning
21
+ - Strategic planning and decision making
22
+ - Root cause analysis
23
+
24
+ Always think step by step, show your reasoning, and provide confident conclusions.
25
+ """
26
+
27
+
28
+ class ReasoningAgent(BaseAgent):
29
+ """
30
+ Specialized agent for complex reasoning, analysis, and problem-solving tasks.
31
+
32
+ Capabilities:
33
+ - Multi-step reasoning with chain-of-thought
34
+ - Complex problem decomposition
35
+ - Mathematical reasoning
36
+ - Logical analysis
37
+ - Strategic planning
38
+ """
39
+
40
+ def __init__(self, ws_manager=None, ai_router=None):
41
+ super().__init__("ReasoningAgent", ws_manager, ai_router)
42
+ self.reasoning_depth = 3
43
+ self.max_reasoning_tokens = 16000
44
+
45
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
46
+ """Execute reasoning task."""
47
+ session_id = kwargs.get("session_id", "")
48
+ task_id = kwargs.get("task_id", "")
49
+
50
+ await self.emit(task_id, "agent_start", {
51
+ "agent": "ReasoningAgent",
52
+ "task": task[:80],
53
+ }, session_id)
54
+
55
+ try:
56
+ # Step 1: Analyze the problem
57
+ analysis = await self._analyze_problem(task, context, task_id, session_id)
58
+
59
+ # Step 2: Decompose if complex
60
+ if len(task.split()) > 30:
61
+ sub_problems = await self._decompose_problem(task, analysis, task_id, session_id)
62
+ solutions = []
63
+ for sub in sub_problems[:3]:
64
+ sol = await self._solve_sub_problem(sub, context, task_id, session_id)
65
+ solutions.append(sol)
66
+ result = await self._synthesize_answer(task, analysis, sub_problems, solutions, task_id, session_id)
67
+ else:
68
+ result = analysis
69
+
70
+ await self.emit(task_id, "reasoning_complete", {
71
+ "agent": "ReasoningAgent",
72
+ "reasoning_depth": self.reasoning_depth,
73
+ }, session_id)
74
+
75
+ return result
76
+
77
+ except Exception as e:
78
+ log.error("ReasoningAgent failed", error=str(e))
79
+ return "Reasoning agent encountered an error: " + str(e)
80
+
81
+ async def _analyze_problem(self, problem: str, context: Dict, task_id: str, session_id: str) -> str:
82
+ """Analyze the problem using reasoning model."""
83
+ messages = [
84
+ {"role": "system", "content": REASONING_SYSTEM},
85
+ {"role": "user", "content": (
86
+ "Analyze this problem step by step:\n\n"
87
+ + problem + "\n\n"
88
+ "Provide:\n"
89
+ "1. Problem type and complexity\n"
90
+ "2. Key constraints and requirements\n"
91
+ "3. Step-by-step solution\n"
92
+ "4. Final answer/recommendation"
93
+ )},
94
+ ]
95
+ return await self.llm(
96
+ messages,
97
+ task_id=task_id,
98
+ session_id=session_id,
99
+ temperature=0.2,
100
+ max_tokens=self.max_reasoning_tokens,
101
+ )
102
+
103
+ async def _decompose_problem(self, problem: str, analysis: str, task_id: str, session_id: str) -> List[str]:
104
+ """Break down complex problem into sub-problems."""
105
+ messages = [
106
+ {"role": "system", "content": REASONING_SYSTEM},
107
+ {"role": "user", "content": (
108
+ "Break this complex problem into 3 specific sub-problems:\n\n"
109
+ + problem + "\n\n"
110
+ "Initial analysis: " + analysis[:500] + "\n\n"
111
+ "List each sub-problem on a new line starting with '1.', '2.', '3.'"
112
+ )},
113
+ ]
114
+ raw = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=2000)
115
+ # Parse numbered sub-problems
116
+ lines = raw.split("\n")
117
+ sub_problems = []
118
+ for line in lines:
119
+ line = line.strip()
120
+ if line and any(line.startswith(str(i) + ".") for i in range(1, 10)):
121
+ sub_problems.append(line)
122
+ return sub_problems if sub_problems else [problem]
123
+
124
+ async def _solve_sub_problem(self, sub_problem: str, context: Dict, task_id: str, session_id: str) -> str:
125
+ """Solve individual sub-problem."""
126
+ messages = [
127
+ {"role": "system", "content": REASONING_SYSTEM},
128
+ {"role": "user", "content": "Solve this specific problem:\n\n" + sub_problem},
129
+ ]
130
+ return await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.2, max_tokens=4096)
131
+
132
+ async def _synthesize_answer(
133
+ self,
134
+ original: str,
135
+ analysis: str,
136
+ sub_problems: List[str],
137
+ solutions: List[str],
138
+ task_id: str,
139
+ session_id: str,
140
+ ) -> str:
141
+ """Synthesize final answer from all reasoning steps."""
142
+ solutions_text = "\n".join(
143
+ "Sub-problem " + str(i + 1) + ": " + sol[:300]
144
+ for i, sol in enumerate(solutions)
145
+ )
146
+ messages = [
147
+ {"role": "system", "content": REASONING_SYSTEM},
148
+ {"role": "user", "content": (
149
+ "Original problem: " + original + "\n\n"
150
+ "Analysis: " + analysis[:800] + "\n\n"
151
+ "Solutions to sub-problems:\n" + solutions_text + "\n\n"
152
+ "Provide a comprehensive final answer that integrates all insights."
153
+ )},
154
+ ]
155
+ return await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=8192)
156
+
157
+ def get_status(self) -> Dict[str, Any]:
158
+ """Get agent status."""
159
+ return {
160
+ "name": self.name,
161
+ "status": "ready",
162
+ "capabilities": [
163
+ "Multi-step reasoning",
164
+ "Problem decomposition",
165
+ "Mathematical reasoning",
166
+ "Logical analysis",
167
+ "Strategic planning",
168
+ ],
169
+ "reasoning_depth": self.reasoning_depth,
170
+ }
171
+
172
+
173
+ __all__ = ["ReasoningAgent"]
agents/sandbox_agent.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SandboxAgent β€” Persistent VS Code sandbox execution (Devin-style)
3
+ Controls file system, terminal, git operations in workspace
4
+ """
5
+ import asyncio
6
+ import json
7
+ import os
8
+ import subprocess
9
+ import tempfile
10
+ from typing import Dict, List, Optional
11
+ import structlog
12
+ from .base_agent import BaseAgent
13
+
14
+ log = structlog.get_logger()
15
+
16
+ WORKSPACE = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
17
+
18
+
19
+ class SandboxAgent(BaseAgent):
20
+ def __init__(self, ws_manager=None, ai_router=None):
21
+ super().__init__("SandboxAgent", ws_manager, ai_router)
22
+ os.makedirs(WORKSPACE, exist_ok=True)
23
+
24
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
25
+ session_id = kwargs.get("session_id", "")
26
+ task_id = kwargs.get("task_id", "")
27
+
28
+ task_lower = task.lower()
29
+
30
+ if "execute" in task_lower or "run" in task_lower or "terminal" in task_lower:
31
+ cmd = context.get("command", task)
32
+ return await self.execute(cmd, task_id=task_id, session_id=session_id)
33
+ elif "write file" in task_lower or "create file" in task_lower:
34
+ filename = context.get("filename", "output.txt")
35
+ content = context.get("content", "")
36
+ return await self.write_file(filename, content, task_id=task_id, session_id=session_id)
37
+ elif "read file" in task_lower:
38
+ filename = context.get("filename", "")
39
+ return await self.read_file(filename, task_id=task_id, session_id=session_id)
40
+ elif "git" in task_lower:
41
+ return await self.git_operation(task, task_id=task_id, session_id=session_id)
42
+ else:
43
+ return await self.execute(task, task_id=task_id, session_id=session_id)
44
+
45
+ # ─── Terminal Execution ───────────────────────────────────────────────────
46
+
47
+ async def execute(
48
+ self,
49
+ command: str,
50
+ cwd: str = "",
51
+ timeout: int = 30,
52
+ task_id: str = "",
53
+ session_id: str = "",
54
+ ) -> str:
55
+ """Execute shell command in sandbox workspace."""
56
+ work_dir = cwd or WORKSPACE
57
+
58
+ # Safety: block dangerous commands
59
+ blocked = ["rm -rf /", ":(){ :|:& };:", "mkfs", "shutdown", "reboot", "halt", "dd if=/dev/"]
60
+ for b in blocked:
61
+ if b in command:
62
+ return f"❌ Blocked dangerous command: {command[:50]}"
63
+
64
+ await self.emit(task_id, "sandbox_exec", {
65
+ "command": command[:200],
66
+ "cwd": work_dir,
67
+ }, session_id)
68
+
69
+ try:
70
+ proc = await asyncio.create_subprocess_shell(
71
+ command,
72
+ stdout=asyncio.subprocess.PIPE,
73
+ stderr=asyncio.subprocess.PIPE,
74
+ cwd=work_dir,
75
+ )
76
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
77
+ output = stdout.decode("utf-8", errors="replace")
78
+ err = stderr.decode("utf-8", errors="replace")
79
+
80
+ result = output[:3000]
81
+ if err and proc.returncode != 0:
82
+ result += f"\n⚠️ stderr:\n{err[:500]}"
83
+
84
+ await self.emit(task_id, "sandbox_result", {
85
+ "command": command[:100],
86
+ "exit_code": proc.returncode,
87
+ "output_length": len(output),
88
+ "success": proc.returncode == 0,
89
+ }, session_id)
90
+
91
+ return result or f"Command executed (exit code: {proc.returncode})"
92
+ except asyncio.TimeoutError:
93
+ return f"⚠️ Command timed out after {timeout}s"
94
+ except Exception as e:
95
+ return f"❌ Execution error: {str(e)}"
96
+
97
+ # ─── File Operations ──────────────────────────────────────────────────────
98
+
99
+ async def write_file(
100
+ self,
101
+ filename: str,
102
+ content: str,
103
+ task_id: str = "",
104
+ session_id: str = "",
105
+ ) -> str:
106
+ """Write file to workspace."""
107
+ filepath = os.path.join(WORKSPACE, filename)
108
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
109
+ try:
110
+ with open(filepath, "w", encoding="utf-8") as f:
111
+ f.write(content)
112
+ await self.emit(task_id, "file_written", {
113
+ "filename": filename,
114
+ "size": len(content),
115
+ "lines": len(content.split("\n")),
116
+ "path": filepath,
117
+ }, session_id)
118
+ return f"βœ… File written: `{filename}` ({len(content)} chars, {len(content.split(chr(10)))} lines)"
119
+ except Exception as e:
120
+ return f"❌ Write failed: {str(e)}"
121
+
122
+ async def read_file(
123
+ self,
124
+ filename: str,
125
+ task_id: str = "",
126
+ session_id: str = "",
127
+ ) -> str:
128
+ """Read file from workspace."""
129
+ filepath = os.path.join(WORKSPACE, filename)
130
+ try:
131
+ with open(filepath, "r", encoding="utf-8") as f:
132
+ content = f.read()
133
+ await self.emit(task_id, "file_read", {
134
+ "filename": filename,
135
+ "size": len(content),
136
+ }, session_id)
137
+ return content[:5000]
138
+ except FileNotFoundError:
139
+ return f"❌ File not found: {filename}"
140
+ except Exception as e:
141
+ return f"❌ Read failed: {str(e)}"
142
+
143
+ async def list_files(self, path: str = "") -> List[str]:
144
+ """List files in workspace."""
145
+ target = os.path.join(WORKSPACE, path) if path else WORKSPACE
146
+ try:
147
+ result = []
148
+ for root, dirs, files in os.walk(target):
149
+ # Skip hidden and cache dirs
150
+ dirs[:] = [d for d in dirs if not d.startswith(".") and d != "__pycache__" and d != "node_modules"]
151
+ for f in files:
152
+ rel = os.path.relpath(os.path.join(root, f), WORKSPACE)
153
+ result.append(rel)
154
+ if len(result) > 100:
155
+ break
156
+ return result
157
+ except Exception:
158
+ return []
159
+
160
+ # ─── Git Operations ───────────────────────────────────────────────────────
161
+
162
+ async def git_operation(
163
+ self,
164
+ task: str,
165
+ repo_path: str = "",
166
+ task_id: str = "",
167
+ session_id: str = "",
168
+ ) -> str:
169
+ """Perform git operations in workspace."""
170
+ work_dir = repo_path or WORKSPACE
171
+ task_lower = task.lower()
172
+
173
+ if "clone" in task_lower:
174
+ # Extract URL
175
+ words = task.split()
176
+ urls = [w for w in words if "github.com" in w or "gitlab.com" in w or ".git" in w]
177
+ if urls:
178
+ url = urls[0]
179
+ return await self.execute(f"git clone {url}", cwd=WORKSPACE, task_id=task_id, session_id=session_id)
180
+ return "❌ No git URL found in task."
181
+
182
+ elif "commit" in task_lower:
183
+ msg = task.replace("commit", "").strip() or "God Agent automated commit"
184
+ cmds = [
185
+ "git add -A",
186
+ f'git commit -m "{msg}"',
187
+ ]
188
+ results = []
189
+ for cmd in cmds:
190
+ r = await self.execute(cmd, cwd=work_dir, task_id=task_id, session_id=session_id)
191
+ results.append(r)
192
+ return "\n".join(results)
193
+
194
+ elif "push" in task_lower:
195
+ return await self.execute("git push", cwd=work_dir, task_id=task_id, session_id=session_id)
196
+
197
+ elif "status" in task_lower:
198
+ return await self.execute("git status", cwd=work_dir, task_id=task_id, session_id=session_id)
199
+
200
+ elif "log" in task_lower:
201
+ return await self.execute("git log --oneline -10", cwd=work_dir, task_id=task_id, session_id=session_id)
202
+
203
+ elif "init" in task_lower:
204
+ return await self.execute("git init && git add -A", cwd=work_dir, task_id=task_id, session_id=session_id)
205
+
206
+ else:
207
+ return await self.execute(task, cwd=work_dir, task_id=task_id, session_id=session_id)
208
+
209
+ # ─── Package Management ───────────────────────────────────────────────────
210
+
211
+ async def install_packages(
212
+ self,
213
+ packages: List[str],
214
+ manager: str = "pip",
215
+ task_id: str = "",
216
+ session_id: str = "",
217
+ ) -> str:
218
+ """Install packages in workspace."""
219
+ pkg_str = " ".join(packages)
220
+ if manager == "pip":
221
+ cmd = f"pip install {pkg_str}"
222
+ elif manager == "npm":
223
+ cmd = f"npm install {pkg_str}"
224
+ elif manager == "pnpm":
225
+ cmd = f"pnpm add {pkg_str}"
226
+ else:
227
+ cmd = f"{manager} install {pkg_str}"
228
+
229
+ await self.emit(task_id, "installing_packages", {
230
+ "manager": manager,
231
+ "packages": packages,
232
+ }, session_id)
233
+ return await self.execute(cmd, task_id=task_id, session_id=session_id)
234
+
235
+ # ─── Workspace Info ───────────────────────────────────────────────────────
236
+
237
+ async def get_workspace_info(self) -> Dict:
238
+ """Get workspace status."""
239
+ files = await self.list_files()
240
+ try:
241
+ disk = await self.execute("df -h /tmp | tail -1")
242
+ except Exception:
243
+ disk = "N/A"
244
+ return {
245
+ "path": WORKSPACE,
246
+ "file_count": len(files),
247
+ "files": files[:20],
248
+ "disk_usage": disk,
249
+ }
agents/test_agent.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ TestAgent v7 β€” Autonomous test generation, execution, and quality assurance
3
+ Like Devin's self-testing loop + Genspark's QA automation
4
+ """
5
+ import asyncio
6
+ import json
7
+ import os
8
+ import re
9
+ from typing import Dict, List
10
+ import structlog
11
+ from .base_agent import BaseAgent
12
+
13
+ log = structlog.get_logger()
14
+
15
+ TEST_SYSTEM = """You are an elite autonomous test engineer.
16
+ You generate comprehensive tests: unit, integration, e2e, performance.
17
+ You analyze code for bugs, edge cases, and security vulnerabilities.
18
+ You write pytest (Python) and Jest/Vitest (TypeScript) tests.
19
+ Always aim for 80%+ test coverage and meaningful assertions.
20
+ """
21
+
22
+
23
+ class TestAgent(BaseAgent):
24
+ def __init__(self, ws_manager=None, ai_router=None):
25
+ super().__init__("TestAgent", ws_manager, ai_router)
26
+ self.workspace = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
27
+
28
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
29
+ session_id = kwargs.get("session_id", "")
30
+ task_id = kwargs.get("task_id", "")
31
+ await self.emit(task_id, "agent_start", {"agent": "TestAgent", "task": task[:80]}, session_id)
32
+
33
+ t = task.lower()
34
+ if any(k in t for k in ["generate test", "write test", "create test"]):
35
+ return await self._generate_tests(task, context, task_id, session_id)
36
+ if any(k in t for k in ["run test", "execute test", "pytest", "jest"]):
37
+ return await self._run_tests(task, context, task_id, session_id)
38
+ if any(k in t for k in ["coverage", "quality", "audit"]):
39
+ return await self._quality_audit(task, context, task_id, session_id)
40
+ return await self._generate_tests(task, context, task_id, session_id)
41
+
42
+ async def _generate_tests(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
43
+ code = context.get("code", "")
44
+ language = context.get("language", "python")
45
+ await self.emit(task_id, "tool_called", {
46
+ "agent": "TestAgent", "tool": "generate_tests", "step": "Generating tests"
47
+ }, session_id)
48
+ msgs = [
49
+ {"role": "system", "content": TEST_SYSTEM},
50
+ {"role": "user", "content": (
51
+ f"Task: {task}\nLanguage: {language}\n\n"
52
+ f"Code to test:\n{code[:3000] if code else 'Generate tests for: ' + task}\n\n"
53
+ "Generate comprehensive tests with:\n"
54
+ "1. Happy path tests\n2. Edge case tests\n3. Error handling tests\n"
55
+ "4. Mocks for external dependencies\n5. Clear test descriptions"
56
+ )},
57
+ ]
58
+ result = await self.llm(msgs, task_id=task_id, session_id=session_id, temperature=0.2, max_tokens=8192)
59
+ # Save test file to workspace
60
+ test_filename = f"test_{re.sub(r'[^a-z0-9]', '_', task.lower()[:30])}.py"
61
+ test_path = os.path.join(self.workspace, "tests", test_filename)
62
+ os.makedirs(os.path.dirname(test_path), exist_ok=True)
63
+ code_blocks = re.findall(r'```(?:python|py)?\n(.*?)```', result, re.DOTALL)
64
+ if code_blocks:
65
+ with open(test_path, "w") as f:
66
+ f.write(code_blocks[0])
67
+ await self.emit(task_id, "file_written", {"path": test_path}, session_id)
68
+ return result
69
+
70
+ async def _run_tests(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
71
+ repo_path = context.get("repo_path", self.workspace)
72
+ await self.emit(task_id, "tool_called", {
73
+ "agent": "TestAgent", "tool": "run_tests", "step": "Executing tests"
74
+ }, session_id)
75
+ # Detect test runner
76
+ if os.path.exists(os.path.join(repo_path, "package.json")):
77
+ cmd = ["npm", "test", "--", "--watchAll=false"]
78
+ else:
79
+ cmd = ["python", "-m", "pytest", "-v", "--tb=short"]
80
+ try:
81
+ proc = await asyncio.create_subprocess_exec(
82
+ *cmd, cwd=repo_path,
83
+ stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
84
+ )
85
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=120)
86
+ output = stdout.decode() + stderr.decode()
87
+ passed = len(re.findall(r'PASSED|βœ“|pass', output, re.I))
88
+ failed = len(re.findall(r'FAILED|βœ—|fail', output, re.I))
89
+ await self.emit(task_id, "tests_complete", {"passed": passed, "failed": failed}, session_id)
90
+ return f"**Test Results:** βœ… {passed} passed | ❌ {failed} failed\n\n```\n{output[:3000]}\n```"
91
+ except Exception as e:
92
+ return f"❌ Test run error: {str(e)}"
93
+
94
+ async def _quality_audit(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
95
+ code = context.get("code", "")
96
+ msgs = [
97
+ {"role": "system", "content": TEST_SYSTEM},
98
+ {"role": "user", "content": (
99
+ f"Task: {task}\n\nCode:\n{code[:3000]}\n\n"
100
+ "Provide quality audit: coverage estimate, complexity score, bugs found, security issues, and recommendations."
101
+ )},
102
+ ]
103
+ return await self.llm(msgs, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=4096)
agents/ui_agent.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ UIAgent β€” UI component generation (React/Next.js/Tailwind)
3
+ """
4
+ from typing import Dict
5
+ import structlog
6
+ from .base_agent import BaseAgent
7
+
8
+ log = structlog.get_logger()
9
+
10
+ UI_SYSTEM = """You are an expert React/Next.js/Tailwind CSS UI engineer.
11
+ You generate beautiful, responsive, production-ready UI components.
12
+ Always use:
13
+ - TypeScript with proper types
14
+ - Tailwind CSS for styling
15
+ - shadcn/ui components when appropriate
16
+ - Framer Motion for animations
17
+ - Lucide React for icons
18
+ - Dark mode support
19
+ - Mobile-first responsive design
20
+ - Accessibility (aria labels, semantic HTML)
21
+ """
22
+
23
+
24
+ class UIAgent(BaseAgent):
25
+ def __init__(self, ws_manager=None, ai_router=None):
26
+ super().__init__("UIAgent", ws_manager, ai_router)
27
+
28
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
29
+ session_id = kwargs.get("session_id", "")
30
+ task_id = kwargs.get("task_id", "")
31
+
32
+ await self.emit(task_id, "agent_start", {"agent": "UIAgent", "task": task[:80]}, session_id)
33
+
34
+ messages = [
35
+ {"role": "system", "content": UI_SYSTEM},
36
+ {"role": "user", "content": (
37
+ f"Generate a complete, production-ready UI component for: {task}\n\n"
38
+ f"Requirements:\n"
39
+ f"- TypeScript\n- Tailwind CSS\n- Dark mode\n- Mobile responsive\n"
40
+ f"- Include all imports\n- Export as default"
41
+ )},
42
+ ]
43
+ result = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=6000)
44
+ await self.emit(task_id, "ui_generated", {"agent": "UIAgent"}, session_id)
45
+ return result
agents/vision_agent.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ VisionAgent v7 β€” UI/UX generation, design-to-code, screenshot analysis
3
+ Converts designs, wireframes, and visual specs into real code
4
+ """
5
+ import json
6
+ import os
7
+ from typing import Dict
8
+ import structlog
9
+ from .base_agent import BaseAgent
10
+
11
+ log = structlog.get_logger()
12
+
13
+ VISION_SYSTEM = """You are an elite UI/UX engineer and design-to-code specialist.
14
+ You can:
15
+ - Convert design mockups and wireframes into pixel-perfect React/Next.js components
16
+ - Generate beautiful, responsive UI components with Tailwind CSS
17
+ - Create complete page layouts with animations (Framer Motion)
18
+ - Analyze screenshots and reproduce UI components
19
+ - Generate design systems, color palettes, and typography scales
20
+ - Build dashboards, landing pages, admin panels, mobile-first UIs
21
+
22
+ Always produce production-ready code with:
23
+ - Responsive design (mobile-first)
24
+ - Accessibility (ARIA labels, semantic HTML)
25
+ - Dark/light mode support
26
+ - Smooth animations and transitions
27
+ - TypeScript types
28
+ """
29
+
30
+
31
+ class VisionAgent(BaseAgent):
32
+ def __init__(self, ws_manager=None, ai_router=None):
33
+ super().__init__("VisionAgent", ws_manager, ai_router)
34
+ self.workspace = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
35
+
36
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
37
+ session_id = kwargs.get("session_id", "")
38
+ task_id = kwargs.get("task_id", "")
39
+ await self.emit(task_id, "agent_start", {"agent": "VisionAgent", "task": task[:80]}, session_id)
40
+
41
+ t = task.lower()
42
+ if any(k in t for k in ["dashboard", "admin", "panel"]):
43
+ return await self._generate_dashboard(task, context, task_id, session_id)
44
+ if any(k in t for k in ["landing", "homepage", "hero"]):
45
+ return await self._generate_landing_page(task, context, task_id, session_id)
46
+ if any(k in t for k in ["component", "button", "card", "form", "modal", "nav"]):
47
+ return await self._generate_component(task, context, task_id, session_id)
48
+ return await self._generate_ui(task, context, task_id, session_id)
49
+
50
+ async def _generate_dashboard(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
51
+ await self.emit(task_id, "tool_called", {
52
+ "agent": "VisionAgent", "tool": "generate_dashboard", "step": "Generating dashboard UI"
53
+ }, session_id)
54
+ msgs = [
55
+ {"role": "system", "content": VISION_SYSTEM},
56
+ {"role": "user", "content": (
57
+ f"Build a complete dashboard UI: {task}\n\n"
58
+ "Requirements:\n"
59
+ "- Dark theme with glass morphism effects\n"
60
+ "- Sidebar navigation with icons\n"
61
+ "- Header with user menu\n"
62
+ "- Stats cards with trends\n"
63
+ "- Data tables with sorting\n"
64
+ "- Charts/graphs area\n"
65
+ "- React + TypeScript + Tailwind CSS\n"
66
+ "Generate the complete component code."
67
+ )},
68
+ ]
69
+ return await self.llm(msgs, task_id=task_id, session_id=session_id, temperature=0.4, max_tokens=8192)
70
+
71
+ async def _generate_landing_page(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
72
+ await self.emit(task_id, "tool_called", {
73
+ "agent": "VisionAgent", "tool": "generate_landing", "step": "Generating landing page"
74
+ }, session_id)
75
+ msgs = [
76
+ {"role": "system", "content": VISION_SYSTEM},
77
+ {"role": "user", "content": (
78
+ f"Create a stunning landing page: {task}\n\n"
79
+ "Include: Hero section, Features grid, Pricing table, Testimonials, CTA, Footer\n"
80
+ "Style: Modern, gradient backgrounds, glassmorphism, smooth animations\n"
81
+ "Tech: Next.js + TypeScript + Tailwind + Framer Motion"
82
+ )},
83
+ ]
84
+ return await self.llm(msgs, task_id=task_id, session_id=session_id, temperature=0.5, max_tokens=8192)
85
+
86
+ async def _generate_component(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
87
+ await self.emit(task_id, "tool_called", {
88
+ "agent": "VisionAgent", "tool": "generate_component", "step": "Generating UI component"
89
+ }, session_id)
90
+ msgs = [
91
+ {"role": "system", "content": VISION_SYSTEM},
92
+ {"role": "user", "content": (
93
+ f"Create a polished UI component: {task}\n\n"
94
+ "Requirements:\n"
95
+ "- Fully typed with TypeScript\n"
96
+ "- Responsive and accessible\n"
97
+ "- Dark/light mode support\n"
98
+ "- Smooth hover/focus animations\n"
99
+ "- Props with sensible defaults"
100
+ )},
101
+ ]
102
+ return await self.llm(msgs, task_id=task_id, session_id=session_id, temperature=0.4, max_tokens=8192)
103
+
104
+ async def _generate_ui(self, task: str, context: Dict, task_id: str, session_id: str) -> str:
105
+ msgs = [
106
+ {"role": "system", "content": VISION_SYSTEM},
107
+ {"role": "user", "content": f"Generate UI for: {task}\n\nContext: {json.dumps(context)[:300]}"},
108
+ ]
109
+ return await self.llm(msgs, task_id=task_id, session_id=session_id, temperature=0.5, max_tokens=8192)
agents/workflow_agent.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ WorkflowAgent β€” n8n workflow generation, validation, deployment (Phase 7)
3
+ Workflow Factor OS merged into God Agent ecosystem
4
+ """
5
+ import json
6
+ from typing import Dict, List
7
+ import structlog
8
+ from .base_agent import BaseAgent
9
+
10
+ log = structlog.get_logger()
11
+
12
+ WORKFLOW_SYSTEM = """You are an expert n8n workflow architect and automation engineer.
13
+ You design production-grade automation workflows for:
14
+ - Telegram/Discord bots with AI responses
15
+ - Data pipelines and ETL processes
16
+ - API integrations and webhooks
17
+ - Scheduled tasks and cron jobs
18
+ - Email/Slack/notification systems
19
+ - GitHub CI/CD triggers
20
+ - AI-powered automation chains
21
+
22
+ Generate valid n8n workflow JSON that can be directly imported.
23
+ Think about error handling, retry logic, and edge cases.
24
+ """
25
+
26
+
27
+ class WorkflowAgent(BaseAgent):
28
+ def __init__(self, ws_manager=None, ai_router=None):
29
+ super().__init__("WorkflowAgent", ws_manager, ai_router)
30
+
31
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
32
+ session_id = kwargs.get("session_id", "")
33
+ task_id = kwargs.get("task_id", "")
34
+
35
+ await self.emit(task_id, "agent_start", {
36
+ "agent": "WorkflowAgent",
37
+ "task": task[:80],
38
+ }, session_id)
39
+
40
+ task_lower = task.lower()
41
+
42
+ if "telegram" in task_lower:
43
+ return await self._telegram_workflow(task, task_id, session_id)
44
+ elif "discord" in task_lower:
45
+ return await self._discord_workflow(task, task_id, session_id)
46
+ elif "schedule" in task_lower or "cron" in task_lower:
47
+ return await self._scheduled_workflow(task, task_id, session_id)
48
+ elif "github" in task_lower and ("webhook" in task_lower or "trigger" in task_lower):
49
+ return await self._github_workflow(task, task_id, session_id)
50
+ else:
51
+ return await self._generic_workflow(task, task_id, session_id)
52
+
53
+ async def _telegram_workflow(self, task: str, task_id: str, session_id: str) -> str:
54
+ messages = [
55
+ {"role": "system", "content": WORKFLOW_SYSTEM},
56
+ {"role": "user", "content": (
57
+ f"Create a complete n8n workflow for: {task}\n\n"
58
+ f"Include:\n"
59
+ f"1. Telegram Trigger node (webhook)\n"
60
+ f"2. AI processing node (HTTP Request to API)\n"
61
+ f"3. Telegram Send Message node\n"
62
+ f"4. Error handling branch\n\n"
63
+ f"Also provide:\n"
64
+ f"- Setup instructions\n"
65
+ f"- Required environment variables\n"
66
+ f"- Testing steps\n"
67
+ f"- The n8n workflow JSON"
68
+ )},
69
+ ]
70
+ result = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.2, max_tokens=6000)
71
+ await self.emit(task_id, "workflow_generated", {
72
+ "type": "telegram_bot",
73
+ "agent": "WorkflowAgent",
74
+ }, session_id)
75
+ return result
76
+
77
+ async def _discord_workflow(self, task: str, task_id: str, session_id: str) -> str:
78
+ messages = [
79
+ {"role": "system", "content": WORKFLOW_SYSTEM},
80
+ {"role": "user", "content": (
81
+ f"Create a complete n8n workflow for Discord automation: {task}\n\n"
82
+ f"Include Discord webhook integration, message processing, and response handling."
83
+ )},
84
+ ]
85
+ result = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.2, max_tokens=5000)
86
+ await self.emit(task_id, "workflow_generated", {"type": "discord", "agent": "WorkflowAgent"}, session_id)
87
+ return result
88
+
89
+ async def _scheduled_workflow(self, task: str, task_id: str, session_id: str) -> str:
90
+ messages = [
91
+ {"role": "system", "content": WORKFLOW_SYSTEM},
92
+ {"role": "user", "content": (
93
+ f"Create a scheduled n8n workflow for: {task}\n\n"
94
+ f"Include cron trigger configuration, processing steps, and notification on completion/failure."
95
+ )},
96
+ ]
97
+ result = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.2)
98
+ await self.emit(task_id, "workflow_generated", {"type": "scheduled", "agent": "WorkflowAgent"}, session_id)
99
+ return result
100
+
101
+ async def _github_workflow(self, task: str, task_id: str, session_id: str) -> str:
102
+ messages = [
103
+ {"role": "system", "content": WORKFLOW_SYSTEM},
104
+ {"role": "user", "content": (
105
+ f"Create a GitHub webhook n8n workflow for: {task}\n\n"
106
+ f"Include GitHub webhook trigger, event processing, and automated responses/actions."
107
+ )},
108
+ ]
109
+ result = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.2)
110
+ await self.emit(task_id, "workflow_generated", {"type": "github_webhook", "agent": "WorkflowAgent"}, session_id)
111
+ return result
112
+
113
+ async def _generic_workflow(self, task: str, task_id: str, session_id: str) -> str:
114
+ messages = [
115
+ {"role": "system", "content": WORKFLOW_SYSTEM},
116
+ {"role": "user", "content": (
117
+ f"Design a complete automation workflow for: {task}\n\n"
118
+ f"Provide:\n"
119
+ f"1. Workflow architecture diagram (text)\n"
120
+ f"2. n8n node configuration\n"
121
+ f"3. Step-by-step setup guide\n"
122
+ f"4. Import-ready n8n JSON\n"
123
+ f"5. Testing checklist"
124
+ )},
125
+ ]
126
+ result = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=6000)
127
+ await self.emit(task_id, "workflow_generated", {"type": "custom", "agent": "WorkflowAgent"}, session_id)
128
+ return result
129
+
130
+ async def validate_workflow(self, workflow_json: str) -> Dict:
131
+ """Validate n8n workflow JSON structure."""
132
+ try:
133
+ workflow = json.loads(workflow_json)
134
+ nodes = workflow.get("nodes", [])
135
+ connections = workflow.get("connections", {})
136
+ has_trigger = any(
137
+ n.get("type", "").startswith("n8n-nodes-base.") and
138
+ ("Trigger" in n.get("type", "") or n.get("type", "").endswith("trigger"))
139
+ for n in nodes
140
+ )
141
+ return {
142
+ "valid": True,
143
+ "node_count": len(nodes),
144
+ "has_trigger": has_trigger,
145
+ "connection_count": len(connections),
146
+ }
147
+ except json.JSONDecodeError as e:
148
+ return {"valid": False, "error": f"Invalid JSON: {e}"}
149
+ except Exception as e:
150
+ return {"valid": False, "error": str(e)}
151
+
152
+ async def simulate_workflow(self, workflow_json: str, test_data: Dict = {}) -> str:
153
+ """Simulate workflow execution with test data."""
154
+ messages = [
155
+ {"role": "system", "content": WORKFLOW_SYSTEM},
156
+ {"role": "user", "content": (
157
+ f"Simulate this n8n workflow execution with test data:\n\n"
158
+ f"Workflow: {workflow_json[:1000]}\n\n"
159
+ f"Test data: {json.dumps(test_data)}\n\n"
160
+ f"Walk through each node's execution and expected output."
161
+ )},
162
+ ]
163
+ return await self.llm(messages, temperature=0.3)
ai_router/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # Multi-Model AI Router β€” GOD AGENT OS v8
2
+ # Primary: Gemini β†’ Sambanova β†’ GitHub Models (task-aware rotation)
3
+ from .router import AIRouter
4
+ from .router_v8 import GodModeRouter, get_router, classify_task, get_provider_order
5
+
6
+ __all__ = ["AIRouter", "GodModeRouter", "get_router", "classify_task", "get_provider_order"]
ai_router/key_pool.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ KeyPool β€” Multi-API Key Manager with Failover & Cooldown
3
+ God Agent OS v8 β€” Supports Gemini, SambaNova, GitHub, OpenAI, Groq, etc.
4
+ Each provider can have multiple comma-separated keys.
5
+ """
6
+
7
+ import asyncio
8
+ import time
9
+ from collections import defaultdict
10
+ from typing import Dict, List, Optional
11
+ import structlog
12
+
13
+ log = structlog.get_logger()
14
+
15
+ COOLDOWN_SECONDS = 60 # Key cooling time after max failures
16
+ MAX_FAILURES = 3 # Max fails before cooldown
17
+
18
+
19
+ class KeyEntry:
20
+ def __init__(self, key: str):
21
+ self.key = key
22
+ self.failures = 0
23
+ self.cooldown_until = 0.0
24
+ self.calls = 0
25
+ self.last_used = 0.0
26
+
27
+ def is_available(self) -> bool:
28
+ if self.cooldown_until > time.time():
29
+ return False
30
+ return True
31
+
32
+ def mark_fail(self):
33
+ self.failures += 1
34
+ if self.failures >= MAX_FAILURES:
35
+ self.cooldown_until = time.time() + COOLDOWN_SECONDS
36
+ log.warning("Key cooldown activated", failures=self.failures)
37
+
38
+ def mark_success(self):
39
+ self.failures = max(0, self.failures - 1)
40
+ self.cooldown_until = 0.0
41
+ self.calls += 1
42
+ self.last_used = time.time()
43
+
44
+ def status(self) -> dict:
45
+ remaining = max(0, self.cooldown_until - time.time())
46
+ return {
47
+ "key_preview": self.key[:8] + "..." + self.key[-4:] if len(self.key) > 12 else "***",
48
+ "available": self.is_available(),
49
+ "failures": self.failures,
50
+ "calls": self.calls,
51
+ "cooldown_remaining_s": round(remaining, 1),
52
+ }
53
+
54
+
55
+ class KeyPool:
56
+ """
57
+ Pool of API keys for a single provider.
58
+ Round-robins through available keys with failure tracking.
59
+ """
60
+
61
+ def __init__(self, provider: str, keys: List[str]):
62
+ self.provider = provider
63
+ self._keys: List[KeyEntry] = [KeyEntry(k.strip()) for k in keys if k.strip()]
64
+ self._index = 0
65
+
66
+ def __len__(self) -> int:
67
+ return len(self._keys)
68
+
69
+ def pick(self) -> Optional[str]:
70
+ """Pick the next available key (round-robin, skip cooling down keys)."""
71
+ if not self._keys:
72
+ return None
73
+ n = len(self._keys)
74
+ for _ in range(n):
75
+ entry = self._keys[self._index % n]
76
+ self._index = (self._index + 1) % n
77
+ if entry.is_available():
78
+ return entry.key
79
+ # All keys in cooldown β€” try the one with shortest cooldown
80
+ soonest = min(self._keys, key=lambda e: e.cooldown_until)
81
+ log.warning(
82
+ "All keys in cooldown, using soonest",
83
+ provider=self.provider,
84
+ cooldown_remaining=round(soonest.cooldown_until - time.time(), 1),
85
+ )
86
+ return soonest.key
87
+
88
+ def mark_fail(self, key: str):
89
+ for e in self._keys:
90
+ if e.key == key:
91
+ e.mark_fail()
92
+ return
93
+
94
+ def mark_success(self, key: str):
95
+ for e in self._keys:
96
+ if e.key == key:
97
+ e.mark_success()
98
+ return
99
+
100
+ def available_count(self) -> int:
101
+ return sum(1 for e in self._keys if e.is_available())
102
+
103
+ def status(self) -> dict:
104
+ return {
105
+ "provider": self.provider,
106
+ "total_keys": len(self._keys),
107
+ "available_keys": self.available_count(),
108
+ "keys": [e.status() for e in self._keys],
109
+ }
110
+
111
+
112
+ # ─── Global Key Pool Registry ─────────────────────────────────────────────────
113
+
114
+ class KeyPoolRegistry:
115
+ """Central registry for all provider key pools."""
116
+
117
+ def __init__(self):
118
+ self._pools: Dict[str, KeyPool] = {}
119
+
120
+ def register(self, provider: str, keys_csv: str):
121
+ """Register comma-separated keys for a provider."""
122
+ keys = [k.strip() for k in keys_csv.split(",") if k.strip()]
123
+ if keys:
124
+ self._pools[provider] = KeyPool(provider, keys)
125
+ log.info("KeyPool registered", provider=provider, count=len(keys))
126
+
127
+ def get(self, provider: str) -> Optional[KeyPool]:
128
+ return self._pools.get(provider)
129
+
130
+ def all_status(self) -> dict:
131
+ return {name: pool.status() for name, pool in self._pools.items()}
ai_router/router.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Multi-Model AI Router β€” Phase 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
+
86
+ async def complete(
87
+ self,
88
+ messages: List[Dict],
89
+ task_id: str = "",
90
+ session_id: str = "",
91
+ temperature: float = 0.7,
92
+ max_tokens: int = 4096,
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,
139
+ "stream": True,
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:
176
+ if m["role"] == "system":
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():
196
+ if not line.startswith("data:"):
197
+ continue
198
+ try:
199
+ data = json.loads(line[5:].strip())
200
+ if data.get("type") == "content_block_delta":
201
+ delta = data["delta"].get("text", "")
202
+ if delta:
203
+ full_text += delta
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
+
241
+ async def _emit_chunk(self, chunk: str, task_id: str, session_id: str, demo: bool = False):
242
+ if not self.ws:
243
+ return
244
+ payload = {"chunk": chunk, "demo": demo}
245
+ if task_id:
246
+ await self.ws.emit(task_id, "llm_chunk", payload, session_id=session_id)
247
+ if session_id and not task_id:
248
+ await self.ws.emit_chat(session_id, "llm_chunk", payload)
249
+
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
ai_router/router_v10.py ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GOD AGENT OS β€” Multi-Provider AI Router v10
3
+ Primary: Gemini (6 keys) β†’ SambaNova (9 keys) β†’ GitHub Models (9 keys)
4
+ Fallback: Groq β†’ OpenAI β†’ Demo
5
+ Auto-failover, round-robin KeyPool, task-type routing.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import time
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
+ PROVIDERS: Dict[str, Dict[str, Any]] = {
21
+ "gemini": {
22
+ "name": "gemini",
23
+ "type": "gemini",
24
+ "base_url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent",
25
+ "key_env": "GEMINI_KEY",
26
+ "default_model": "gemini-2.0-flash",
27
+ "max_tokens": 8192,
28
+ "priority_tasks": ["language", "research", "content", "general", "analysis"],
29
+ },
30
+ "sambanova": {
31
+ "name": "sambanova",
32
+ "type": "openai",
33
+ "base_url": "https://api.sambanova.ai/v1",
34
+ "key_env": "SAMBANOVA_KEY",
35
+ "default_model": "Meta-Llama-3.3-70B-Instruct",
36
+ "max_tokens": 8192,
37
+ "priority_tasks": ["reasoning", "engineering", "planning", "analysis"],
38
+ },
39
+ "github": {
40
+ "name": "github",
41
+ "type": "openai",
42
+ "base_url": "https://models.inference.ai.azure.com",
43
+ "key_env": "GITHUB_KEY",
44
+ "default_model": "gpt-4o",
45
+ "max_tokens": 4096,
46
+ "priority_tasks": ["planning", "engineering", "general"],
47
+ },
48
+ "groq": {
49
+ "name": "groq",
50
+ "type": "openai",
51
+ "base_url": "https://api.groq.com/openai/v1",
52
+ "key_env": "GROQ_API_KEY",
53
+ "default_model": "llama-3.3-70b-versatile",
54
+ "max_tokens": 8192,
55
+ "priority_tasks": ["general"],
56
+ },
57
+ "openai": {
58
+ "name": "openai",
59
+ "type": "openai",
60
+ "base_url": "https://api.openai.com/v1",
61
+ "key_env": "OPENAI_API_KEY",
62
+ "default_model": "gpt-4o",
63
+ "max_tokens": 4096,
64
+ "priority_tasks": ["general"],
65
+ },
66
+ }
67
+
68
+ PRIMARY_ORDER = ["gemini", "sambanova", "github"]
69
+ FALLBACK_ORDER = ["groq", "openai"]
70
+ KEY_COOLDOWN_SECONDS = 300
71
+ KEY_MAX_FAILS = 3
72
+ MAX_RETRIES_PER_KEY = 2
73
+
74
+
75
+ # ─── KeyPool ──────────────────────────────────────────────────────────────────
76
+
77
+ class KeyPool:
78
+ """Round-robin key pool with failure tracking and cooldown."""
79
+
80
+ def __init__(self, provider: str, raw_keys: str):
81
+ self.provider = provider
82
+ self._keys: List[Dict[str, Any]] = []
83
+ for key in raw_keys.split(","):
84
+ key = key.strip()
85
+ if key:
86
+ self._keys.append({
87
+ "key": key,
88
+ "fails": 0,
89
+ "cooldown_until": 0.0,
90
+ "calls": 0,
91
+ })
92
+ log.info("key_pool_init", provider=provider, keys=len(self._keys))
93
+
94
+ def pick(self) -> Optional[Dict[str, Any]]:
95
+ now = time.time()
96
+ available = [item for item in self._keys if item["cooldown_until"] < now]
97
+ if not available:
98
+ # All in cooldown β€” return soonest
99
+ if self._keys:
100
+ soonest = min(self._keys, key=lambda x: x["cooldown_until"])
101
+ log.warning("all_keys_cooldown", provider=self.provider,
102
+ wait_s=round(soonest["cooldown_until"] - now, 1))
103
+ return soonest
104
+ return None
105
+ available.sort(key=lambda item: item["fails"])
106
+ return available[0]
107
+
108
+ def mark_fail(self, key_obj: Dict[str, Any]):
109
+ key_obj["fails"] += 1
110
+ if key_obj["fails"] >= KEY_MAX_FAILS:
111
+ key_obj["cooldown_until"] = time.time() + KEY_COOLDOWN_SECONDS
112
+ log.warning("key_cooled_down", provider=self.provider,
113
+ key_prefix=key_obj["key"][:8])
114
+
115
+ def mark_success(self, key_obj: Dict[str, Any]):
116
+ key_obj["fails"] = 0
117
+ key_obj["cooldown_until"] = 0.0
118
+ key_obj["calls"] += 1
119
+
120
+ def has_keys(self) -> bool:
121
+ return bool(self._keys)
122
+
123
+ def count(self) -> int:
124
+ return len(self._keys)
125
+
126
+ def available_count(self) -> int:
127
+ now = time.time()
128
+ return sum(1 for k in self._keys if k["cooldown_until"] < now)
129
+
130
+ def status(self) -> Dict[str, Any]:
131
+ now = time.time()
132
+ return {
133
+ "provider": self.provider,
134
+ "total_keys": len(self._keys),
135
+ "available_keys": self.available_count(),
136
+ "keys": [
137
+ {
138
+ "key_prefix": k["key"][:8] + "...",
139
+ "fails": k["fails"],
140
+ "calls": k["calls"],
141
+ "available": k["cooldown_until"] < now,
142
+ "cooldown_s": max(0, round(k["cooldown_until"] - now, 1)),
143
+ }
144
+ for k in self._keys
145
+ ],
146
+ }
147
+
148
+
149
+ # ─── Task Classifier ──────────────────────────────────────────────────────────
150
+
151
+ def classify_task(prompt: str = "") -> str:
152
+ p = (prompt or "").lower()
153
+ if any(w in p for w in ["code", "function", "implement", "build", "develop", "api", "class", "debug", "script", "program"]):
154
+ return "engineering"
155
+ if any(w in p for w in ["plan", "strategy", "workflow", "json", "automate", "pipeline", "step", "task"]):
156
+ return "planning"
157
+ if any(w in p for w in ["analyze", "reasoning", "why", "explain", "evaluate", "compare", "think"]):
158
+ return "reasoning"
159
+ if any(w in p for w in ["research", "find", "search", "discover", "investigate", "browse", "web"]):
160
+ return "research"
161
+ if any(w in p for w in ["write", "content", "blog", "article", "copy", "generate text", "summarize", "essay"]):
162
+ return "content"
163
+ if any(w in p for w in ["translate", "language", "convert", "myanmar", "burmese"]):
164
+ return "language"
165
+ if any(w in p for w in ["data", "csv", "metrics", "report", "insight", "chart", "graph"]):
166
+ return "analysis"
167
+ return "general"
168
+
169
+
170
+ def get_provider_order(task_type: str, preferred: str = "") -> List[str]:
171
+ ordered = sorted(
172
+ PRIMARY_ORDER,
173
+ key=lambda p: 0 if task_type in PROVIDERS[p]["priority_tasks"] else 1
174
+ )
175
+ result = ordered + [p for p in FALLBACK_ORDER if os.environ.get(PROVIDERS[p]["key_env"], "")]
176
+ if preferred and preferred in PROVIDERS and preferred in result:
177
+ result = [preferred] + [p for p in result if p != preferred]
178
+ return result
179
+
180
+
181
+ # ─── API Calls ────────────────────────────────────────────────────────────────
182
+
183
+ async def call_gemini(
184
+ base_url: str, key: str,
185
+ messages: List[Dict[str, str]], max_tokens: int
186
+ ) -> Tuple[bool, str]:
187
+ url = f"{base_url}?key={key}"
188
+ # Build contents from messages
189
+ contents = []
190
+ system_text = ""
191
+ for msg in messages:
192
+ role = msg.get("role", "user")
193
+ content = msg.get("content", "")
194
+ if role == "system":
195
+ system_text = content
196
+ elif role == "user":
197
+ contents.append({"role": "user", "parts": [{"text": content}]})
198
+ elif role == "assistant":
199
+ contents.append({"role": "model", "parts": [{"text": content}]})
200
+
201
+ if not contents:
202
+ contents = [{"role": "user", "parts": [{"text": "Hello"}]}]
203
+
204
+ body: Dict[str, Any] = {
205
+ "contents": contents,
206
+ "generationConfig": {
207
+ "maxOutputTokens": min(max_tokens, 8192),
208
+ "temperature": 0.7,
209
+ },
210
+ }
211
+ if system_text:
212
+ body["systemInstruction"] = {"parts": [{"text": system_text}]}
213
+
214
+ try:
215
+ async with httpx.AsyncClient(timeout=60.0) as client:
216
+ resp = await client.post(url, json=body)
217
+ if resp.status_code == 200:
218
+ data = resp.json()
219
+ candidates = data.get("candidates", [])
220
+ if candidates:
221
+ text = candidates[0].get("content", {}).get("parts", [{}])[0].get("text", "")
222
+ if text:
223
+ return True, text
224
+ return False, f"Empty response: {str(data)[:200]}"
225
+ return False, f"HTTP {resp.status_code}: {resp.text[:300]}"
226
+ except Exception as exc:
227
+ return False, str(exc)
228
+
229
+
230
+ async def call_openai_compat(
231
+ base_url: str, key: str, model: str,
232
+ messages: List[Dict[str, str]], max_tokens: int
233
+ ) -> Tuple[bool, str]:
234
+ url = f"{base_url}/chat/completions"
235
+ headers = {
236
+ "Authorization": f"Bearer {key}",
237
+ "Content-Type": "application/json",
238
+ }
239
+ body = {
240
+ "model": model,
241
+ "messages": messages,
242
+ "max_tokens": max_tokens,
243
+ "temperature": 0.7,
244
+ }
245
+ try:
246
+ async with httpx.AsyncClient(timeout=60.0) as client:
247
+ resp = await client.post(url, json=body, headers=headers)
248
+ if resp.status_code == 200:
249
+ data = resp.json()
250
+ choices = data.get("choices", [])
251
+ if choices:
252
+ content = choices[0].get("message", {}).get("content", "")
253
+ if content:
254
+ return True, content
255
+ return False, f"Empty choices: {str(data)[:200]}"
256
+ return False, f"HTTP {resp.status_code}: {resp.text[:300]}"
257
+ except Exception as exc:
258
+ return False, str(exc)
259
+
260
+
261
+ # ─── Main Router ──────────────────────────────────────────────────────────────
262
+
263
+ class AIRouterV10:
264
+ """
265
+ God Agent OS AI Router v10.
266
+ Multi-provider routing: Gemini (6 keys) β†’ SambaNova (9 keys) β†’ GitHub (9 keys) β†’ fallback.
267
+ """
268
+
269
+ VERSION = "10.0"
270
+
271
+ def __init__(self, ws_manager=None):
272
+ self.ws = ws_manager
273
+ self._pools: Dict[str, KeyPool] = {}
274
+ self._stats: Dict[str, Dict[str, Any]] = {
275
+ name: {"calls": 0, "errors": 0, "latency_ms": [], "last_used": 0.0}
276
+ for name in PROVIDERS
277
+ }
278
+ self._load_pools()
279
+
280
+ def _load_pools(self):
281
+ """Load key pools from environment variables."""
282
+ for name, cfg in PROVIDERS.items():
283
+ raw = os.environ.get(cfg["key_env"], "").strip()
284
+ if raw:
285
+ self._pools[name] = KeyPool(name, raw)
286
+ loaded = list(self._pools.keys())
287
+ log.info("router_v10_loaded", providers=loaded, total=len(loaded))
288
+
289
+ def reload_pools(self):
290
+ """Hot-reload pools (e.g., after env var update)."""
291
+ self._pools.clear()
292
+ self._load_pools()
293
+
294
+ def get_stats(self) -> Dict[str, Any]:
295
+ return {
296
+ name: {
297
+ "available": name in self._pools and self._pools[name].has_keys(),
298
+ "keys": self._pools[name].count() if name in self._pools else 0,
299
+ "calls": self._stats[name]["calls"],
300
+ "errors": self._stats[name]["errors"],
301
+ "avg_latency_ms": (
302
+ round(
303
+ sum(self._stats[name]["latency_ms"][-20:]) /
304
+ max(len(self._stats[name]["latency_ms"][-20:]), 1),
305
+ 1,
306
+ )
307
+ if self._stats[name]["latency_ms"] else 0
308
+ ),
309
+ }
310
+ for name in PROVIDERS
311
+ }
312
+
313
+ def get_pool_status(self) -> Dict[str, Any]:
314
+ return {
315
+ name: (
316
+ self._pools[name].status()
317
+ if name in self._pools
318
+ else {"provider": name, "total_keys": 0, "available_keys": 0, "keys": []}
319
+ )
320
+ for name in PROVIDERS
321
+ }
322
+
323
+ def get_status(self) -> Dict[str, Any]:
324
+ return {
325
+ "version": self.VERSION,
326
+ "providers": self.get_stats(),
327
+ "primary_order": PRIMARY_ORDER,
328
+ "fallback_order": FALLBACK_ORDER,
329
+ "active_providers": [n for n in self._pools if self._pools[n].has_keys()],
330
+ }
331
+
332
+ def _normalize_messages(
333
+ self,
334
+ messages: Optional[List[Dict[str, str]]] = None,
335
+ prompt: str = "",
336
+ system: str = "",
337
+ ) -> Tuple[List[Dict[str, str]], bool]:
338
+ if messages:
339
+ return messages, False
340
+ normalized: List[Dict[str, str]] = []
341
+ if system:
342
+ normalized.append({"role": "system", "content": system})
343
+ normalized.append({"role": "user", "content": prompt or "Hello"})
344
+ return normalized, True
345
+
346
+ async def complete(
347
+ self,
348
+ messages: Optional[List[Dict[str, str]]] = None,
349
+ task_id: str = "",
350
+ session_id: str = "",
351
+ temperature: float = 0.7,
352
+ max_tokens: int = 4096,
353
+ preferred_provider: str = "",
354
+ preferred_model: str = "",
355
+ stream: bool = False,
356
+ prompt: str = "",
357
+ system: str = "",
358
+ **_: Any,
359
+ ) -> Any:
360
+ normalized_messages, return_dict = self._normalize_messages(
361
+ messages=messages, prompt=prompt, system=system
362
+ )
363
+ user_msg = next(
364
+ (msg.get("content", "") for msg in reversed(normalized_messages) if msg.get("role") == "user"),
365
+ "",
366
+ )
367
+ task_type = classify_task(user_msg)
368
+ order = get_provider_order(task_type, preferred=preferred_provider)
369
+
370
+ log.info("routing", task_type=task_type, order=order[:3], session=session_id[:8] if session_id else "")
371
+ last_error = "No providers available β€” set GEMINI_KEY, SAMBANOVA_KEY, or GITHUB_KEY"
372
+
373
+ for provider_name in order:
374
+ if provider_name not in self._pools:
375
+ continue
376
+
377
+ pool = self._pools[provider_name]
378
+ cfg = PROVIDERS[provider_name]
379
+ model = preferred_model or cfg["default_model"]
380
+
381
+ for _attempt in range(MAX_RETRIES_PER_KEY):
382
+ key_obj = pool.pick()
383
+ if key_obj is None:
384
+ break
385
+
386
+ t0 = time.time()
387
+ try:
388
+ if cfg["type"] == "gemini":
389
+ ok, text = await call_gemini(
390
+ cfg["base_url"], key_obj["key"],
391
+ normalized_messages, min(max_tokens, cfg["max_tokens"])
392
+ )
393
+ else:
394
+ ok, text = await call_openai_compat(
395
+ cfg["base_url"], key_obj["key"], model,
396
+ normalized_messages, min(max_tokens, cfg["max_tokens"])
397
+ )
398
+
399
+ elapsed = int((time.time() - t0) * 1000)
400
+
401
+ if ok and text.strip():
402
+ pool.mark_success(key_obj)
403
+ self._stats[provider_name]["calls"] += 1
404
+ self._stats[provider_name]["latency_ms"].append(elapsed)
405
+ self._stats[provider_name]["last_used"] = time.time()
406
+ log.info("llm_success", provider=provider_name, ms=elapsed, task_type=task_type)
407
+
408
+ if self.ws and (task_id or session_id):
409
+ await self._emit_response(text, task_id, session_id)
410
+
411
+ payload = {
412
+ "content": text,
413
+ "provider": provider_name,
414
+ "task_type": task_type,
415
+ "latency_ms": elapsed,
416
+ "model": model,
417
+ }
418
+ return payload if return_dict else text
419
+
420
+ pool.mark_fail(key_obj)
421
+ last_error = text
422
+ self._stats[provider_name]["errors"] += 1
423
+ log.warning("llm_fail", provider=provider_name, error=text[:150])
424
+
425
+ except Exception as exc:
426
+ pool.mark_fail(key_obj)
427
+ self._stats[provider_name]["errors"] += 1
428
+ last_error = str(exc)
429
+ log.error("llm_exception", provider=provider_name, error=str(exc)[:200])
430
+
431
+ # All failed β€” return demo
432
+ demo = await self._demo_response(normalized_messages, task_type)
433
+ log.warning("all_providers_failed", last_error=last_error[:200])
434
+ payload = {
435
+ "content": demo,
436
+ "provider": "demo",
437
+ "task_type": task_type,
438
+ "error": last_error,
439
+ }
440
+ return payload if return_dict else demo
441
+
442
+ async def _emit_response(self, text: str, task_id: str, session_id: str):
443
+ """Emit response through WebSocket if available."""
444
+ try:
445
+ if task_id and hasattr(self.ws, "emit"):
446
+ await self.ws.emit(task_id, "llm_response", {"content": text}, session_id=session_id)
447
+ elif session_id and hasattr(self.ws, "emit_chat"):
448
+ await self.ws.emit_chat(session_id, "llm_response", {"content": text})
449
+ except Exception:
450
+ pass
451
+
452
+ async def _demo_response(self, messages: List[Dict[str, str]], task_type: str) -> str:
453
+ user_msg = next(
454
+ (msg.get("content", "") for msg in reversed(messages) if msg.get("role") == "user"),
455
+ "Hello"
456
+ )
457
+ active = list(self._pools.keys())
458
+ return (
459
+ "πŸ€– **GOD AGENT OS v10 β€” Demo Mode**\n\n"
460
+ f"Task type detected: `{task_type}`\n"
461
+ f"Your request: *{user_msg[:200]}*\n\n"
462
+ f"**Active providers:** {active or 'none'}\n\n"
463
+ "**To enable real AI, set environment variables:**\n"
464
+ "```\n"
465
+ "GEMINI_KEY=AIza...,AIza... (comma-separated, 6 keys)\n"
466
+ "SAMBANOVA_KEY=uuid,... (comma-separated, 9 keys)\n"
467
+ "GITHUB_KEY=ghp_...,ghp_... (comma-separated, 9 keys)\n"
468
+ "```\n\n"
469
+ "**God Agent OS v10 Features:**\n"
470
+ "- πŸ”‘ Multi-key pool routing with auto-failover\n"
471
+ "- 🧠 22 distributed worker spaces\n"
472
+ "- πŸ€– 16 autonomous agents\n"
473
+ "- πŸ’¬ Persistent chat history\n"
474
+ "- ⚑ Real-time WebSocket streaming\n"
475
+ "- 🌐 Manus-like UI experience\n"
476
+ )
477
+
478
+
479
+ # ─── Singleton ────────────────────────────────────────────────────────────────
480
+
481
+ _router_instance: Optional[AIRouterV10] = None
482
+
483
+
484
+ def get_router_v10(ws_manager=None) -> AIRouterV10:
485
+ global _router_instance
486
+ if _router_instance is None:
487
+ _router_instance = AIRouterV10(ws_manager)
488
+ return _router_instance
489
+
490
+
491
+ # Alias for compatibility
492
+ AIRouterV8 = AIRouterV10
493
+ GodModeRouter = AIRouterV10
ai_router/router_v3.py ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ πŸš€ GOD MODE+ v3 - Advanced AI Router with Reasoning Models
3
+ Intelligent model selection based on task type and requirements
4
+ Version: 3.0.0
5
+ """
6
+
7
+ import asyncio
8
+ import logging
9
+ from enum import Enum
10
+ from typing import Optional, Dict, List, Any
11
+ from dataclasses import dataclass
12
+ from datetime import datetime
13
+
14
+ import structlog
15
+ from openai import AsyncOpenAI, RateLimitError, APIError
16
+ from anthropic import AsyncAnthropic
17
+
18
+ log = structlog.get_logger()
19
+
20
+
21
+ class TaskType(str, Enum):
22
+ """Task classification for optimal model selection."""
23
+ REASONING = "reasoning"
24
+ CODING = "coding"
25
+ CHAT = "chat"
26
+ ANALYSIS = "analysis"
27
+ CREATIVE = "creative"
28
+ LIGHTWEIGHT = "lightweight"
29
+
30
+
31
+ class ModelProvider(str, Enum):
32
+ """Supported AI model providers."""
33
+ OPENAI = "openai"
34
+ ANTHROPIC = "anthropic"
35
+ GROQ = "groq"
36
+ DEEPSEEK = "deepseek"
37
+ TOGETHER = "together"
38
+ OPENROUTER = "openrouter"
39
+ CEREBRAS = "cerebras"
40
+ QWEN = "qwen"
41
+
42
+
43
+ @dataclass
44
+ class ModelConfig:
45
+ """Configuration for each model."""
46
+ provider: ModelProvider
47
+ model_id: str
48
+ name: str
49
+ max_tokens: int
50
+ cost_per_1k_input: float
51
+ cost_per_1k_output: float
52
+ latency_ms: int
53
+ reasoning_capable: bool
54
+ coding_capable: bool
55
+ context_length: int
56
+ is_free: bool = False
57
+
58
+
59
+ class AIRouterV3:
60
+ """
61
+ Advanced AI Router with:
62
+ - Reasoning model support (DeepSeek R1, Qwen QwQ, o1-mini)
63
+ - Smart task-based model selection
64
+ - Cost optimization
65
+ - Latency optimization
66
+ - Automatic failover with exponential backoff
67
+ """
68
+
69
+ # Model Registry
70
+ MODELS: Dict[str, ModelConfig] = {
71
+ # Reasoning Models (v3 NEW)
72
+ "deepseek-r1": ModelConfig(
73
+ provider=ModelProvider.DEEPSEEK,
74
+ model_id="deepseek-r1",
75
+ name="DeepSeek R1",
76
+ max_tokens=8000,
77
+ cost_per_1k_input=0.55,
78
+ cost_per_1k_output=2.19,
79
+ latency_ms=3000,
80
+ reasoning_capable=True,
81
+ coding_capable=True,
82
+ context_length=128000,
83
+ ),
84
+ "qwen-qwq": ModelConfig(
85
+ provider=ModelProvider.QWEN,
86
+ model_id="qwen-qwq-32b",
87
+ name="Qwen QwQ",
88
+ max_tokens=32000,
89
+ cost_per_1k_input=0.20,
90
+ cost_per_1k_output=0.60,
91
+ latency_ms=2500,
92
+ reasoning_capable=True,
93
+ coding_capable=True,
94
+ context_length=32768,
95
+ ),
96
+ "o1-mini": ModelConfig(
97
+ provider=ModelProvider.OPENAI,
98
+ model_id="o1-mini",
99
+ name="OpenAI o1-mini",
100
+ max_tokens=65536,
101
+ cost_per_1k_input=3.00,
102
+ cost_per_1k_output=12.00,
103
+ latency_ms=5000,
104
+ reasoning_capable=True,
105
+ coding_capable=True,
106
+ context_length=128000,
107
+ ),
108
+ # Standard Models
109
+ "gpt-4o": ModelConfig(
110
+ provider=ModelProvider.OPENAI,
111
+ model_id="gpt-4o",
112
+ name="GPT-4o",
113
+ max_tokens=4096,
114
+ cost_per_1k_input=5.00,
115
+ cost_per_1k_output=15.00,
116
+ latency_ms=1500,
117
+ reasoning_capable=False,
118
+ coding_capable=True,
119
+ context_length=128000,
120
+ ),
121
+ "claude-3.5-sonnet": ModelConfig(
122
+ provider=ModelProvider.ANTHROPIC,
123
+ model_id="claude-3-5-sonnet-20241022",
124
+ name="Claude 3.5 Sonnet",
125
+ max_tokens=4096,
126
+ cost_per_1k_input=3.00,
127
+ cost_per_1k_output=15.00,
128
+ latency_ms=1200,
129
+ reasoning_capable=False,
130
+ coding_capable=True,
131
+ context_length=200000,
132
+ ),
133
+ "llama-3.3-70b": ModelConfig(
134
+ provider=ModelProvider.GROQ,
135
+ model_id="llama-3.3-70b-versatile",
136
+ name="Llama 3.3 70B (Groq)",
137
+ max_tokens=8192,
138
+ cost_per_1k_input=0.00,
139
+ cost_per_1k_output=0.00,
140
+ latency_ms=800,
141
+ reasoning_capable=False,
142
+ coding_capable=True,
143
+ context_length=8192,
144
+ is_free=True,
145
+ ),
146
+ "mixtral-8x7b": ModelConfig(
147
+ provider=ModelProvider.TOGETHER,
148
+ model_id="mistralai/Mixtral-8x7B-Instruct-v0.1",
149
+ name="Mixtral 8x7B",
150
+ max_tokens=4096,
151
+ cost_per_1k_input=0.60,
152
+ cost_per_1k_output=0.60,
153
+ latency_ms=1000,
154
+ reasoning_capable=False,
155
+ coding_capable=True,
156
+ context_length=32768,
157
+ ),
158
+ }
159
+
160
+ # Routing Chains for Different Task Types
161
+ ROUTING_CHAINS = {
162
+ TaskType.REASONING: [
163
+ "deepseek-r1",
164
+ "qwen-qwq",
165
+ "o1-mini",
166
+ "gpt-4o",
167
+ "claude-3.5-sonnet",
168
+ ],
169
+ TaskType.CODING: [
170
+ "gpt-4o",
171
+ "claude-3.5-sonnet",
172
+ "deepseek-r1",
173
+ "llama-3.3-70b",
174
+ "mixtral-8x7b",
175
+ ],
176
+ TaskType.CHAT: [
177
+ "llama-3.3-70b", # Free first
178
+ "gpt-4o",
179
+ "claude-3.5-sonnet",
180
+ "mixtral-8x7b",
181
+ ],
182
+ TaskType.ANALYSIS: [
183
+ "gpt-4o",
184
+ "claude-3.5-sonnet",
185
+ "deepseek-r1",
186
+ "llama-3.3-70b",
187
+ ],
188
+ TaskType.CREATIVE: [
189
+ "gpt-4o",
190
+ "claude-3.5-sonnet",
191
+ "mixtral-8x7b",
192
+ "llama-3.3-70b",
193
+ ],
194
+ TaskType.LIGHTWEIGHT: [
195
+ "llama-3.3-70b",
196
+ "mixtral-8x7b",
197
+ "gpt-4o",
198
+ ],
199
+ }
200
+
201
+ def __init__(self, ws_manager=None):
202
+ """Initialize AI Router v3."""
203
+ self.ws_manager = ws_manager
204
+ self.clients = {}
205
+ self.model_stats = {}
206
+ self.retry_config = {
207
+ "max_retries": 3,
208
+ "initial_delay": 1,
209
+ "max_delay": 30,
210
+ "exponential_base": 2,
211
+ }
212
+ log.info("πŸ€– AI Router v3 initialized with reasoning models")
213
+
214
+ def detect_task_type(self, message: str, context: Dict[str, Any] = None) -> TaskType:
215
+ """
216
+ Detect task type from message content.
217
+ Uses heuristics and optional context hints.
218
+ """
219
+ message_lower = message.lower()
220
+ context = context or {}
221
+
222
+ # Check explicit task type hint
223
+ if context.get("task_type"):
224
+ try:
225
+ return TaskType(context["task_type"])
226
+ except ValueError:
227
+ pass
228
+
229
+ # Heuristic detection
230
+ if any(word in message_lower for word in ["think", "reason", "why", "explain", "analyze"]):
231
+ return TaskType.REASONING
232
+
233
+ if any(word in message_lower for word in ["code", "function", "debug", "fix", "implement"]):
234
+ return TaskType.CODING
235
+
236
+ if any(word in message_lower for word in ["analyze", "compare", "evaluate", "assess"]):
237
+ return TaskType.ANALYSIS
238
+
239
+ if any(word in message_lower for word in ["write", "create", "story", "poem", "imagine"]):
240
+ return TaskType.CREATIVE
241
+
242
+ # Default to chat for general conversation
243
+ return TaskType.CHAT
244
+
245
+ def select_model(
246
+ self,
247
+ task_type: TaskType,
248
+ optimize_for: str = "quality", # "quality", "speed", "cost"
249
+ context_length_needed: int = 4096,
250
+ ) -> str:
251
+ """
252
+ Select optimal model based on task type and optimization preference.
253
+ """
254
+ chain = self.ROUTING_CHAINS.get(task_type, self.ROUTING_CHAINS[TaskType.CHAT])
255
+
256
+ if optimize_for == "cost":
257
+ # Prefer free models first
258
+ for model_id in chain:
259
+ if self.MODELS[model_id].is_free:
260
+ return model_id
261
+ return chain[0]
262
+
263
+ elif optimize_for == "speed":
264
+ # Sort by latency
265
+ sorted_chain = sorted(
266
+ chain,
267
+ key=lambda m: self.MODELS[m].latency_ms
268
+ )
269
+ return sorted_chain[0]
270
+
271
+ else: # quality (default)
272
+ # Prefer models with better reasoning/coding capabilities
273
+ if task_type == TaskType.REASONING:
274
+ reasoning_models = [m for m in chain if self.MODELS[m].reasoning_capable]
275
+ return reasoning_models[0] if reasoning_models else chain[0]
276
+ elif task_type == TaskType.CODING:
277
+ coding_models = [m for m in chain if self.MODELS[m].coding_capable]
278
+ return coding_models[0] if coding_models else chain[0]
279
+
280
+ return chain[0]
281
+
282
+ async def route(
283
+ self,
284
+ message: str,
285
+ context: Dict[str, Any] = None,
286
+ optimize_for: str = "quality",
287
+ ) -> Dict[str, Any]:
288
+ """
289
+ Main routing function: detect task type β†’ select model β†’ execute with failover.
290
+ """
291
+ context = context or {}
292
+ task_type = self.detect_task_type(message, context)
293
+ model_id = self.select_model(task_type, optimize_for)
294
+
295
+ log.info(
296
+ "🎯 Routing decision",
297
+ task_type=task_type,
298
+ selected_model=model_id,
299
+ optimize_for=optimize_for,
300
+ )
301
+
302
+ # Try selected model with failover chain
303
+ chain = self.ROUTING_CHAINS.get(task_type, self.ROUTING_CHAINS[TaskType.CHAT])
304
+
305
+ for attempt, fallback_model in enumerate(chain):
306
+ try:
307
+ result = await self._call_model(fallback_model, message, context)
308
+
309
+ # Track success
310
+ if fallback_model not in self.model_stats:
311
+ self.model_stats[fallback_model] = {"success": 0, "failures": 0}
312
+ self.model_stats[fallback_model]["success"] += 1
313
+
314
+ return {
315
+ "success": True,
316
+ "model": fallback_model,
317
+ "task_type": task_type,
318
+ "response": result,
319
+ "attempts": attempt + 1,
320
+ }
321
+
322
+ except (RateLimitError, APIError) as e:
323
+ log.warning(
324
+ "⚠️ Model failed, trying next in chain",
325
+ model=fallback_model,
326
+ error=str(e),
327
+ attempt=attempt + 1,
328
+ )
329
+ if fallback_model not in self.model_stats:
330
+ self.model_stats[fallback_model] = {"success": 0, "failures": 0}
331
+ self.model_stats[fallback_model]["failures"] += 1
332
+
333
+ if attempt < len(chain) - 1:
334
+ await asyncio.sleep(self.retry_config["initial_delay"] ** attempt)
335
+ continue
336
+
337
+ return {
338
+ "success": False,
339
+ "error": "All models in chain failed",
340
+ "task_type": task_type,
341
+ "attempts": len(chain),
342
+ }
343
+
344
+ async def _call_model(self, model_id: str, message: str, context: Dict[str, Any]) -> str:
345
+ """Call specific model with appropriate client."""
346
+ config = self.MODELS[model_id]
347
+
348
+ if config.provider == ModelProvider.OPENAI:
349
+ return await self._call_openai(model_id, message, context)
350
+ elif config.provider == ModelProvider.ANTHROPIC:
351
+ return await self._call_anthropic(model_id, message, context)
352
+ elif config.provider == ModelProvider.GROQ:
353
+ return await self._call_groq(model_id, message, context)
354
+ else:
355
+ raise ValueError(f"Provider {config.provider} not yet implemented")
356
+
357
+ async def _call_openai(self, model_id: str, message: str, context: Dict[str, Any]) -> str:
358
+ """Call OpenAI models (GPT-4o, o1-mini)."""
359
+ # Implementation would go here
360
+ return f"[{model_id}] Response placeholder"
361
+
362
+ async def _call_anthropic(self, model_id: str, message: str, context: Dict[str, Any]) -> str:
363
+ """Call Anthropic Claude models."""
364
+ # Implementation would go here
365
+ return f"[{model_id}] Response placeholder"
366
+
367
+ async def _call_groq(self, model_id: str, message: str, context: Dict[str, Any]) -> str:
368
+ """Call Groq models (Llama 3.3 70B)."""
369
+ # Implementation would go here
370
+ return f"[{model_id}] Response placeholder"
371
+
372
+ def get_stats(self) -> Dict[str, Any]:
373
+ """Get router statistics."""
374
+ return {
375
+ "models": len(self.MODELS),
376
+ "model_stats": self.model_stats,
377
+ "timestamp": datetime.now().isoformat(),
378
+ }
379
+
380
+
381
+ # Export for use in main.py
382
+ __all__ = ["AIRouterV3", "TaskType", "ModelProvider"]
ai_router/router_v8.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GOD AGENT OS β€” Multi-Provider AI Router v8/v10 compatibility layer.
3
+ Primary providers: Gemini -> SambaNova -> GitHub Models.
4
+ Supports both legacy `messages=[...]` calls and newer `prompt/system` style calls.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import time
11
+ from typing import Any, Dict, List, Optional, Tuple
12
+
13
+ import httpx
14
+ import structlog
15
+
16
+ log = structlog.get_logger()
17
+
18
+ PROVIDERS = {
19
+ "gemini": {
20
+ "name": "gemini",
21
+ "type": "gemini",
22
+ "base_url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent",
23
+ "key_env": "GEMINI_KEY",
24
+ "default_model": "gemini-2.0-flash",
25
+ "max_tokens": 8192,
26
+ "priority_tasks": ["language", "research", "content", "general"],
27
+ },
28
+ "sambanova": {
29
+ "name": "sambanova",
30
+ "type": "openai",
31
+ "base_url": "https://api.sambanova.ai/v1",
32
+ "key_env": "SAMBANOVA_KEY",
33
+ "default_model": "Meta-Llama-3.3-70B-Instruct",
34
+ "max_tokens": 8192,
35
+ "priority_tasks": ["reasoning", "engineering", "planning", "analysis"],
36
+ },
37
+ "github": {
38
+ "name": "github",
39
+ "type": "openai",
40
+ "base_url": "https://models.inference.ai.azure.com",
41
+ "key_env": "GITHUB_KEY",
42
+ "default_model": "gpt-4o",
43
+ "max_tokens": 4096,
44
+ "priority_tasks": ["planning", "engineering", "general"],
45
+ },
46
+ "groq": {
47
+ "name": "groq",
48
+ "type": "openai",
49
+ "base_url": "https://api.groq.com/openai/v1",
50
+ "key_env": "GROQ_API_KEY",
51
+ "default_model": "llama-3.3-70b-versatile",
52
+ "max_tokens": 8192,
53
+ "priority_tasks": ["general"],
54
+ },
55
+ "openai": {
56
+ "name": "openai",
57
+ "type": "openai",
58
+ "base_url": "https://api.openai.com/v1",
59
+ "key_env": "OPENAI_API_KEY",
60
+ "default_model": "gpt-4o",
61
+ "max_tokens": 4096,
62
+ "priority_tasks": ["general"],
63
+ },
64
+ }
65
+
66
+ PRIMARY_ORDER = ["gemini", "sambanova", "github"]
67
+ FALLBACK_ORDER = ["groq", "openai"]
68
+ MAX_RETRIES_PER_KEY = 2
69
+ KEY_COOLDOWN_SECONDS = 300
70
+ KEY_MAX_FAILS = 3
71
+
72
+
73
+ class KeyPool:
74
+ def __init__(self, raw_keys: str):
75
+ self._keys: List[Dict[str, Any]] = []
76
+ for key in raw_keys.split(","):
77
+ key = key.strip()
78
+ if key:
79
+ self._keys.append({"key": key, "fails": 0, "cooldown_until": 0.0})
80
+
81
+ def pick(self) -> Optional[Dict[str, Any]]:
82
+ now = time.time()
83
+ available = [item for item in self._keys if item["cooldown_until"] < now]
84
+ if not available:
85
+ return None
86
+ available.sort(key=lambda item: item["fails"])
87
+ return available[0]
88
+
89
+ def mark_fail(self, key_obj: Dict[str, Any]):
90
+ key_obj["fails"] += 1
91
+ if key_obj["fails"] >= KEY_MAX_FAILS:
92
+ key_obj["cooldown_until"] = time.time() + KEY_COOLDOWN_SECONDS
93
+ log.warning("key_cooled_down", key_prefix=key_obj["key"][:8])
94
+
95
+ def mark_success(self, key_obj: Dict[str, Any]):
96
+ key_obj["fails"] = 0
97
+ key_obj["cooldown_until"] = 0.0
98
+
99
+ def has_keys(self) -> bool:
100
+ return bool(self._keys)
101
+
102
+ def count(self) -> int:
103
+ return len(self._keys)
104
+
105
+ def status(self) -> List[Dict[str, Any]]:
106
+ now = time.time()
107
+ result = []
108
+ for item in self._keys:
109
+ result.append({
110
+ "key_prefix": item["key"][:8],
111
+ "fails": item["fails"],
112
+ "cooldown_seconds": max(0, int(item["cooldown_until"] - now)),
113
+ "available": item["cooldown_until"] < now,
114
+ })
115
+ return result
116
+
117
+
118
+ def classify_task(prompt: str = "") -> str:
119
+ p = (prompt or "").lower()
120
+ if any(word in p for word in ["code", "function", "implement", "build", "develop", "api", "class", "debug"]):
121
+ return "engineering"
122
+ if any(word in p for word in ["plan", "strategy", "workflow", "json", "automate", "pipeline"]):
123
+ return "planning"
124
+ if any(word in p for word in ["analyze", "reasoning", "why", "explain", "evaluate", "compare"]):
125
+ return "reasoning"
126
+ if any(word in p for word in ["research", "find", "search", "discover", "investigate"]):
127
+ return "research"
128
+ if any(word in p for word in ["write", "content", "blog", "article", "copy", "generate text", "summarize"]):
129
+ return "content"
130
+ if any(word in p for word in ["translate", "language", "convert"]):
131
+ return "language"
132
+ if any(word in p for word in ["data", "csv", "metrics", "report", "insight"]):
133
+ return "analysis"
134
+ return "general"
135
+
136
+
137
+ def get_provider_order(task_type: str) -> List[str]:
138
+ ordered = sorted(PRIMARY_ORDER, key=lambda provider: 0 if task_type in PROVIDERS[provider]["priority_tasks"] else 1)
139
+ return ordered + [provider for provider in FALLBACK_ORDER if os.environ.get(PROVIDERS[provider]["key_env"], "")]
140
+
141
+
142
+ async def call_gemini(base_url: str, key: str, messages: List[Dict[str, str]], max_tokens: int) -> Tuple[bool, str]:
143
+ url = f"{base_url}?key={key}"
144
+ parts = [{"text": message.get("content", "")} for message in messages if message.get("content")]
145
+ body = {
146
+ "contents": [{"parts": parts or [{"text": "Hello"}]}],
147
+ "generationConfig": {"maxOutputTokens": max_tokens, "temperature": 0.7},
148
+ }
149
+ try:
150
+ async with httpx.AsyncClient(timeout=60.0) as client:
151
+ resp = await client.post(url, json=body)
152
+ if resp.status_code == 200:
153
+ data = resp.json()
154
+ text = data.get("candidates", [{}])[0].get("content", {}).get("parts", [{}])[0].get("text", "")
155
+ return True, text
156
+ return False, f"HTTP {resp.status_code}: {resp.text[:200]}"
157
+ except Exception as exc:
158
+ return False, str(exc)
159
+
160
+
161
+ async def call_openai_compat(base_url: str, key: str, model: str, messages: List[Dict[str, str]], max_tokens: int) -> Tuple[bool, str]:
162
+ url = f"{base_url}/chat/completions"
163
+ headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
164
+ body = {"model": model, "messages": messages, "max_tokens": max_tokens, "temperature": 0.7}
165
+ try:
166
+ async with httpx.AsyncClient(timeout=60.0) as client:
167
+ resp = await client.post(url, json=body, headers=headers)
168
+ if resp.status_code == 200:
169
+ data = resp.json()
170
+ return True, data["choices"][0]["message"]["content"]
171
+ return False, f"HTTP {resp.status_code}: {resp.text[:200]}"
172
+ except Exception as exc:
173
+ return False, str(exc)
174
+
175
+
176
+ class GodModeRouter:
177
+ def __init__(self, ws_manager=None):
178
+ self.ws = ws_manager
179
+ self._pools: Dict[str, KeyPool] = {}
180
+ self._stats: Dict[str, Dict[str, Any]] = {
181
+ name: {"calls": 0, "errors": 0, "latency_ms": []} for name in PROVIDERS
182
+ }
183
+ self._load_pools()
184
+
185
+ def _load_pools(self):
186
+ for name, cfg in PROVIDERS.items():
187
+ raw = os.environ.get(cfg["key_env"], "")
188
+ if raw:
189
+ self._pools[name] = KeyPool(raw)
190
+ log.info("key_pool_loaded", provider=name, key_count=self._pools[name].count())
191
+
192
+ def reload_pools(self):
193
+ self._pools.clear()
194
+ self._load_pools()
195
+
196
+ def get_status(self) -> Dict[str, Any]:
197
+ return {
198
+ "providers": {
199
+ name: {
200
+ "available": name in self._pools and self._pools[name].has_keys(),
201
+ "keys": self._pools[name].count() if name in self._pools else 0,
202
+ "stats": self._stats.get(name, {}),
203
+ }
204
+ for name in PROVIDERS
205
+ },
206
+ "primary_order": PRIMARY_ORDER,
207
+ }
208
+
209
+ def get_stats(self) -> Dict[str, Any]:
210
+ return {
211
+ name: {"available": name in self._pools, "calls": self._stats[name]["calls"]}
212
+ for name in PROVIDERS
213
+ }
214
+
215
+ def get_pool_status(self) -> Dict[str, Any]:
216
+ return {
217
+ name: {
218
+ "available": name in self._pools and self._pools[name].has_keys(),
219
+ "keys": self._pools[name].count() if name in self._pools else 0,
220
+ "entries": self._pools[name].status() if name in self._pools else [],
221
+ }
222
+ for name in PROVIDERS
223
+ }
224
+
225
+ def _normalize_messages(self, messages=None, prompt: str = "", system: str = "") -> Tuple[List[Dict[str, str]], bool]:
226
+ if messages:
227
+ return messages, False
228
+ normalized: List[Dict[str, str]] = []
229
+ if system:
230
+ normalized.append({"role": "system", "content": system})
231
+ normalized.append({"role": "user", "content": prompt or "Hello"})
232
+ return normalized, True
233
+
234
+ async def complete(
235
+ self,
236
+ messages: Optional[List[Dict[str, str]]] = None,
237
+ task_id: str = "",
238
+ session_id: str = "",
239
+ temperature: float = 0.7,
240
+ max_tokens: int = 4096,
241
+ preferred_provider: str = "",
242
+ stream: bool = False,
243
+ prompt: str = "",
244
+ system: str = "",
245
+ preferred_model: str = "",
246
+ **_: Any,
247
+ ) -> Any:
248
+ normalized_messages, return_dict = self._normalize_messages(messages=messages, prompt=prompt, system=system)
249
+ user_msg = next((msg.get("content", "") for msg in reversed(normalized_messages) if msg.get("role") == "user"), "")
250
+ task_type = classify_task(user_msg)
251
+
252
+ if preferred_provider and preferred_provider in PROVIDERS:
253
+ order = [preferred_provider] + [provider for provider in get_provider_order(task_type) if provider != preferred_provider]
254
+ else:
255
+ order = get_provider_order(task_type)
256
+
257
+ log.info("routing_request", task_type=task_type, order=order[:3], task_id=task_id, session_id=session_id)
258
+ last_error = "No providers available"
259
+
260
+ for provider_name in order:
261
+ if provider_name not in self._pools:
262
+ continue
263
+
264
+ pool = self._pools[provider_name]
265
+ cfg = PROVIDERS[provider_name]
266
+ model = preferred_model or cfg["default_model"]
267
+
268
+ for _attempt in range(MAX_RETRIES_PER_KEY):
269
+ key_obj = pool.pick()
270
+ if key_obj is None:
271
+ break
272
+
273
+ t0 = time.time()
274
+ try:
275
+ if cfg["type"] == "gemini":
276
+ ok, text = await call_gemini(cfg["base_url"], key_obj["key"], normalized_messages, min(max_tokens, cfg["max_tokens"]))
277
+ else:
278
+ ok, text = await call_openai_compat(cfg["base_url"], key_obj["key"], model, normalized_messages, min(max_tokens, cfg["max_tokens"]))
279
+ elapsed = int((time.time() - t0) * 1000)
280
+
281
+ if ok and text.strip():
282
+ pool.mark_success(key_obj)
283
+ self._stats[provider_name]["calls"] += 1
284
+ self._stats[provider_name]["latency_ms"].append(elapsed)
285
+ payload = {"content": text, "provider": provider_name, "task_type": task_type, "latency_ms": elapsed}
286
+ return payload if return_dict else text
287
+
288
+ pool.mark_fail(key_obj)
289
+ last_error = text
290
+ self._stats[provider_name]["errors"] += 1
291
+ log.warning("llm_fail", provider=provider_name, error=text[:120])
292
+ except Exception as exc:
293
+ pool.mark_fail(key_obj)
294
+ self._stats[provider_name]["errors"] += 1
295
+ last_error = str(exc)
296
+ log.error("llm_exception", provider=provider_name, error=str(exc)[:160])
297
+
298
+ demo = await self._demo_response(normalized_messages, task_type)
299
+ return {"content": demo, "provider": "demo", "task_type": task_type, "error": last_error} if return_dict else demo
300
+
301
+ async def _demo_response(self, messages: List[Dict[str, str]], task_type: str) -> str:
302
+ user_msg = next((msg.get("content", "") for msg in reversed(messages) if msg.get("role") == "user"), "Hello")
303
+ return (
304
+ "[GOD AGENT OS β€” Demo Mode]\n\n"
305
+ f"Task type detected: {task_type}\n"
306
+ f"Your request: '{user_msg[:160]}'\n\n"
307
+ "Configure API keys in environment: GEMINI_KEY, SAMBANOVA_KEY, GITHUB_KEY"
308
+ )
309
+
310
+
311
+ _router_instance: Optional[GodModeRouter] = None
312
+
313
+
314
+ def get_router(ws_manager=None) -> GodModeRouter:
315
+ global _router_instance
316
+ if _router_instance is None:
317
+ _router_instance = GodModeRouter(ws_manager)
318
+ return _router_instance
319
+
320
+
321
+ AIRouterV8 = GodModeRouter
api/__init__.py ADDED
File without changes
api/routes/__init__.py ADDED
File without changes
api/routes/agents.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
10
+
11
+ class OrchestrateRequest(BaseModel):
12
+ message: str
13
+ session_id: str = ""
14
+ task_id: str = ""
15
+ context: Dict[str, Any] = {}
16
+
17
+
18
+ class SandboxExecRequest(BaseModel):
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")
35
+ result = await orchestrator.orchestrate(
36
+ user_message=req.message,
37
+ session_id=req.session_id,
38
+ task_id=req.task_id,
39
+ context=req.context,
40
+ )
41
+ return {"result": result, "session_id": req.session_id}
42
+
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"}
50
+ return orchestrator.get_status()
51
+
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)
70
+ if not orchestrator:
71
+ raise HTTPException(500, "Orchestrator not initialized")
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 ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
13
+ from core.models import ChatRequest, GoalRequest, TaskCreateRequest
14
+ from memory.db import save_memory, get_history
15
+
16
+ router = APIRouter()
17
+
18
+
19
+ def get_engine(request: Request):
20
+ return request.app.state.task_engine
21
+
22
+
23
+ def get_ws(request: Request):
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(
157
+ goal=req.goal,
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 {
165
+ "task_id": task_id,
166
+ "goal": req.goal,
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 ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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):
14
+ connector_id: str
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 ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Health + Status Routes β€” GOD AGENT OS v11
3
+ """
4
+
5
+ import time
6
+ import os
7
+ import psutil
8
+ from fastapi import APIRouter, Request
9
+
10
+ router = APIRouter()
11
+
12
+
13
+ @router.get("/health", summary="Health check")
14
+ async def health(request: Request):
15
+ ws = getattr(request.app.state, "ws_manager", None)
16
+ engine = getattr(request.app.state, "task_engine", None)
17
+ orchestrator = getattr(request.app.state, "orchestrator", None)
18
+ ai_router = getattr(request.app.state, "ai_router", None)
19
+ connector_manager = getattr(request.app.state, "connector_manager", None)
20
+
21
+ ws_stats = ws.get_stats() if ws else {"total_connections": 0, "rooms": {}}
22
+ cs = connector_manager.get_summary() if connector_manager else {"connected": 0, "total": 0}
23
+ ai_stats = ai_router.get_stats() if ai_router else {}
24
+
25
+ orch_status = orchestrator.get_status() if orchestrator else {"agents": [], "total_agents": 0}
26
+
27
+ return {
28
+ "status": "healthy",
29
+ "name": "GOD AGENT OS v11 β€” Autonomous Engineering OS",
30
+ "version": "11.0.0",
31
+ "powered_by": "Pyae Sone",
32
+ "architecture": "Multi-Agent Orchestrator + Worker Spaces",
33
+ "timestamp": time.time(),
34
+ "platform": {
35
+ "mode": "god_mode",
36
+ "agents": orch_status.get("agents", []),
37
+ "agent_count": orch_status.get("total_agents", 0),
38
+ },
39
+ "ai_router": {
40
+ "providers": {k: v.get("available", False) for k, v in ai_stats.items()},
41
+ "ai_ready": any(v.get("available", False) for v in ai_stats.values()),
42
+ },
43
+ "connectors": {
44
+ "connected": cs.get("connected", 0),
45
+ "total": cs.get("total", 0),
46
+ "ai_ready": cs.get("ai_ready", False),
47
+ },
48
+ "task_engine": {
49
+ "queue_size": engine._queue.qsize() if engine else 0,
50
+ "active_tasks": len(engine._active) if engine else 0,
51
+ },
52
+ "websocket": {
53
+ "connections": ws_stats.get("total_connections", 0),
54
+ "rooms": list(ws_stats.get("rooms", {}).keys()),
55
+ },
56
+ "phases": [
57
+ "Phase 1: God Agent Orchestrator βœ…",
58
+ "Phase 2: Sandbox Agent βœ…",
59
+ "Phase 3: Connector System βœ…",
60
+ "Phase 4: Autonomous Coding Engine βœ…",
61
+ "Phase 5: Memory System βœ…",
62
+ "Phase 6: Real-time Streaming βœ…",
63
+ "Phase 7: Workflow Factor OS βœ…",
64
+ "Phase 8: Modern UI Rebuild βœ…",
65
+ "Phase 9: Multi-Model AI Router v10 βœ…",
66
+ "Phase 10: v11 Production Hardening βœ…",
67
+ ],
68
+ }
69
+
70
+
71
+ @router.get("/metrics", summary="System metrics")
72
+ async def metrics():
73
+ cpu = psutil.cpu_percent(interval=0.1)
74
+ mem = psutil.virtual_memory()
75
+ disk = psutil.disk_usage("/")
76
+ return {
77
+ "cpu_percent": cpu,
78
+ "memory": {
79
+ "total_mb": round(mem.total / 1024 / 1024),
80
+ "used_mb": round(mem.used / 1024 / 1024),
81
+ "percent": mem.percent,
82
+ },
83
+ "disk": {
84
+ "total_gb": round(disk.total / 1024 / 1024 / 1024, 1),
85
+ "used_gb": round(disk.used / 1024 / 1024 / 1024, 1),
86
+ "percent": disk.percent,
87
+ },
88
+ "timestamp": time.time(),
89
+ }
api/routes/memory.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Memory API Routes β€” Persistent agent memory
3
+ """
4
+
5
+ import time
6
+ from fastapi import APIRouter, HTTPException, Query
7
+ from core.models import MemorySaveRequest, MemorySearchRequest
8
+ from memory.db import save_memory, search_memory, get_project_memory, get_history
9
+
10
+ router = APIRouter()
11
+
12
+
13
+ @router.post("/", summary="Save memory")
14
+ async def save(req: MemorySaveRequest):
15
+ await save_memory(
16
+ content=req.content,
17
+ memory_type=req.memory_type.value,
18
+ session_id=req.session_id,
19
+ project_id=req.project_id,
20
+ key=req.key,
21
+ metadata=req.metadata,
22
+ )
23
+ return {"status": "saved", "memory_type": req.memory_type, "timestamp": time.time()}
24
+
25
+
26
+ @router.post("/search", summary="Search memory")
27
+ async def search(req: MemorySearchRequest):
28
+ results = await search_memory(
29
+ query=req.query,
30
+ session_id=req.session_id,
31
+ project_id=req.project_id,
32
+ limit=req.limit,
33
+ )
34
+ return {"results": results, "total": len(results), "query": req.query}
35
+
36
+
37
+ @router.get("/project/{project_id}", summary="Get project memory")
38
+ async def project_memory(
39
+ project_id: str,
40
+ memory_type: str = Query(default=""),
41
+ limit: int = Query(default=100, le=500),
42
+ ):
43
+ results = await get_project_memory(project_id, memory_type=memory_type, limit=limit)
44
+ return {"project_id": project_id, "memories": results, "total": len(results)}
45
+
46
+
47
+ @router.get("/history/{session_id}", summary="Get conversation history")
48
+ async def history(session_id: str, limit: int = Query(default=50, le=200)):
49
+ results = await get_history(session_id, limit=limit)
50
+ return {"session_id": session_id, "history": results, "total": len(results)}
api/routes/tasks.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Task API Routes β€” CRUD + Streaming + WebSocket
3
+ """
4
+
5
+ import asyncio
6
+ import json
7
+ import time
8
+ from typing import Optional
9
+
10
+ from fastapi import APIRouter, HTTPException, Request, Query
11
+ from fastapi.responses import StreamingResponse
12
+
13
+ from core.models import (
14
+ TaskCreateRequest, TaskCancelRequest, TaskRetryRequest, TaskResponse, TaskStatus
15
+ )
16
+ from memory.db import get_task, list_tasks, get_task_events, update_task_status
17
+
18
+ router = APIRouter()
19
+
20
+
21
+ def get_engine(request: Request):
22
+ return request.app.state.task_engine
23
+
24
+
25
+ def get_ws(request: Request):
26
+ return request.app.state.ws_manager
27
+
28
+
29
+ # ─── Create Task ───────────────────────────────────────────────────────────────
30
+
31
+ @router.post("/create", summary="Create & queue a new agent task")
32
+ async def create_task(req: TaskCreateRequest, request: Request):
33
+ engine = get_engine(request)
34
+ task_id = await engine.submit(req)
35
+ task = await get_task(task_id)
36
+ return {
37
+ "task_id": task_id,
38
+ "status": "queued",
39
+ "goal": req.goal,
40
+ "session_id": req.session_id,
41
+ "stream_url": f"/api/v1/tasks/{task_id}/stream",
42
+ "ws_url": f"/ws/tasks/{task_id}",
43
+ "created_at": task["created_at"] if task else time.time(),
44
+ }
45
+
46
+
47
+ # ─── Get Task ──────────────────────────────────────────────────────────────────
48
+
49
+ @router.get("/{task_id}", summary="Get task details")
50
+ async def get_task_detail(task_id: str):
51
+ task = await get_task(task_id)
52
+ if not task:
53
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
54
+ return task
55
+
56
+
57
+ # ─── Get Task Status ───────────────────────────────────────────────────────────
58
+
59
+ @router.get("/{task_id}/status", summary="Get task status only")
60
+ async def get_task_status(task_id: str):
61
+ task = await get_task(task_id)
62
+ if not task:
63
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
64
+ return {
65
+ "task_id": task_id,
66
+ "status": task["status"],
67
+ "retry_count": task.get("retry_count", 0),
68
+ "created_at": task.get("created_at"),
69
+ "started_at": task.get("started_at"),
70
+ "completed_at": task.get("completed_at"),
71
+ }
72
+
73
+
74
+ # ─── Cancel Task ───────────────────────────────────────────────────────────────
75
+
76
+ @router.post("/{task_id}/cancel", summary="Cancel a running task")
77
+ async def cancel_task(task_id: str, req: TaskCancelRequest, request: Request):
78
+ task = await get_task(task_id)
79
+ if not task:
80
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
81
+ if task["status"] in ("completed", "failed", "cancelled"):
82
+ raise HTTPException(status_code=400, detail=f"Task already {task['status']}")
83
+ engine = get_engine(request)
84
+ await engine.cancel(task_id, req.reason)
85
+ return {"task_id": task_id, "status": "cancelled", "reason": req.reason}
86
+
87
+
88
+ # ─── Retry Task ────────────────────────────────────────────────────────────────
89
+
90
+ @router.post("/{task_id}/retry", summary="Retry a failed task")
91
+ async def retry_task(task_id: str, request: Request):
92
+ task = await get_task(task_id)
93
+ if not task:
94
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
95
+ if task["status"] not in ("failed", "cancelled"):
96
+ raise HTTPException(status_code=400, detail="Only failed/cancelled tasks can be retried")
97
+ engine = get_engine(request)
98
+ await engine.retry(task_id)
99
+ return {"task_id": task_id, "status": "queued", "message": "Task requeued for retry"}
100
+
101
+
102
+ # ─── Stream Task Events (SSE) ──────────────────────────────────────────────────
103
+
104
+ @router.get("/{task_id}/stream", summary="Stream task events via SSE")
105
+ async def stream_task(task_id: str, request: Request):
106
+ task = await get_task(task_id)
107
+ if not task:
108
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
109
+
110
+ async def event_generator():
111
+ # First, replay all stored events
112
+ events = await get_task_events(task_id)
113
+ for ev in events:
114
+ data = json.dumps({
115
+ "type": ev["event_type"],
116
+ "task_id": task_id,
117
+ "timestamp": ev["timestamp"],
118
+ "data": json.loads(ev["data"]) if ev.get("data") else {},
119
+ })
120
+ yield f"data: {data}\n\n"
121
+
122
+ # Then stream live events via WS manager buffer
123
+ ws = get_ws(request)
124
+ room = f"task:{task_id}"
125
+ last_count = len(events)
126
+
127
+ # Poll for new events (for SSE fallback)
128
+ for _ in range(600): # max 5 minutes
129
+ await asyncio.sleep(0.5)
130
+ current_task = await get_task(task_id)
131
+ if current_task and current_task["status"] in ("completed", "failed", "cancelled"):
132
+ yield f"data: {json.dumps({'type': 'stream_end', 'task_id': task_id, 'status': current_task['status']})}\n\n"
133
+ break
134
+ # heartbeat
135
+ yield f"data: {json.dumps({'type': 'heartbeat', 'timestamp': time.time()})}\n\n"
136
+
137
+ return StreamingResponse(
138
+ event_generator(),
139
+ media_type="text/event-stream",
140
+ headers={
141
+ "Cache-Control": "no-cache",
142
+ "X-Accel-Buffering": "no",
143
+ "Connection": "keep-alive",
144
+ },
145
+ )
146
+
147
+
148
+ # ─── List Tasks ────────────────────────────────────────────────────────────────
149
+
150
+ @router.get("/", summary="List tasks")
151
+ async def list_all_tasks(
152
+ session_id: str = Query(default=""),
153
+ limit: int = Query(default=50, le=200),
154
+ ):
155
+ tasks = await list_tasks(session_id=session_id, limit=limit)
156
+ return {"tasks": tasks, "total": len(tasks)}
157
+
158
+
159
+ # ─── Task Events History ───────────────────────────────────────────────────────
160
+
161
+ @router.get("/{task_id}/events", summary="Get all events for a task")
162
+ async def task_events(task_id: str):
163
+ task = await get_task(task_id)
164
+ if not task:
165
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
166
+ events = await get_task_events(task_id)
167
+ return {"task_id": task_id, "events": events, "total": len(events)}
api/websocket_manager.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ WebSocket Connection Manager β€” Production Grade
3
+ Handles rooms, heartbeats, event buffering, reconnect support
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import time
9
+ import uuid
10
+ from collections import defaultdict
11
+ from typing import Dict, List, Optional, Set
12
+ import structlog
13
+
14
+ log = structlog.get_logger()
15
+
16
+
17
+ class WebSocketManager:
18
+ def __init__(self):
19
+ # room β†’ set of websockets
20
+ self._rooms: Dict[str, Set] = defaultdict(set)
21
+ # ws β†’ list of rooms
22
+ self._ws_rooms: Dict[object, Set[str]] = defaultdict(set)
23
+ # Event buffer per room (for replay on reconnect)
24
+ self._event_buffer: Dict[str, List] = defaultdict(list)
25
+ self._buffer_max = 100
26
+ # Active connection count
27
+ self._connection_count = 0
28
+
29
+ async def connect(self, websocket, room: str):
30
+ await websocket.accept()
31
+ self._rooms[room].add(websocket)
32
+ self._ws_rooms[websocket].add(room)
33
+ self._connection_count += 1
34
+ log.info("WS connected", room=room, total=self._connection_count)
35
+
36
+ # Replay buffered events for this room
37
+ buffered = self._event_buffer.get(room, [])[-20:]
38
+ for event in buffered:
39
+ try:
40
+ await websocket.send_json(event)
41
+ except Exception:
42
+ pass
43
+
44
+ await websocket.send_json({
45
+ "type": "connected",
46
+ "room": room,
47
+ "timestamp": time.time(),
48
+ "buffered_events": len(buffered),
49
+ })
50
+
51
+ def disconnect(self, websocket, room: Optional[str] = None):
52
+ if room:
53
+ self._rooms[room].discard(websocket)
54
+ self._ws_rooms[websocket].discard(room)
55
+ else:
56
+ for r in list(self._ws_rooms.get(websocket, [])):
57
+ self._rooms[r].discard(websocket)
58
+ self._ws_rooms.pop(websocket, None)
59
+ self._connection_count = max(0, self._connection_count - 1)
60
+ log.info("WS disconnected", room=room, total=self._connection_count)
61
+
62
+ async def broadcast(self, room: str, event: dict):
63
+ """Broadcast event to all sockets in a room."""
64
+ if "timestamp" not in event:
65
+ event["timestamp"] = time.time()
66
+ if "id" not in event:
67
+ event["id"] = str(uuid.uuid4())[:8]
68
+
69
+ # Buffer event
70
+ self._event_buffer[room].append(event)
71
+ if len(self._event_buffer[room]) > self._buffer_max:
72
+ self._event_buffer[room].pop(0)
73
+
74
+ dead = set()
75
+ for ws in list(self._rooms.get(room, [])):
76
+ try:
77
+ await ws.send_json(event)
78
+ except Exception:
79
+ dead.add(ws)
80
+
81
+ for ws in dead:
82
+ self.disconnect(ws, room)
83
+
84
+ async def broadcast_global(self, event: dict):
85
+ """Broadcast to ALL connected websockets."""
86
+ for room in list(self._rooms.keys()):
87
+ await self.broadcast(room, event)
88
+
89
+ async def emit(self, task_id: str, event_type: str, data: dict, session_id: str = ""):
90
+ """Emit a structured event to a task room + logs room."""
91
+ event = {
92
+ "type": event_type,
93
+ "task_id": task_id,
94
+ "session_id": session_id,
95
+ "timestamp": time.time(),
96
+ "data": data,
97
+ }
98
+ await self.broadcast(f"task:{task_id}", event)
99
+ await self.broadcast("logs", event)
100
+ await self.broadcast("agent_status", {
101
+ "type": "agent_event",
102
+ "task_id": task_id,
103
+ "event_type": event_type,
104
+ "timestamp": time.time(),
105
+ })
106
+
107
+ async def emit_chat(self, session_id: str, event_type: str, data: dict):
108
+ """Emit event to a chat session room."""
109
+ event = {
110
+ "type": event_type,
111
+ "session_id": session_id,
112
+ "timestamp": time.time(),
113
+ "data": data,
114
+ }
115
+ await self.broadcast(f"chat:{session_id}", event)
116
+
117
+ async def heartbeat_loop(self):
118
+ """Send heartbeat to all connections every 15s."""
119
+ while True:
120
+ await asyncio.sleep(15)
121
+ heartbeat = {
122
+ "type": "heartbeat",
123
+ "timestamp": time.time(),
124
+ "connections": self._connection_count,
125
+ }
126
+ for room in list(self._rooms.keys()):
127
+ await self.broadcast(room, heartbeat)
128
+
129
+ def get_stats(self) -> dict:
130
+ return {
131
+ "total_connections": self._connection_count,
132
+ "rooms": {r: len(ws) for r, ws in self._rooms.items()},
133
+ "buffered_events": {r: len(e) for r, e in self._event_buffer.items()},
134
+ }
app.py ADDED
@@ -0,0 +1,1240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GOD AGENT OS β€” Clean Phase-1 Backend
3
+ =====================================
4
+ Stability-first rebuild. One pipeline, no dead code.
5
+
6
+ Endpoints:
7
+ GET /health β€” real status + provider availability
8
+ GET /api/v1/health β€” alias
9
+ GET /api/v1/system/status β€” extended status
10
+ POST /api/v1/chat (SSE) β€” LLM-only streaming chat (NO sandbox)
11
+ POST /api/v1/execute (SSE) β€” real E2B sandbox execution
12
+ POST /api/v1/agent (SSE) β€” intent-routed: chat OR execute
13
+ POST /api/v1/orchestrate (SSE) β€” alias of /api/v1/agent (legacy)
14
+ POST /api/v1/kernel/orchestrate β€” alias of /api/v1/agent (legacy, frontend)
15
+ GET /api/v1/sandbox/{session} β€” sandbox info
16
+ DELETE /api/v1/sandbox/{session} β€” kill sandbox
17
+ WS /ws/{session_id} β€” same events over WebSocket
18
+
19
+ Execution rules (per spec):
20
+ * Normal chat (greetings, explanations, brainstorming) β†’ LLM only, no E2B
21
+ * Execution intent (code, shell, files, packages) β†’ real E2B sandbox
22
+ * Intent detection: keyword heuristic + LLM tie-breaker (cheap, sync)
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import asyncio
28
+ import json
29
+ import os
30
+ import re
31
+ import time
32
+ import uuid
33
+ from contextlib import asynccontextmanager
34
+ from typing import Any, AsyncGenerator, Dict, List, Optional, Tuple
35
+
36
+ import httpx
37
+ import structlog
38
+ from fastapi import FastAPI, HTTPException, Request, WebSocket, WebSocketDisconnect
39
+ from fastapi.middleware.cors import CORSMiddleware
40
+ from fastapi.middleware.gzip import GZipMiddleware
41
+ from fastapi.responses import JSONResponse, StreamingResponse
42
+
43
+ # ─────────────────────────────────────────────────────────────────────────────
44
+ # Logging
45
+ # ─────────────────────────────────────────────────────────────────────────────
46
+ structlog.configure(
47
+ processors=[
48
+ structlog.processors.TimeStamper(fmt="iso"),
49
+ structlog.stdlib.add_log_level,
50
+ structlog.dev.ConsoleRenderer(),
51
+ ]
52
+ )
53
+ log = structlog.get_logger()
54
+
55
+ # ─────────────────────────────────────────────────────────────────────────────
56
+ # Environment
57
+ # ─────────────────────────────────────────────────────────────────────────────
58
+ E2B_API_KEY = os.environ.get("E2B_API_KEY", "").strip()
59
+
60
+ # Multiple keys can be comma-separated; we just pick the first non-empty.
61
+ def _first_key(*env_names: str) -> str:
62
+ for name in env_names:
63
+ v = os.environ.get(name, "")
64
+ if v:
65
+ return v.split(",")[0].strip()
66
+ return ""
67
+
68
+ SAMBANOVA_KEY = _first_key("SAMBANOVA_KEY", "SAMBANOVA_API_KEY", "SAMBANOVA_API_KEYS")
69
+ GEMINI_KEY = _first_key("GEMINI_KEY", "GEMINI_API_KEY", "GEMINI_API_KEYS")
70
+ GITHUB_LLM_KEY= _first_key("GITHUB_KEY", "GITHUB_API_KEY", "GITHUB_MODELS_TOKEN", "GITHUB_API_KEYS")
71
+ OPENAI_KEY = _first_key("OPENAI_API_KEY")
72
+ GROQ_KEY = _first_key("GROQ_API_KEY")
73
+ ANTHROPIC_KEY = _first_key("ANTHROPIC_API_KEY")
74
+ HF_TOKEN = _first_key("HF_TOKEN", "HUGGINGFACE_TOKEN")
75
+
76
+ VERSION = "13.0.0-phase1"
77
+
78
+ # ─────────────────────────────────────────────────────────────────────────────
79
+ # LLM Providers β€” OpenAI-compatible streaming (most stable, no tool-calling)
80
+ # ─────────────────────────────────────────────────────────────────────────────
81
+ # Order: most stable & fastest first. We deliberately do NOT use Gemini for
82
+ # tool-calling here β€” Phase 1 is text-only streaming.
83
+ PROVIDERS: List[Dict[str, Any]] = [
84
+ {
85
+ "name": "sambanova",
86
+ "key": SAMBANOVA_KEY,
87
+ "url": "https://api.sambanova.ai/v1/chat/completions",
88
+ "model": "Meta-Llama-3.3-70B-Instruct",
89
+ },
90
+ {
91
+ "name": "groq",
92
+ "key": GROQ_KEY,
93
+ "url": "https://api.groq.com/openai/v1/chat/completions",
94
+ "model": "llama-3.3-70b-versatile",
95
+ },
96
+ {
97
+ "name": "github",
98
+ "key": GITHUB_LLM_KEY,
99
+ "url": "https://models.inference.ai.azure.com/chat/completions",
100
+ "model": "gpt-4o-mini",
101
+ },
102
+ {
103
+ "name": "openai",
104
+ "key": OPENAI_KEY,
105
+ "url": "https://api.openai.com/v1/chat/completions",
106
+ "model": "gpt-4o-mini",
107
+ },
108
+ {
109
+ "name": "anthropic",
110
+ "key": ANTHROPIC_KEY,
111
+ "url": "https://api.anthropic.com/v1/messages", # different protocol; handled specially
112
+ "model": "claude-3-5-haiku-20241022",
113
+ },
114
+ {
115
+ "name": "gemini",
116
+ "key": GEMINI_KEY,
117
+ "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent",
118
+ "model": "gemini-2.0-flash",
119
+ },
120
+ ]
121
+
122
+
123
+ def active_providers() -> List[str]:
124
+ return [p["name"] for p in PROVIDERS if p["key"]]
125
+
126
+
127
+ # ─────────────────────────────────────────────────────────────────────────────
128
+ # Streaming helpers
129
+ # ─────────────────────────────────────────────────────────────────────────────
130
+ SYSTEM_PROMPT_CHAT = (
131
+ "You are God Agent OS, a helpful autonomous AI assistant. "
132
+ "Answer clearly and concisely. Use Markdown when helpful. "
133
+ "Do NOT pretend to execute code β€” if execution is needed, the system "
134
+ "will route the request to a real sandbox automatically."
135
+ )
136
+
137
+
138
+ async def _stream_openai_compat(
139
+ provider: Dict[str, Any],
140
+ messages: List[Dict[str, str]],
141
+ temperature: float = 0.7,
142
+ max_tokens: int = 2048,
143
+ ) -> AsyncGenerator[str, None]:
144
+ """Stream tokens from any OpenAI-compatible /chat/completions endpoint."""
145
+ headers = {
146
+ "Authorization": f"Bearer {provider['key']}",
147
+ "Content-Type": "application/json",
148
+ }
149
+ payload = {
150
+ "model": provider["model"],
151
+ "messages": messages,
152
+ "stream": True,
153
+ "temperature": temperature,
154
+ "max_tokens": max_tokens,
155
+ }
156
+ async with httpx.AsyncClient(timeout=httpx.Timeout(120.0, connect=15.0)) as client:
157
+ async with client.stream("POST", provider["url"], json=payload, headers=headers) as resp:
158
+ if resp.status_code != 200:
159
+ body = (await resp.aread())[:300].decode("utf-8", "ignore")
160
+ log.warning("LLM provider failed", provider=provider["name"], status=resp.status_code, body=body)
161
+ raise RuntimeError(f"{provider['name']} HTTP {resp.status_code}: {body}")
162
+
163
+ async for line in resp.aiter_lines():
164
+ if not line or not line.startswith("data:"):
165
+ continue
166
+ data_str = line[5:].strip()
167
+ if data_str in ("", "[DONE]"):
168
+ if data_str == "[DONE]":
169
+ return
170
+ continue
171
+ try:
172
+ obj = json.loads(data_str)
173
+ delta = obj["choices"][0].get("delta", {})
174
+ content = delta.get("content")
175
+ if content:
176
+ yield content
177
+ except Exception:
178
+ continue
179
+
180
+
181
+ async def _stream_gemini(
182
+ provider: Dict[str, Any],
183
+ messages: List[Dict[str, str]],
184
+ temperature: float = 0.7,
185
+ max_tokens: int = 2048,
186
+ ) -> AsyncGenerator[str, None]:
187
+ """Stream from Gemini SSE endpoint β€” text-only (no tools)."""
188
+ system_text = ""
189
+ contents: List[Dict[str, Any]] = []
190
+ for m in messages:
191
+ role = m.get("role", "user")
192
+ content = m.get("content", "")
193
+ if role == "system":
194
+ system_text = content
195
+ continue
196
+ g_role = "user" if role == "user" else "model"
197
+ contents.append({"role": g_role, "parts": [{"text": content}]})
198
+ if not contents:
199
+ contents = [{"role": "user", "parts": [{"text": "Hello"}]}]
200
+
201
+ body: Dict[str, Any] = {
202
+ "contents": contents,
203
+ "generationConfig": {"maxOutputTokens": max_tokens, "temperature": temperature},
204
+ }
205
+ if system_text:
206
+ body["systemInstruction"] = {"parts": [{"text": system_text}]}
207
+
208
+ url = f"{provider['url']}?alt=sse&key={provider['key']}"
209
+ async with httpx.AsyncClient(timeout=httpx.Timeout(120.0, connect=15.0)) as client:
210
+ async with client.stream("POST", url, json=body) as resp:
211
+ if resp.status_code != 200:
212
+ err = (await resp.aread())[:300].decode("utf-8", "ignore")
213
+ raise RuntimeError(f"gemini HTTP {resp.status_code}: {err}")
214
+ async for line in resp.aiter_lines():
215
+ if not line.startswith("data:"):
216
+ continue
217
+ data_str = line[5:].strip()
218
+ if not data_str:
219
+ continue
220
+ try:
221
+ obj = json.loads(data_str)
222
+ parts = obj.get("candidates", [{}])[0].get("content", {}).get("parts", [])
223
+ for p in parts:
224
+ if "text" in p and p["text"]:
225
+ yield p["text"]
226
+ except Exception:
227
+ continue
228
+
229
+
230
+ async def _stream_anthropic(
231
+ provider: Dict[str, Any],
232
+ messages: List[Dict[str, str]],
233
+ temperature: float = 0.7,
234
+ max_tokens: int = 2048,
235
+ ) -> AsyncGenerator[str, None]:
236
+ """Stream from Anthropic Messages API."""
237
+ system_text = ""
238
+ msgs: List[Dict[str, Any]] = []
239
+ for m in messages:
240
+ if m.get("role") == "system":
241
+ system_text = m.get("content", "")
242
+ continue
243
+ msgs.append({"role": m["role"], "content": m["content"]})
244
+
245
+ payload: Dict[str, Any] = {
246
+ "model": provider["model"],
247
+ "max_tokens": max_tokens,
248
+ "temperature": temperature,
249
+ "messages": msgs,
250
+ "stream": True,
251
+ }
252
+ if system_text:
253
+ payload["system"] = system_text
254
+
255
+ headers = {
256
+ "x-api-key": provider["key"],
257
+ "anthropic-version": "2023-06-01",
258
+ "content-type": "application/json",
259
+ }
260
+ async with httpx.AsyncClient(timeout=httpx.Timeout(120.0, connect=15.0)) as client:
261
+ async with client.stream("POST", provider["url"], json=payload, headers=headers) as resp:
262
+ if resp.status_code != 200:
263
+ err = (await resp.aread())[:300].decode("utf-8", "ignore")
264
+ raise RuntimeError(f"anthropic HTTP {resp.status_code}: {err}")
265
+ async for line in resp.aiter_lines():
266
+ if not line.startswith("data:"):
267
+ continue
268
+ data_str = line[5:].strip()
269
+ if not data_str or data_str == "[DONE]":
270
+ continue
271
+ try:
272
+ obj = json.loads(data_str)
273
+ if obj.get("type") == "content_block_delta":
274
+ d = obj.get("delta", {})
275
+ if d.get("type") == "text_delta":
276
+ t = d.get("text", "")
277
+ if t:
278
+ yield t
279
+ except Exception:
280
+ continue
281
+
282
+
283
+ async def stream_llm(
284
+ messages: List[Dict[str, str]],
285
+ temperature: float = 0.7,
286
+ max_tokens: int = 2048,
287
+ ) -> AsyncGenerator[Tuple[str, str], None]:
288
+ """
289
+ Yield (provider_name, token) pairs. Tries providers in PROVIDERS order,
290
+ falling back to next on failure. Yields a 'demo' provider with a helpful
291
+ message if NO keys are configured.
292
+ """
293
+ last_err: Optional[str] = None
294
+ for p in PROVIDERS:
295
+ if not p["key"]:
296
+ continue
297
+ name = p["name"]
298
+ try:
299
+ if name == "gemini":
300
+ stream = _stream_gemini(p, messages, temperature, max_tokens)
301
+ elif name == "anthropic":
302
+ stream = _stream_anthropic(p, messages, temperature, max_tokens)
303
+ else:
304
+ stream = _stream_openai_compat(p, messages, temperature, max_tokens)
305
+
306
+ emitted = False
307
+ async for chunk in stream:
308
+ emitted = True
309
+ yield (name, chunk)
310
+ if emitted:
311
+ return
312
+ last_err = f"{name} produced no tokens"
313
+ log.warning("Provider produced no tokens, falling back", provider=name)
314
+ except Exception as e:
315
+ last_err = f"{name}: {e}"
316
+ log.warning("Provider failed, falling back", provider=name, error=str(e)[:200])
317
+ continue
318
+
319
+ # No provider worked β€” emit a clear demo message
320
+ demo = (
321
+ "**[Demo mode]** No LLM provider succeeded.\n\n"
322
+ f"Last error: `{last_err or 'no API keys configured'}`\n\n"
323
+ "Set one of: `SAMBANOVA_KEY`, `GEMINI_KEY`, `GITHUB_KEY`, `OPENAI_API_KEY`, "
324
+ "`GROQ_API_KEY`, `ANTHROPIC_API_KEY` in HF Space β†’ Settings β†’ Variables and Secrets."
325
+ )
326
+ for tok in re.findall(r"\S+\s*", demo):
327
+ yield ("demo", tok)
328
+ await asyncio.sleep(0.01)
329
+
330
+
331
+ # ─────────────────────────────────────────────────────────────────────────────
332
+ # E2B Sandbox Manager β€” real execution, real stdout/stderr streaming
333
+ # ─────────────────────────────────────────────────────────────────────────────
334
+ class SandboxManager:
335
+ """Manages per-session E2B sandboxes with reuse + lazy creation."""
336
+
337
+ def __init__(self) -> None:
338
+ self._sandboxes: Dict[str, Any] = {} # session_id -> Sandbox
339
+ self._meta: Dict[str, Dict[str, Any]] = {} # session_id -> {created_at, sandbox_id}
340
+ self._lock = asyncio.Lock()
341
+ self._sdk_ok = False
342
+ try:
343
+ from e2b_code_interpreter import Sandbox # noqa: F401
344
+ self._sdk_ok = True
345
+ except Exception as e:
346
+ log.warning("E2B SDK not importable", error=str(e))
347
+
348
+ @property
349
+ def available(self) -> bool:
350
+ return self._sdk_ok and bool(E2B_API_KEY)
351
+
352
+ async def get_or_create(self, session_id: str):
353
+ if not self.available:
354
+ return None
355
+ async with self._lock:
356
+ sbx = self._sandboxes.get(session_id)
357
+ if sbx is not None:
358
+ return sbx
359
+ from e2b_code_interpreter import Sandbox # type: ignore
360
+ # Sandbox constructor is blocking β†’ offload to a thread
361
+ sbx = await asyncio.to_thread(Sandbox, api_key=E2B_API_KEY)
362
+ self._sandboxes[session_id] = sbx
363
+ self._meta[session_id] = {
364
+ "sandbox_id": getattr(sbx, "sandbox_id", "unknown"),
365
+ "created_at": time.time(),
366
+ }
367
+ log.info("E2B sandbox created", session_id=session_id, sandbox_id=self._meta[session_id]["sandbox_id"])
368
+ return sbx
369
+
370
+ async def kill(self, session_id: str) -> bool:
371
+ async with self._lock:
372
+ sbx = self._sandboxes.pop(session_id, None)
373
+ self._meta.pop(session_id, None)
374
+ if sbx is None:
375
+ return False
376
+ try:
377
+ await asyncio.to_thread(sbx.kill)
378
+ return True
379
+ except Exception as e:
380
+ log.warning("Sandbox kill error", error=str(e))
381
+ return False
382
+
383
+ def info(self, session_id: str) -> Optional[Dict[str, Any]]:
384
+ return self._meta.get(session_id)
385
+
386
+ def stats(self) -> Dict[str, Any]:
387
+ return {
388
+ "available": self.available,
389
+ "active_sandboxes": len(self._sandboxes),
390
+ "sessions": list(self._meta.keys()),
391
+ }
392
+
393
+ async def shutdown_all(self) -> None:
394
+ async with self._lock:
395
+ sessions = list(self._sandboxes.keys())
396
+ for sid in sessions:
397
+ await self.kill(sid)
398
+
399
+
400
+ sandbox_mgr = SandboxManager()
401
+
402
+
403
+ async def execute_in_sandbox(
404
+ session_id: str,
405
+ language: str,
406
+ code: str,
407
+ timeout: int = 60,
408
+ ) -> AsyncGenerator[Dict[str, Any], None]:
409
+ """
410
+ Execute code in a real E2B sandbox (or LOCAL fallback if E2B unavailable).
411
+ Yields event dicts:
412
+ {type: 'sandbox_ready', sandbox_id, session_id}
413
+ {type: 'stdout', text}
414
+ {type: 'stderr', text}
415
+ {type: 'result', exit_code, duration_ms, sandbox_id, success}
416
+ {type: 'error', error}
417
+ """
418
+ t0 = time.time()
419
+ language = (language or "python").lower()
420
+
421
+ # ── Real E2B path ────────────────────────────────────────────────────────
422
+ if sandbox_mgr.available:
423
+ try:
424
+ sbx = await sandbox_mgr.get_or_create(session_id)
425
+ meta = sandbox_mgr.info(session_id) or {}
426
+ yield {
427
+ "type": "sandbox_ready",
428
+ "sandbox_id": meta.get("sandbox_id", "unknown"),
429
+ "session_id": session_id,
430
+ "backend": "e2b",
431
+ }
432
+
433
+ stdout_buf: List[str] = []
434
+ stderr_buf: List[str] = []
435
+
436
+ def on_stdout(line): # called from SDK thread
437
+ text = getattr(line, "line", None) or str(line)
438
+ stdout_buf.append(text)
439
+
440
+ def on_stderr(line):
441
+ text = getattr(line, "line", None) or str(line)
442
+ stderr_buf.append(text)
443
+
444
+ if language in ("bash", "sh", "shell"):
445
+ # Run as shell command
446
+ exec_result = await asyncio.to_thread(
447
+ sbx.run_code,
448
+ f"import subprocess, sys\n"
449
+ f"r = subprocess.run({code!r}, shell=True, capture_output=True, text=True, timeout={timeout})\n"
450
+ f"sys.stdout.write(r.stdout)\n"
451
+ f"sys.stderr.write(r.stderr)\n"
452
+ f"sys.exit(r.returncode)\n",
453
+ )
454
+ else:
455
+ exec_result = await asyncio.to_thread(sbx.run_code, code)
456
+
457
+ # Drain logs (E2B 1.0.5 returns logs in exec_result.logs)
458
+ for line in (getattr(exec_result.logs, "stdout", []) or []):
459
+ yield {"type": "stdout", "text": line if line.endswith("\n") else line + "\n"}
460
+ for line in (getattr(exec_result.logs, "stderr", []) or []):
461
+ yield {"type": "stderr", "text": line if line.endswith("\n") else line + "\n"}
462
+
463
+ err = getattr(exec_result, "error", None)
464
+ exit_code = 0
465
+ if err:
466
+ yield {"type": "stderr", "text": f"{getattr(err, 'name', 'Error')}: {getattr(err, 'value', err)}\n"}
467
+ exit_code = 1
468
+
469
+ yield {
470
+ "type": "result",
471
+ "exit_code": exit_code,
472
+ "success": exit_code == 0,
473
+ "duration_ms": int((time.time() - t0) * 1000),
474
+ "sandbox_id": meta.get("sandbox_id", "unknown"),
475
+ "backend": "e2b",
476
+ }
477
+ return
478
+
479
+ except Exception as e:
480
+ log.error("E2B execution failed, falling back to local", error=str(e))
481
+ yield {"type": "stderr", "text": f"[e2b fallback] {e}\n"}
482
+ # Fall through to LOCAL
483
+
484
+ # ── LOCAL fallback (subprocess) β€” clearly marked as fallback ─────────────
485
+ import subprocess
486
+ workdir = f"/tmp/god_workspace/{session_id}"
487
+ os.makedirs(workdir, exist_ok=True)
488
+ yield {
489
+ "type": "sandbox_ready",
490
+ "sandbox_id": f"local-{session_id}",
491
+ "session_id": session_id,
492
+ "backend": "local",
493
+ }
494
+ try:
495
+ if language in ("bash", "sh", "shell"):
496
+ cmd = ["bash", "-lc", code]
497
+ else:
498
+ # Run python via stdin to avoid filename issues
499
+ cmd = ["python3", "-c", code]
500
+ proc = await asyncio.create_subprocess_exec(
501
+ *cmd,
502
+ cwd=workdir,
503
+ stdout=asyncio.subprocess.PIPE,
504
+ stderr=asyncio.subprocess.PIPE,
505
+ )
506
+
507
+ async def _drain(stream, evt):
508
+ async for raw in stream:
509
+ yield_text = raw.decode("utf-8", "replace")
510
+ await _queue.put({"type": evt, "text": yield_text})
511
+ await _queue.put({"_eof": evt})
512
+
513
+ _queue: asyncio.Queue = asyncio.Queue()
514
+ t1 = asyncio.create_task(_pipe(proc.stdout, "stdout", _queue))
515
+ t2 = asyncio.create_task(_pipe(proc.stderr, "stderr", _queue))
516
+
517
+ eofs = 0
518
+ deadline = time.time() + timeout
519
+ while eofs < 2:
520
+ try:
521
+ item = await asyncio.wait_for(_queue.get(), timeout=max(0.1, deadline - time.time()))
522
+ except asyncio.TimeoutError:
523
+ proc.kill()
524
+ yield {"type": "stderr", "text": f"[timeout after {timeout}s]\n"}
525
+ break
526
+ if "_eof" in item:
527
+ eofs += 1
528
+ continue
529
+ yield item
530
+
531
+ rc = await proc.wait()
532
+ yield {
533
+ "type": "result",
534
+ "exit_code": rc,
535
+ "success": rc == 0,
536
+ "duration_ms": int((time.time() - t0) * 1000),
537
+ "sandbox_id": f"local-{session_id}",
538
+ "backend": "local",
539
+ }
540
+ except Exception as e:
541
+ yield {"type": "error", "error": str(e)}
542
+
543
+
544
+ async def _pipe(stream, evt: str, queue: asyncio.Queue) -> None:
545
+ """Drain a subprocess stream line-by-line into queue."""
546
+ try:
547
+ while True:
548
+ line = await stream.readline()
549
+ if not line:
550
+ break
551
+ await queue.put({"type": evt, "text": line.decode("utf-8", "replace")})
552
+ finally:
553
+ await queue.put({"_eof": evt})
554
+
555
+
556
+ # ─────────────────────────────────────────────────────────────────────────────
557
+ # Intent router β€” chat vs execute
558
+ # ─────────────────────────────────────────────────────────────────────────────
559
+ EXEC_KEYWORDS = [
560
+ "run ", "execute ", "compute ", "calculate ", "evaluate this",
561
+ "write a file", "create a file", "create file", "write to ", "save to file",
562
+ "list files", "ls ", "pwd ", "cat ",
563
+ "install ", "pip install", "npm install", "apt-get",
564
+ "python -c", "bash -c",
565
+ "ΠΏΡ€ΠΎof.txt", "proof.txt", # from the spec example
566
+ "current unix timestamp", "unix timestamp", "current timestamp",
567
+ "shell command", "terminal command",
568
+ "debug this", "test this code",
569
+ "scrape ", "fetch url", "curl ",
570
+ ]
571
+
572
+ CODE_BLOCK_RE = re.compile(r"```([a-zA-Z0-9_+\-]*)\n(.+?)```", re.DOTALL)
573
+
574
+
575
+ def detect_intent(message: str) -> Dict[str, Any]:
576
+ """
577
+ Lightweight intent detector. Returns:
578
+ {intent: 'chat'|'execute', language?, code?, reason}
579
+ Only flips to 'execute' if very confident β€” protects normal chat.
580
+ """
581
+ msg = message.strip()
582
+ lower = msg.lower()
583
+
584
+ # 1) explicit code block in message β†’ execute
585
+ m = CODE_BLOCK_RE.search(msg)
586
+ if m:
587
+ lang = (m.group(1) or "python").lower()
588
+ if lang in ("py", ""):
589
+ lang = "python"
590
+ return {"intent": "execute", "language": lang, "code": m.group(2).strip(), "reason": "code_block"}
591
+
592
+ # 2) execution keywords + looks like a task
593
+ hits = [kw for kw in EXEC_KEYWORDS if kw in lower]
594
+ if hits:
595
+ return {"intent": "execute", "language": "python", "code": None, "reason": f"keywords:{hits[:3]}"}
596
+
597
+ # 3) very short or greeting β†’ chat
598
+ if len(msg) < 200 and re.search(r"\b(hi|hello|hey|thanks|thank you|α€™α€„α€Ία€Ήα€‚α€œα€¬|α€Ÿα€šα€Ία€œα€­α€―)\b", lower):
599
+ return {"intent": "chat", "reason": "greeting"}
600
+
601
+ # default: chat (safe; user can still ask explicitly)
602
+ return {"intent": "chat", "reason": "default"}
603
+
604
+
605
+ async def llm_generate_code(user_message: str) -> Optional[Tuple[str, str]]:
606
+ """
607
+ Ask LLM to produce executable code for the user's task.
608
+ Uses non-streaming completion (more reliable than streamed concat for code).
609
+ Returns (language, code) or None.
610
+ """
611
+ sys_prompt = (
612
+ "You are a precise code generator. The user wants a task DONE by executing code. "
613
+ "Respond with EXACTLY ONE fenced code block (```python ... ```), and NOTHING else β€” "
614
+ "no explanation, no preamble, no trailing text. "
615
+ "Code must be valid, self-contained, and PRINT results to stdout so the user can verify. "
616
+ "When the user asks for file operations, write under /home/user/ in the sandbox."
617
+ )
618
+ messages = [
619
+ {"role": "system", "content": sys_prompt},
620
+ {"role": "user", "content": user_message},
621
+ ]
622
+
623
+ # Prefer non-streaming completion against the FIRST working provider β€” more
624
+ # reliable for short code outputs than re-assembling streamed deltas.
625
+ for p in PROVIDERS:
626
+ if not p["key"]:
627
+ continue
628
+ try:
629
+ buf = await _complete_once(p, messages, temperature=0.0, max_tokens=1024)
630
+ m = CODE_BLOCK_RE.search(buf or "")
631
+ if m:
632
+ lang = (m.group(1) or "python").lower()
633
+ if lang in ("py", ""):
634
+ lang = "python"
635
+ return (lang, m.group(2).strip())
636
+ if buf and ("print(" in buf or "import " in buf or "open(" in buf):
637
+ return ("python", buf.strip())
638
+ # else try next provider
639
+ except Exception as e:
640
+ log.warning("code-gen provider failed", provider=p["name"], error=str(e)[:160])
641
+ continue
642
+ return None
643
+
644
+
645
+ async def _complete_once(
646
+ provider: Dict[str, Any],
647
+ messages: List[Dict[str, str]],
648
+ temperature: float = 0.0,
649
+ max_tokens: int = 1024,
650
+ ) -> str:
651
+ """One-shot non-streaming completion (more reliable than concat for code)."""
652
+ name = provider["name"]
653
+ if name == "gemini":
654
+ # gemini supports non-stream via generateContent
655
+ contents: List[Dict[str, Any]] = []
656
+ system_text = ""
657
+ for m in messages:
658
+ if m.get("role") == "system":
659
+ system_text = m.get("content", "")
660
+ continue
661
+ contents.append({"role": "user" if m["role"] == "user" else "model", "parts": [{"text": m["content"]}]})
662
+ body: Dict[str, Any] = {"contents": contents, "generationConfig": {"temperature": temperature, "maxOutputTokens": max_tokens}}
663
+ if system_text:
664
+ body["systemInstruction"] = {"parts": [{"text": system_text}]}
665
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/{provider['model']}:generateContent?key={provider['key']}"
666
+ async with httpx.AsyncClient(timeout=60.0) as cli:
667
+ r = await cli.post(url, json=body)
668
+ r.raise_for_status()
669
+ d = r.json()
670
+ parts = d.get("candidates", [{}])[0].get("content", {}).get("parts", [])
671
+ return "".join(p.get("text", "") for p in parts)
672
+ if name == "anthropic":
673
+ msgs = [m for m in messages if m["role"] != "system"]
674
+ sys_text = next((m["content"] for m in messages if m["role"] == "system"), "")
675
+ payload: Dict[str, Any] = {"model": provider["model"], "max_tokens": max_tokens, "temperature": temperature, "messages": msgs}
676
+ if sys_text:
677
+ payload["system"] = sys_text
678
+ headers = {"x-api-key": provider["key"], "anthropic-version": "2023-06-01", "content-type": "application/json"}
679
+ async with httpx.AsyncClient(timeout=60.0) as cli:
680
+ r = await cli.post(provider["url"], json=payload, headers=headers)
681
+ r.raise_for_status()
682
+ d = r.json()
683
+ return "".join(b.get("text", "") for b in d.get("content", []) if b.get("type") == "text")
684
+ # OpenAI-compatible
685
+ payload = {"model": provider["model"], "messages": messages, "temperature": temperature, "max_tokens": max_tokens}
686
+ headers = {"Authorization": f"Bearer {provider['key']}", "Content-Type": "application/json"}
687
+ async with httpx.AsyncClient(timeout=60.0) as cli:
688
+ r = await cli.post(provider["url"], json=payload, headers=headers)
689
+ r.raise_for_status()
690
+ d = r.json()
691
+ return d["choices"][0]["message"]["content"] or ""
692
+
693
+
694
+ # ─────────────────────────────────────────────────────────────────────────────
695
+ # WebSocket manager (used by /ws/{session_id})
696
+ # ─────────────────────────────────────────────────────────────────────────────
697
+ class WSManager:
698
+ def __init__(self) -> None:
699
+ self._sessions: Dict[str, set] = {}
700
+
701
+ async def connect(self, ws: WebSocket, sid: str) -> None:
702
+ await ws.accept()
703
+ self._sessions.setdefault(sid, set()).add(ws)
704
+
705
+ def disconnect(self, ws: WebSocket, sid: str) -> None:
706
+ self._sessions.get(sid, set()).discard(ws)
707
+
708
+ async def broadcast(self, sid: str, event: Dict[str, Any]) -> None:
709
+ dead = []
710
+ for ws in list(self._sessions.get(sid, [])):
711
+ try:
712
+ await ws.send_json(event)
713
+ except Exception:
714
+ dead.append(ws)
715
+ for ws in dead:
716
+ self._sessions.get(sid, set()).discard(ws)
717
+
718
+ def stats(self) -> Dict[str, Any]:
719
+ return {"sessions": len(self._sessions), "connections": sum(len(s) for s in self._sessions.values())}
720
+
721
+
722
+ ws_manager = WSManager()
723
+
724
+
725
+ # ─────────────────────────────────────────────────────────────────────────────
726
+ # FastAPI app
727
+ # ─────────────────────────────────────────────────────────────────────────────
728
+ @asynccontextmanager
729
+ async def lifespan(app: FastAPI):
730
+ log.info(
731
+ "God Agent OS starting",
732
+ version=VERSION,
733
+ e2b=sandbox_mgr.available,
734
+ providers=active_providers(),
735
+ )
736
+ yield
737
+ log.info("Shutting down β€” killing sandboxes")
738
+ await sandbox_mgr.shutdown_all()
739
+
740
+
741
+ app = FastAPI(
742
+ title="God Agent OS β€” Phase 1",
743
+ version=VERSION,
744
+ description="Stable autonomous agent backend (LLM + E2B + SSE).",
745
+ lifespan=lifespan,
746
+ )
747
+
748
+ app.add_middleware(
749
+ CORSMiddleware,
750
+ allow_origins=["*"],
751
+ allow_credentials=True,
752
+ allow_methods=["*"],
753
+ allow_headers=["*"],
754
+ expose_headers=["*"],
755
+ )
756
+ app.add_middleware(GZipMiddleware, minimum_size=1024)
757
+
758
+
759
+ # ─────────────────────────────────────────────────────────────────────────────
760
+ # Health
761
+ # ─────────────────────────────────────────────────────────────────────────────
762
+ def _health_payload() -> Dict[str, Any]:
763
+ return {
764
+ "status": "healthy",
765
+ "version": VERSION,
766
+ "timestamp": time.time(),
767
+ "e2b": sandbox_mgr.available,
768
+ "ai_providers": active_providers(),
769
+ "ws": ws_manager.stats(),
770
+ "sandboxes": sandbox_mgr.stats(),
771
+ "mode": "phase1_stable",
772
+ "features": {
773
+ "chat_streaming": True,
774
+ "real_execution": True,
775
+ "e2b_sandbox": sandbox_mgr.available,
776
+ "local_fallback": True,
777
+ "intent_routing": True,
778
+ },
779
+ }
780
+
781
+
782
+ @app.get("/")
783
+ async def root():
784
+ return {"service": "god-agent-os", "version": VERSION, "docs": "/api/docs", "health": "/health"}
785
+
786
+
787
+ @app.get("/health")
788
+ async def health():
789
+ return _health_payload()
790
+
791
+
792
+ @app.get("/api/v1/health")
793
+ async def health_v1():
794
+ return _health_payload()
795
+
796
+
797
+ @app.get("/api/v1/system/status")
798
+ async def system_status():
799
+ return _health_payload()
800
+
801
+
802
+ @app.get("/api/v1/ai/stats")
803
+ async def ai_stats():
804
+ stats = {p["name"]: {"available": bool(p["key"]), "model": p["model"]} for p in PROVIDERS}
805
+ return {"stats": stats, "active": next((p["name"] for p in PROVIDERS if p["key"]), None)}
806
+
807
+
808
+ # ─────────────────────────────────────────────────────────────────────────────
809
+ # SSE helpers
810
+ # ─────────────────────────────────────────────────────────────────────────────
811
+ SSE_HEADERS = {
812
+ "Cache-Control": "no-cache, no-transform",
813
+ "X-Accel-Buffering": "no",
814
+ "Connection": "keep-alive",
815
+ }
816
+
817
+
818
+ def sse_pack(event: Dict[str, Any]) -> str:
819
+ return f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
820
+
821
+
822
+ # ─────────────────────────────────────────────────────────────────────────────
823
+ # /api/v1/chat β€” LLM only, real token streaming. NO sandbox.
824
+ # ─────────────────────────────────────────────────────────────────────────────
825
+ @app.post("/api/v1/chat")
826
+ async def chat(request: Request):
827
+ body = await request.json()
828
+ messages: List[Dict[str, str]] = body.get("messages") or []
829
+ session_id: str = body.get("session_id") or uuid.uuid4().hex[:12]
830
+ stream: bool = bool(body.get("stream", True))
831
+ temperature = float(body.get("temperature", 0.7))
832
+ max_tokens = int(body.get("max_tokens", 2048))
833
+
834
+ # If client sent legacy {"message": "..."} β†’ wrap it
835
+ if not messages and body.get("message"):
836
+ messages = [{"role": "user", "content": str(body["message"])}]
837
+
838
+ if not messages:
839
+ raise HTTPException(status_code=400, detail="messages or message required")
840
+
841
+ # Inject system prompt unless caller provided one
842
+ if not any(m.get("role") == "system" for m in messages):
843
+ messages = [{"role": "system", "content": SYSTEM_PROMPT_CHAT}] + messages
844
+
845
+ task_id = uuid.uuid4().hex[:12]
846
+
847
+ if not stream:
848
+ full = ""
849
+ provider_used = None
850
+ async for prov, tok in stream_llm(messages, temperature, max_tokens):
851
+ provider_used = prov
852
+ full += tok
853
+ return {"task_id": task_id, "session_id": session_id, "result": full, "provider": provider_used}
854
+
855
+ async def gen():
856
+ provider_used: Optional[str] = None
857
+ yield sse_pack({"type": "agent_start", "data": {"task_id": task_id, "mode": "chat"}, "session_id": session_id})
858
+ await ws_manager.broadcast(session_id, {"type": "agent_start", "task_id": task_id, "mode": "chat"})
859
+ full = ""
860
+ try:
861
+ async for prov, tok in stream_llm(messages, temperature, max_tokens):
862
+ if provider_used != prov:
863
+ provider_used = prov
864
+ yield sse_pack({"type": "provider", "data": {"provider": prov}, "session_id": session_id})
865
+ full += tok
866
+ evt = {"type": "llm_chunk", "data": {"chunk": tok}, "session_id": session_id}
867
+ yield sse_pack(evt)
868
+ await ws_manager.broadcast(session_id, evt)
869
+ yield sse_pack({"type": "agent_complete", "data": {"task_id": task_id, "tools_called": 0, "iterations": 1}, "session_id": session_id})
870
+ yield sse_pack({"type": "stream_end", "data": {"full_response": full, "task_id": task_id, "provider": provider_used}, "session_id": session_id})
871
+ except Exception as e:
872
+ log.exception("chat stream error")
873
+ yield sse_pack({"type": "error", "data": {"error": str(e)}, "session_id": session_id})
874
+
875
+ return StreamingResponse(gen(), media_type="text/event-stream", headers=SSE_HEADERS)
876
+
877
+
878
+ # ─────────────────────────────────────────────────────────────────────────────
879
+ # /api/v1/execute β€” REAL E2B execution with live stdout/stderr streaming
880
+ # ─────────────────────────────────────────────────────────────────────────────
881
+ @app.post("/api/v1/execute")
882
+ async def execute(request: Request):
883
+ body = await request.json()
884
+
885
+ # Accept multiple schemas:
886
+ # {language, code} ← primary (Phase 1)
887
+ # {tool, args:{code,...}} ← legacy v12 schema
888
+ language: Optional[str] = body.get("language")
889
+ code: Optional[str] = body.get("code")
890
+ if not code and body.get("tool"):
891
+ args = body.get("args") or {}
892
+ tool = body.get("tool")
893
+ if tool in ("execute_python", "python"):
894
+ language = "python"
895
+ code = args.get("code")
896
+ elif tool in ("execute_shell", "shell", "bash"):
897
+ language = "bash"
898
+ code = args.get("command") or args.get("code")
899
+ elif tool == "write_file":
900
+ language = "python"
901
+ path = args.get("path", "/home/user/out.txt").replace("'", "\\'")
902
+ content = (args.get("content") or "").replace("\\", "\\\\").replace("'''", "\\'\\'\\'")
903
+ code = f"open('{path}','w').write('''{content}''')\nprint('wrote', '{path}')"
904
+ elif tool == "read_file":
905
+ language = "python"
906
+ path = args.get("path", "/home/user/out.txt").replace("'", "\\'")
907
+ code = f"print(open('{path}').read())"
908
+ session_id: str = body.get("session_id") or uuid.uuid4().hex[:12]
909
+ timeout = int(body.get("timeout") or 60)
910
+ stream: bool = bool(body.get("stream", True))
911
+
912
+ if not code:
913
+ raise HTTPException(status_code=400, detail="code (or tool+args) required")
914
+ language = (language or "python").lower()
915
+
916
+ task_id = uuid.uuid4().hex[:12]
917
+
918
+ if not stream:
919
+ # Collect everything and return as JSON
920
+ stdout, stderr, exit_code, sandbox_id, backend = "", "", None, None, None
921
+ async for ev in execute_in_sandbox(session_id, language, code, timeout):
922
+ t = ev.get("type")
923
+ if t == "stdout":
924
+ stdout += ev.get("text", "")
925
+ elif t == "stderr":
926
+ stderr += ev.get("text", "")
927
+ elif t == "sandbox_ready":
928
+ sandbox_id = ev.get("sandbox_id"); backend = ev.get("backend")
929
+ elif t == "result":
930
+ exit_code = ev.get("exit_code")
931
+ elif t == "error":
932
+ stderr += ev.get("error", "")
933
+ return {
934
+ "task_id": task_id, "session_id": session_id, "sandbox_id": sandbox_id, "backend": backend,
935
+ "language": language, "stdout": stdout, "stderr": stderr, "exit_code": exit_code,
936
+ "success": exit_code == 0,
937
+ }
938
+
939
+ async def gen():
940
+ yield sse_pack({"type": "agent_start", "data": {"task_id": task_id, "mode": "execute", "language": language}, "session_id": session_id})
941
+ yield sse_pack({"type": "tool_executing", "data": {"tool": f"execute_{language}", "args": {"code": code[:200]}}, "session_id": session_id})
942
+ await ws_manager.broadcast(session_id, {"type": "tool_executing", "tool": f"execute_{language}"})
943
+ stdout_acc = ""
944
+ stderr_acc = ""
945
+ result_meta: Dict[str, Any] = {}
946
+ try:
947
+ async for ev in execute_in_sandbox(session_id, language, code, timeout):
948
+ # Mirror to WS
949
+ await ws_manager.broadcast(session_id, ev)
950
+ if ev["type"] == "stdout":
951
+ stdout_acc += ev["text"]
952
+ elif ev["type"] == "stderr":
953
+ stderr_acc += ev["text"]
954
+ elif ev["type"] == "result":
955
+ result_meta = ev
956
+ yield sse_pack(ev)
957
+
958
+ tool_result = {
959
+ "tool": f"execute_{language}",
960
+ "success": bool(result_meta.get("success")),
961
+ "sandbox_id": result_meta.get("sandbox_id"),
962
+ "raw": {
963
+ "stdout": stdout_acc[-2000:],
964
+ "stderr": stderr_acc[-2000:],
965
+ "exit_code": result_meta.get("exit_code"),
966
+ "_duration_ms": result_meta.get("duration_ms"),
967
+ "backend": result_meta.get("backend"),
968
+ },
969
+ "result": (stdout_acc or stderr_acc)[-1500:],
970
+ }
971
+ yield sse_pack({"type": "tool_result", "data": tool_result, "session_id": session_id})
972
+ yield sse_pack({"type": "agent_complete", "data": {"task_id": task_id, "tools_called": 1, "iterations": 1}, "session_id": session_id})
973
+ yield sse_pack({"type": "stream_end", "data": {"task_id": task_id, "full_response": stdout_acc[-2000:]}, "session_id": session_id})
974
+ except Exception as e:
975
+ log.exception("execute stream error")
976
+ yield sse_pack({"type": "error", "data": {"error": str(e)}, "session_id": session_id})
977
+
978
+ return StreamingResponse(gen(), media_type="text/event-stream", headers=SSE_HEADERS)
979
+
980
+
981
+ # ─────────────────────────────────────────────────────────────────────────────
982
+ # /api/v1/agent β€” intent-routed: chat OR execute
983
+ # ─────────────────────────────────────────────────────────────────────────────
984
+ @app.post("/api/v1/agent")
985
+ async def agent(request: Request):
986
+ body = await request.json()
987
+ message: str = (body.get("message") or "").strip()
988
+ if not message and body.get("messages"):
989
+ last_user = next((m["content"] for m in reversed(body["messages"]) if m.get("role") == "user"), "")
990
+ message = (last_user or "").strip()
991
+ session_id: str = body.get("session_id") or uuid.uuid4().hex[:12]
992
+ force = (body.get("intent") or "").lower() # 'chat' | 'execute' | ''
993
+ if not message:
994
+ raise HTTPException(status_code=400, detail="message required")
995
+
996
+ intent = {"intent": force, "reason": "forced"} if force in ("chat", "execute") else detect_intent(message)
997
+ task_id = uuid.uuid4().hex[:12]
998
+
999
+ async def gen():
1000
+ yield sse_pack({"type": "agent_start", "data": {"task_id": task_id, "intent": intent}, "session_id": session_id})
1001
+ await ws_manager.broadcast(session_id, {"type": "agent_start", "task_id": task_id, "intent": intent})
1002
+
1003
+ if intent["intent"] == "chat":
1004
+ messages = [
1005
+ {"role": "system", "content": SYSTEM_PROMPT_CHAT},
1006
+ {"role": "user", "content": message},
1007
+ ]
1008
+ full = ""
1009
+ provider_used = None
1010
+ try:
1011
+ async for prov, tok in stream_llm(messages):
1012
+ if prov != provider_used:
1013
+ provider_used = prov
1014
+ yield sse_pack({"type": "provider", "data": {"provider": prov}, "session_id": session_id})
1015
+ full += tok
1016
+ evt = {"type": "llm_chunk", "data": {"chunk": tok}, "session_id": session_id}
1017
+ yield sse_pack(evt)
1018
+ await ws_manager.broadcast(session_id, evt)
1019
+ except Exception as e:
1020
+ yield sse_pack({"type": "error", "data": {"error": str(e)}, "session_id": session_id})
1021
+ yield sse_pack({"type": "agent_complete", "data": {"task_id": task_id, "tools_called": 0, "iterations": 1}, "session_id": session_id})
1022
+ yield sse_pack({"type": "stream_end", "data": {"task_id": task_id, "full_response": full, "provider": provider_used}, "session_id": session_id})
1023
+ return
1024
+
1025
+ # ── EXECUTE branch ────────────────────────────────────────────────
1026
+ lang = intent.get("language") or "python"
1027
+ code = intent.get("code")
1028
+ if not code:
1029
+ # Generate code with LLM
1030
+ yield sse_pack({"type": "thinking_start", "data": {"iteration": 1, "phase": "code_generation"}, "session_id": session_id})
1031
+ gen_result = await llm_generate_code(message)
1032
+ if not gen_result:
1033
+ yield sse_pack({"type": "error", "data": {"error": "could not generate executable code"}, "session_id": session_id})
1034
+ yield sse_pack({"type": "stream_end", "data": {"task_id": task_id}, "session_id": session_id})
1035
+ return
1036
+ lang, code = gen_result
1037
+ yield sse_pack({"type": "llm_chunk", "data": {"chunk": f"```{lang}\n{code}\n```\n"}, "session_id": session_id})
1038
+
1039
+ yield sse_pack({"type": "tool_executing", "data": {"tool": f"execute_{lang}", "args": {"code": code[:200]}}, "session_id": session_id})
1040
+
1041
+ stdout_acc, stderr_acc, meta = "", "", {}
1042
+ async for ev in execute_in_sandbox(session_id, lang, code, timeout=60):
1043
+ await ws_manager.broadcast(session_id, ev)
1044
+ if ev["type"] == "stdout":
1045
+ stdout_acc += ev["text"]
1046
+ elif ev["type"] == "stderr":
1047
+ stderr_acc += ev["text"]
1048
+ elif ev["type"] == "result":
1049
+ meta = ev
1050
+ yield sse_pack(ev)
1051
+
1052
+ yield sse_pack({
1053
+ "type": "tool_result",
1054
+ "data": {
1055
+ "tool": f"execute_{lang}",
1056
+ "success": bool(meta.get("success")),
1057
+ "sandbox_id": meta.get("sandbox_id"),
1058
+ "raw": {"stdout": stdout_acc[-2000:], "stderr": stderr_acc[-2000:], "exit_code": meta.get("exit_code"), "backend": meta.get("backend")},
1059
+ "result": (stdout_acc or stderr_acc)[-1500:],
1060
+ },
1061
+ "session_id": session_id,
1062
+ })
1063
+ yield sse_pack({"type": "agent_complete", "data": {"task_id": task_id, "tools_called": 1, "iterations": 1}, "session_id": session_id})
1064
+ yield sse_pack({"type": "stream_end", "data": {"task_id": task_id, "full_response": stdout_acc[-2000:]}, "session_id": session_id})
1065
+
1066
+ return StreamingResponse(gen(), media_type="text/event-stream", headers=SSE_HEADERS)
1067
+
1068
+
1069
+ # ─────────────────────────────────────────────────────────────────────────────
1070
+ # Legacy aliases (so existing frontend keeps working without redeploy)
1071
+ # ─────────────────────────────────────────────────────────────────────────────
1072
+ @app.post("/api/v1/orchestrate")
1073
+ async def orchestrate_alias(request: Request):
1074
+ return await agent(request)
1075
+
1076
+
1077
+ @app.post("/api/v1/kernel/orchestrate")
1078
+ async def kernel_orchestrate_alias(request: Request):
1079
+ """Legacy non-streaming endpoint used by ChatPanel.tsx."""
1080
+ body = await request.json()
1081
+ message = (body.get("message") or "").strip()
1082
+ session_id = body.get("session_id") or uuid.uuid4().hex[:12]
1083
+ if not message:
1084
+ raise HTTPException(status_code=400, detail="message required")
1085
+ intent = detect_intent(message)
1086
+
1087
+ if intent["intent"] == "chat":
1088
+ messages = [
1089
+ {"role": "system", "content": SYSTEM_PROMPT_CHAT},
1090
+ {"role": "user", "content": message},
1091
+ ]
1092
+ full, prov = "", None
1093
+ async for p, tok in stream_llm(messages):
1094
+ full += tok; prov = p
1095
+ return {"session_id": session_id, "response": full, "provider": prov, "intent": "chat"}
1096
+
1097
+ # execute path
1098
+ lang = intent.get("language") or "python"
1099
+ code = intent.get("code")
1100
+ if not code:
1101
+ g = await llm_generate_code(message)
1102
+ if not g:
1103
+ return {"session_id": session_id, "response": "Could not generate code.", "intent": "execute", "success": False}
1104
+ lang, code = g
1105
+ stdout, stderr, meta = "", "", {}
1106
+ async for ev in execute_in_sandbox(session_id, lang, code, timeout=60):
1107
+ if ev["type"] == "stdout": stdout += ev["text"]
1108
+ elif ev["type"] == "stderr": stderr += ev["text"]
1109
+ elif ev["type"] == "result": meta = ev
1110
+ return {
1111
+ "session_id": session_id,
1112
+ "response": (stdout or stderr)[-3000:],
1113
+ "intent": "execute",
1114
+ "code": code,
1115
+ "language": lang,
1116
+ "stdout": stdout, "stderr": stderr,
1117
+ "exit_code": meta.get("exit_code"),
1118
+ "sandbox_id": meta.get("sandbox_id"),
1119
+ "backend": meta.get("backend"),
1120
+ "success": bool(meta.get("success")),
1121
+ }
1122
+
1123
+
1124
+ @app.post("/api/v1/chat/stream")
1125
+ async def chat_stream_alias(request: Request):
1126
+ body = await request.json()
1127
+ body["stream"] = True
1128
+ async def receive():
1129
+ return {"type": "http.request", "body": json.dumps(body).encode()}
1130
+ request._receive = receive # type: ignore
1131
+ return await chat(request)
1132
+
1133
+
1134
+ # ─────────────────────────────────────────────────────────────────────────────
1135
+ # Sandbox lifecycle
1136
+ # ─────────────────────────────────────────────────────────────────────────────
1137
+ @app.get("/api/v1/sandbox/{session_id}")
1138
+ async def sandbox_info(session_id: str):
1139
+ return {
1140
+ "session_id": session_id,
1141
+ "sandbox": sandbox_mgr.info(session_id),
1142
+ "e2b_configured": sandbox_mgr.available,
1143
+ }
1144
+
1145
+
1146
+ @app.delete("/api/v1/sandbox/{session_id}")
1147
+ async def sandbox_kill(session_id: str):
1148
+ ok = await sandbox_mgr.kill(session_id)
1149
+ return {"status": "closed" if ok else "not_found", "session_id": session_id}
1150
+
1151
+
1152
+ # Stub endpoints used by old frontend pages β€” return empty/healthy responses so UI doesn't error
1153
+ @app.get("/api/v1/agents")
1154
+ async def list_agents():
1155
+ return {"agents": [], "count": 0}
1156
+
1157
+ @app.get("/api/v1/tasks/")
1158
+ async def list_tasks():
1159
+ return {"tasks": [], "count": 0}
1160
+
1161
+ @app.get("/api/v1/spaces")
1162
+ async def list_spaces():
1163
+ return {"spaces": [], "count": 0}
1164
+
1165
+ @app.get("/api/v1/connectors")
1166
+ async def list_connectors():
1167
+ return {"connectors": [], "count": 0}
1168
+
1169
+ @app.get("/api/v1/memory/")
1170
+ async def memory_list():
1171
+ return {"items": [], "count": 0}
1172
+
1173
+ @app.get("/api/v1/ai/pool-status")
1174
+ async def pool_status():
1175
+ return {"pools": {p["name"]: {"keys": 1 if p["key"] else 0, "model": p["model"]} for p in PROVIDERS}}
1176
+
1177
+
1178
+ # ─────────────────────────────────────────────────────────────────────────────
1179
+ # WebSocket β€” mirror of SSE events; also accepts {"type":"message"|"execute"|"ping"}
1180
+ # ─────────────────────────────────────────────────────────────────────────────
1181
+ @app.websocket("/ws/{session_id}")
1182
+ async def websocket_endpoint(ws: WebSocket, session_id: str):
1183
+ await ws_manager.connect(ws, session_id)
1184
+ try:
1185
+ # Keep-alive loop
1186
+ while True:
1187
+ try:
1188
+ data = await asyncio.wait_for(ws.receive_json(), timeout=30.0)
1189
+ except asyncio.TimeoutError:
1190
+ await ws.send_json({"type": "ping", "ts": time.time()})
1191
+ continue
1192
+ except WebSocketDisconnect:
1193
+ break
1194
+
1195
+ event_type = data.get("type")
1196
+ if event_type == "ping":
1197
+ await ws.send_json({"type": "pong", "ts": time.time()})
1198
+ elif event_type == "message":
1199
+ msg = data.get("message") or ""
1200
+ # Run agent and broadcast events
1201
+ async def _run():
1202
+ intent = detect_intent(msg)
1203
+ if intent["intent"] == "chat":
1204
+ messages = [{"role": "system", "content": SYSTEM_PROMPT_CHAT}, {"role": "user", "content": msg}]
1205
+ async for prov, tok in stream_llm(messages):
1206
+ await ws_manager.broadcast(session_id, {"type": "llm_chunk", "data": {"chunk": tok}})
1207
+ await ws_manager.broadcast(session_id, {"type": "stream_end", "data": {}})
1208
+ else:
1209
+ lang = intent.get("language") or "python"
1210
+ code = intent.get("code")
1211
+ if not code:
1212
+ g = await llm_generate_code(msg)
1213
+ if g:
1214
+ lang, code = g
1215
+ else:
1216
+ await ws_manager.broadcast(session_id, {"type": "error", "data": {"error": "no code"}})
1217
+ return
1218
+ async for ev in execute_in_sandbox(session_id, lang, code):
1219
+ await ws_manager.broadcast(session_id, ev)
1220
+ asyncio.create_task(_run())
1221
+ elif event_type == "execute":
1222
+ lang = (data.get("language") or "python").lower()
1223
+ code = data.get("code") or ""
1224
+ if not code:
1225
+ await ws.send_json({"type": "error", "data": {"error": "code required"}})
1226
+ continue
1227
+ async def _exec():
1228
+ async for ev in execute_in_sandbox(session_id, lang, code):
1229
+ await ws_manager.broadcast(session_id, ev)
1230
+ asyncio.create_task(_exec())
1231
+ except WebSocketDisconnect:
1232
+ pass
1233
+ finally:
1234
+ ws_manager.disconnect(ws, session_id)
1235
+
1236
+
1237
+ # Dev entry
1238
+ if __name__ == "__main__":
1239
+ import uvicorn
1240
+ uvicorn.run("app:app", host="0.0.0.0", port=int(os.environ.get("PORT", 7860)), reload=False)
connectors/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # Connector System
2
+ from .manager import ConnectorManager
3
+
4
+ __all__ = ["ConnectorManager"]
connectors/manager.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Connector Manager β€” Manus-style connector ecosystem
3
+ Manages OAuth tokens, connection state, API access
4
+ """
5
+ import json
6
+ import os
7
+ import time
8
+ from typing import Dict, List, Optional
9
+ import structlog
10
+
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/__init__.py ADDED
File without changes
core/agent.py ADDED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Agent Core β€” Planner + Executor + Self-Heal Loop
3
+ LLM-powered with OpenAI/Anthropic support, streaming tokens
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import os
9
+ import time
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ import httpx
13
+ import structlog
14
+
15
+ from core.models import TaskPlan, TaskStep
16
+ from api.websocket_manager import WebSocketManager
17
+ from memory.db import save_memory, get_history, search_memory
18
+
19
+ log = structlog.get_logger()
20
+
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.
28
+ You can plan, code, debug, refactor, test, and deploy software autonomously.
29
+ You think step-by-step, write production-quality code, and self-heal on errors.
30
+ Always respond in structured JSON when asked for plans or structured output.
31
+ """
32
+
33
+ PLANNER_PROMPT = """You are a senior software architect. Given a goal, produce a detailed execution plan.
34
+
35
+ Respond ONLY with valid JSON:
36
+ {
37
+ "steps": [
38
+ {
39
+ "name": "Step name",
40
+ "description": "What this step does",
41
+ "tool": "code|shell|file|browser|github|memory|search|test|none",
42
+ "estimated_seconds": 10
43
+ }
44
+ ],
45
+ "estimated_duration": 60,
46
+ "tools_needed": ["code", "shell"]
47
+ }
48
+
49
+ Goal: {goal}
50
+ Context: {context}
51
+ """
52
+
53
+
54
+ class AgentCore:
55
+ def __init__(self, ws_manager: WebSocketManager):
56
+ self.ws = ws_manager
57
+ self.model = DEFAULT_MODEL
58
+
59
+ # ─── LLM Call (with streaming) ─────────────────────────────────────────────
60
+
61
+ async def llm_stream(
62
+ self,
63
+ messages: List[Dict],
64
+ task_id: str = "",
65
+ session_id: str = "",
66
+ model: str = "",
67
+ temperature: float = 0.7,
68
+ max_tokens: int = 4096,
69
+ ) -> str:
70
+ """Stream LLM tokens, emitting llm_chunk events via WebSocket."""
71
+ model = model or self.model
72
+ full_text = ""
73
+
74
+ if OPENAI_API_KEY:
75
+ full_text = await self._openai_stream(
76
+ messages, task_id, session_id, model, temperature, max_tokens
77
+ )
78
+ elif ANTHROPIC_API_KEY:
79
+ full_text = await self._anthropic_stream(
80
+ messages, task_id, session_id, temperature, max_tokens
81
+ )
82
+ else:
83
+ # Demo mode β€” simulate streaming
84
+ full_text = await self._demo_stream(messages, task_id, session_id)
85
+
86
+ return full_text
87
+
88
+ async def _openai_stream(
89
+ self, messages, task_id, session_id, model, temperature, max_tokens
90
+ ) -> str:
91
+ full_text = ""
92
+ headers = {
93
+ "Authorization": f"Bearer {OPENAI_API_KEY}",
94
+ "Content-Type": "application/json",
95
+ }
96
+ payload = {
97
+ "model": model,
98
+ "messages": messages,
99
+ "stream": True,
100
+ "temperature": temperature,
101
+ "max_tokens": max_tokens,
102
+ }
103
+ async with httpx.AsyncClient(timeout=120) as client:
104
+ async with client.stream(
105
+ "POST", f"{OPENAI_BASE_URL}/chat/completions",
106
+ headers=headers, json=payload
107
+ ) as resp:
108
+ resp.raise_for_status()
109
+ async for line in resp.aiter_lines():
110
+ if not line.startswith("data:"):
111
+ continue
112
+ chunk = line[6:].strip()
113
+ if chunk == "[DONE]":
114
+ break
115
+ try:
116
+ data = json.loads(chunk)
117
+ delta = data["choices"][0]["delta"].get("content", "")
118
+ if delta:
119
+ full_text += delta
120
+ if task_id:
121
+ await self.ws.emit(task_id, "llm_chunk", {
122
+ "chunk": delta,
123
+ "accumulated": len(full_text),
124
+ }, session_id=session_id)
125
+ if session_id and not task_id:
126
+ await self.ws.emit_chat(session_id, "llm_chunk", {
127
+ "chunk": delta,
128
+ })
129
+ except Exception:
130
+ pass
131
+ return full_text
132
+
133
+ async def _anthropic_stream(
134
+ self, messages, task_id, session_id, temperature, max_tokens
135
+ ) -> str:
136
+ full_text = ""
137
+ system = ""
138
+ filtered = []
139
+ for m in messages:
140
+ if m["role"] == "system":
141
+ system = m["content"]
142
+ else:
143
+ filtered.append(m)
144
+ headers = {
145
+ "x-api-key": ANTHROPIC_API_KEY,
146
+ "anthropic-version": "2023-06-01",
147
+ "Content-Type": "application/json",
148
+ }
149
+ payload = {
150
+ "model": "claude-3-5-sonnet-20241022",
151
+ "max_tokens": max_tokens,
152
+ "messages": filtered,
153
+ "stream": True,
154
+ }
155
+ if system:
156
+ payload["system"] = system
157
+ async with httpx.AsyncClient(timeout=120) as client:
158
+ async with client.stream(
159
+ "POST", "https://api.anthropic.com/v1/messages",
160
+ headers=headers, json=payload
161
+ ) as resp:
162
+ resp.raise_for_status()
163
+ async for line in resp.aiter_lines():
164
+ if not line.startswith("data:"):
165
+ continue
166
+ try:
167
+ data = json.loads(line[5:].strip())
168
+ if data.get("type") == "content_block_delta":
169
+ delta = data["delta"].get("text", "")
170
+ if delta:
171
+ full_text += delta
172
+ if task_id:
173
+ await self.ws.emit(task_id, "llm_chunk", {
174
+ "chunk": delta,
175
+ }, session_id=session_id)
176
+ if session_id and not task_id:
177
+ await self.ws.emit_chat(session_id, "llm_chunk", {
178
+ "chunk": delta,
179
+ })
180
+ except Exception:
181
+ pass
182
+ return full_text
183
+
184
+ async def _demo_stream(self, messages, task_id, session_id) -> str:
185
+ """Demo mode β€” simulate LLM streaming without API key."""
186
+ last_user = next(
187
+ (m["content"] for m in reversed(messages) if m["role"] == "user"), "Hello"
188
+ )
189
+ response = (
190
+ f"πŸ€– **Devin Agent** (Demo Mode)\n\n"
191
+ f"I received your request: *{last_user[:100]}*\n\n"
192
+ f"To enable real AI responses, set `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` in your environment.\n\n"
193
+ f"**What I can do with a real API key:**\n"
194
+ f"- πŸ“‹ Generate detailed execution plans\n"
195
+ f"- πŸ’» Write and execute code autonomously\n"
196
+ f"- πŸ”§ Debug and self-heal on errors\n"
197
+ f"- πŸ™ Manage GitHub repos autonomously\n"
198
+ f"- 🧠 Remember long-running project context\n"
199
+ f"- πŸš€ Deploy applications automatically\n"
200
+ )
201
+ full_text = ""
202
+ for word in response.split():
203
+ chunk = word + " "
204
+ full_text += chunk
205
+ await asyncio.sleep(0.03)
206
+ if task_id:
207
+ await self.ws.emit(task_id, "llm_chunk", {
208
+ "chunk": chunk,
209
+ "demo": True,
210
+ }, session_id=session_id)
211
+ if session_id and not task_id:
212
+ await self.ws.emit_chat(session_id, "llm_chunk", {
213
+ "chunk": chunk,
214
+ "demo": True,
215
+ })
216
+ return full_text
217
+
218
+ # ─── Planning ──────────────────────────────────────────────────────────────
219
+
220
+ async def plan(self, goal: str, task_id: str, session_id: str = "") -> TaskPlan:
221
+ """Generate a structured execution plan."""
222
+ # Get context from memory
223
+ memories = await search_memory(goal[:50], session_id=session_id)
224
+ context = "\n".join([m["content"][:200] for m in memories[:3]])
225
+
226
+ prompt = PLANNER_PROMPT.format(goal=goal, context=context or "No prior context")
227
+
228
+ messages = [
229
+ {"role": "system", "content": SYSTEM_PROMPT},
230
+ {"role": "user", "content": prompt},
231
+ ]
232
+
233
+ if not OPENAI_API_KEY and not ANTHROPIC_API_KEY:
234
+ # Demo plan
235
+ return self._demo_plan(goal)
236
+
237
+ raw = await self.llm_stream(messages, task_id=task_id, session_id=session_id)
238
+
239
+ # Extract JSON from response
240
+ try:
241
+ # Find JSON block
242
+ start = raw.find("{")
243
+ end = raw.rfind("}") + 1
244
+ if start >= 0 and end > start:
245
+ data = json.loads(raw[start:end])
246
+ else:
247
+ data = json.loads(raw)
248
+
249
+ steps = []
250
+ for i, s in enumerate(data.get("steps", [])):
251
+ steps.append(TaskStep(
252
+ name=s.get("name", f"Step {i+1}"),
253
+ description=s.get("description", ""),
254
+ tool=s.get("tool", "none"),
255
+ ))
256
+
257
+ return TaskPlan(
258
+ goal=goal,
259
+ steps=steps if steps else [TaskStep(name="Execute goal", description=goal, tool="code")],
260
+ estimated_duration=data.get("estimated_duration", 60),
261
+ tools_needed=data.get("tools_needed", []),
262
+ )
263
+ except Exception as e:
264
+ log.warning("Plan parse failed, using fallback", error=str(e))
265
+ return self._demo_plan(goal)
266
+
267
+ def _demo_plan(self, goal: str) -> TaskPlan:
268
+ """Fallback plan for demo mode."""
269
+ steps = [
270
+ TaskStep(name="Analyze Requirements", description=f"Analyze: {goal[:60]}", tool="none"),
271
+ TaskStep(name="Design Solution", description="Design the solution architecture", tool="none"),
272
+ TaskStep(name="Implement", description="Write the implementation code", tool="code"),
273
+ TaskStep(name="Test", description="Test the implementation", tool="test"),
274
+ TaskStep(name="Document", description="Write documentation", tool="none"),
275
+ ]
276
+ return TaskPlan(
277
+ goal=goal,
278
+ steps=steps,
279
+ estimated_duration=120,
280
+ tools_needed=["code", "test"],
281
+ )
282
+
283
+ # ─── Step Execution ────────────────────────────────────────────────────────
284
+
285
+ async def execute_step(
286
+ self,
287
+ step: TaskStep,
288
+ task_id: str,
289
+ session_id: str = "",
290
+ context: Dict = {},
291
+ ) -> str:
292
+ """Execute a single step using the appropriate tool."""
293
+ from tools.executor import ToolExecutor
294
+ executor = ToolExecutor(self.ws)
295
+
296
+ await self.ws.emit(task_id, "tool_called", {
297
+ "tool": step.tool or "none",
298
+ "step": step.name,
299
+ "description": step.description,
300
+ }, session_id=session_id)
301
+
302
+ try:
303
+ result = await executor.run(
304
+ tool=step.tool or "none",
305
+ task=step.description,
306
+ goal=context.get("goal", ""),
307
+ previous=context.get("previous_results", []),
308
+ task_id=task_id,
309
+ session_id=session_id,
310
+ )
311
+ await self.ws.emit(task_id, "tool_result", {
312
+ "tool": step.tool,
313
+ "step": step.name,
314
+ "result": str(result)[:500],
315
+ "success": True,
316
+ }, session_id=session_id)
317
+ return result
318
+ except Exception as e:
319
+ await self.ws.emit(task_id, "tool_result", {
320
+ "tool": step.tool,
321
+ "step": step.name,
322
+ "error": str(e),
323
+ "success": False,
324
+ }, session_id=session_id)
325
+ return f"Error in {step.name}: {str(e)}"
326
+
327
+ # ─── Finalize ──────────────────────────────────────────────────────────────
328
+
329
+ async def finalize(
330
+ self,
331
+ goal: str,
332
+ steps: List[TaskStep],
333
+ results: List[str],
334
+ task_id: str,
335
+ session_id: str = "",
336
+ ) -> str:
337
+ """Compile final result summary."""
338
+ steps_summary = "\n".join([
339
+ f"- {s.name}: {r[:200]}" for s, r in zip(steps, results)
340
+ ])
341
+ messages = [
342
+ {"role": "system", "content": SYSTEM_PROMPT},
343
+ {"role": "user", "content": (
344
+ f"Summarize the completion of this goal:\n"
345
+ f"Goal: {goal}\n\n"
346
+ f"Steps completed:\n{steps_summary}\n\n"
347
+ f"Write a concise success summary with key outcomes."
348
+ )},
349
+ ]
350
+ result = await self.llm_stream(messages, task_id=task_id, session_id=session_id)
351
+ return result or f"βœ… Completed: {goal}"
352
+
353
+ # ─── Chat ──────────────────────────────────────────────────────────────────
354
+
355
+ async def stream_chat(self, session_id: str, user_message: str):
356
+ """Stream a conversational chat response."""
357
+ # Save user message to memory
358
+ await save_memory(
359
+ content=user_message,
360
+ memory_type="conversation",
361
+ session_id=session_id,
362
+ key="user_message",
363
+ )
364
+
365
+ # Get conversation history
366
+ history = await get_history(session_id, limit=10)
367
+ messages = [{"role": "system", "content": SYSTEM_PROMPT}]
368
+ for h in reversed(history[-10:]):
369
+ messages.append({"role": "user", "content": h["content"]})
370
+
371
+ messages.append({"role": "user", "content": user_message})
372
+
373
+ await self.ws.emit_chat(session_id, "stream_start", {
374
+ "status": "generating",
375
+ })
376
+
377
+ response = await self.llm_stream(messages, session_id=session_id)
378
+
379
+ # Save assistant response to memory
380
+ await save_memory(
381
+ content=response,
382
+ memory_type="conversation",
383
+ session_id=session_id,
384
+ key="assistant_response",
385
+ )
386
+
387
+ await self.ws.emit_chat(session_id, "stream_end", {
388
+ "full_response": response,
389
+ "status": "complete",
390
+ })
391
+
392
+ return response
core/models.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Pydantic Models β€” Task, Chat, Memory, GitHub
3
+ """
4
+
5
+ import time
6
+ import uuid
7
+ from enum import Enum
8
+ from typing import Any, Dict, List, Optional
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ def gen_id(prefix: str = "") -> str:
13
+ return f"{prefix}{uuid.uuid4().hex[:12]}"
14
+
15
+
16
+ # ─── Enums ─────────────────────────────────────────────────────────────────────
17
+
18
+ class TaskStatus(str, Enum):
19
+ queued = "queued"
20
+ initializing = "initializing"
21
+ planning = "planning"
22
+ executing = "executing"
23
+ streaming = "streaming"
24
+ waiting_input = "waiting_input"
25
+ retrying = "retrying"
26
+ finalizing = "finalizing"
27
+ completed = "completed"
28
+ failed = "failed"
29
+ cancelled = "cancelled"
30
+
31
+
32
+ class EventType(str, Enum):
33
+ task_created = "task_created"
34
+ task_queued = "task_queued"
35
+ task_started = "task_started"
36
+ plan_generated = "plan_generated"
37
+ step_started = "step_started"
38
+ step_progress = "step_progress"
39
+ tool_called = "tool_called"
40
+ tool_result = "tool_result"
41
+ llm_chunk = "llm_chunk"
42
+ memory_updated = "memory_updated"
43
+ retry_attempt = "retry_attempt"
44
+ step_completed = "step_completed"
45
+ warning = "warning"
46
+ error = "error"
47
+ task_completed = "task_completed"
48
+ task_failed = "task_failed"
49
+ heartbeat = "heartbeat"
50
+
51
+
52
+ class MemoryType(str, Enum):
53
+ conversation = "conversation"
54
+ task = "task"
55
+ project = "project"
56
+ execution = "execution"
57
+ tool = "tool"
58
+ error = "error"
59
+ repo = "repo"
60
+ planning = "planning"
61
+
62
+
63
+ # ─── Task Models ───────────────────────────────────────────────────────────────
64
+
65
+ class TaskCreateRequest(BaseModel):
66
+ goal: str = Field(..., min_length=1, max_length=10000, description="What should the agent do?")
67
+ session_id: str = Field(default_factory=lambda: gen_id("sess_"))
68
+ project_id: str = Field(default="")
69
+ stream: bool = True
70
+ metadata: Dict[str, Any] = Field(default_factory=dict)
71
+ github_repo: Optional[str] = None
72
+ auto_commit: bool = False
73
+
74
+
75
+ class TaskStep(BaseModel):
76
+ id: str = Field(default_factory=lambda: gen_id("step_"))
77
+ name: str
78
+ description: str = ""
79
+ tool: Optional[str] = None
80
+ status: str = "pending"
81
+ output: Optional[str] = None
82
+ error: Optional[str] = None
83
+ started_at: Optional[float] = None
84
+ completed_at: Optional[float] = None
85
+ duration_ms: Optional[float] = None
86
+
87
+
88
+ class TaskPlan(BaseModel):
89
+ goal: str
90
+ steps: List[TaskStep]
91
+ estimated_duration: int = 0
92
+ tools_needed: List[str] = []
93
+ created_at: float = Field(default_factory=time.time)
94
+
95
+
96
+ class TaskResponse(BaseModel):
97
+ id: str
98
+ goal: str
99
+ status: TaskStatus
100
+ session_id: str
101
+ project_id: str
102
+ plan: Optional[TaskPlan] = None
103
+ result: Optional[str] = None
104
+ error: Optional[str] = None
105
+ created_at: float
106
+ started_at: Optional[float] = None
107
+ completed_at: Optional[float] = None
108
+ retry_count: int = 0
109
+ stream_url: Optional[str] = None
110
+ ws_url: Optional[str] = None
111
+
112
+
113
+ class TaskCancelRequest(BaseModel):
114
+ reason: str = "User cancelled"
115
+
116
+
117
+ class TaskRetryRequest(BaseModel):
118
+ reset_state: bool = True
119
+
120
+
121
+ # ─── Chat Models ───────────────────────────────────────────────────────────────
122
+
123
+ class ChatMessage(BaseModel):
124
+ role: str = Field(..., pattern="^(user|assistant|system)$")
125
+ content: str
126
+ timestamp: float = Field(default_factory=time.time)
127
+
128
+
129
+ class ChatRequest(BaseModel):
130
+ messages: List[ChatMessage]
131
+ session_id: str = Field(default_factory=lambda: gen_id("sess_"))
132
+ project_id: str = ""
133
+ stream: bool = True
134
+ model: str = "gpt-4o"
135
+ temperature: float = 0.7
136
+ max_tokens: int = 4096
137
+ system_prompt: Optional[str] = None
138
+
139
+
140
+ class GoalRequest(BaseModel):
141
+ goal: str = Field(..., min_length=1, max_length=10000)
142
+ session_id: str = Field(default_factory=lambda: gen_id("sess_"))
143
+ project_id: str = ""
144
+ stream: bool = True
145
+ auto_execute: bool = True
146
+ github_repo: Optional[str] = None
147
+
148
+
149
+ # ─── Memory Models ─────────────────────────────────────────────────────────────
150
+
151
+ class MemorySaveRequest(BaseModel):
152
+ content: str
153
+ memory_type: MemoryType
154
+ session_id: str = ""
155
+ project_id: str = ""
156
+ key: str = ""
157
+ metadata: Dict[str, Any] = {}
158
+
159
+
160
+ class MemorySearchRequest(BaseModel):
161
+ query: str
162
+ session_id: str = ""
163
+ project_id: str = ""
164
+ limit: int = 20
165
+
166
+
167
+ # ─── GitHub Models ───────────────��─────────────────────────────────────────────
168
+
169
+ class GitHubCloneRequest(BaseModel):
170
+ repo_url: str
171
+ branch: str = "main"
172
+ local_path: Optional[str] = None
173
+
174
+
175
+ class GitHubCreateRepoRequest(BaseModel):
176
+ name: str
177
+ description: str = ""
178
+ private: bool = False
179
+ auto_init: bool = True
180
+
181
+
182
+ class GitHubCommitRequest(BaseModel):
183
+ repo: str
184
+ branch: str = "main"
185
+ files: Dict[str, str] # path β†’ content
186
+ message: str
187
+ create_branch: bool = False
188
+
189
+
190
+ class GitHubPRRequest(BaseModel):
191
+ repo: str
192
+ title: str
193
+ body: str = ""
194
+ head: str
195
+ base: str = "main"
196
+ draft: bool = False
197
+
198
+
199
+ class GitHubIssueRequest(BaseModel):
200
+ repo: str
201
+ title: str
202
+ body: str = ""
203
+ labels: List[str] = []
204
+
205
+
206
+ # ─── Event Schema (unified) ────────────────────────────────────────────────────
207
+
208
+ class StreamEvent(BaseModel):
209
+ type: str
210
+ task_id: str = ""
211
+ session_id: str = ""
212
+ timestamp: float = Field(default_factory=time.time)
213
+ data: Dict[str, Any] = {}