File size: 5,579 Bytes
19c4f92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""
GOD AGENT OS β€” Clean Phase-1 Backend
=====================================
Stability-first rebuild. One pipeline, no dead code.

Endpoints:
  GET  /health                       β€” real status + provider availability
  GET  /api/v1/health                β€” alias
  GET  /api/v1/system/status         β€” extended status
  POST /api/v1/chat            (SSE) β€” LLM-only streaming chat (NO sandbox)
  POST /api/v1/execute         (SSE) β€” real E2B sandbox execution
  POST /api/v1/agent           (SSE) β€” intent-routed: chat OR execute
  POST /api/v1/orchestrate     (SSE) β€” alias of /api/v1/agent (legacy)
  POST /api/v1/kernel/orchestrate    β€” alias of /api/v1/agent (legacy, frontend)
  GET  /api/v1/sandbox/{session}     β€” sandbox info
  DELETE /api/v1/sandbox/{session}   β€” kill sandbox
  WS   /ws/{session_id}              β€” same events over WebSocket

Execution rules (per spec):
  * Normal chat (greetings, explanations, brainstorming) β†’ LLM only, no E2B
  * Execution intent (code, shell, files, packages) β†’ real E2B sandbox
  * Intent detection: keyword heuristic + LLM tie-breaker (cheap, sync)
"""

from __future__ import annotations

import asyncio
import json
import os
import re
import time
import uuid
from contextlib import asynccontextmanager
from typing import Any, AsyncGenerator, Dict, List, Optional, Tuple

import httpx
import structlog
from fastapi import FastAPI, HTTPException, Request, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.responses import JSONResponse, StreamingResponse

# ─────────────────────────────────────────────────────────────────────────────
# Logging
# ─────────────────────────────────────────────────────────────────────────────
structlog.configure(
    processors=[
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.stdlib.add_log_level,
        structlog.dev.ConsoleRenderer(),
    ]
)
log = structlog.get_logger()

# ─────────────────────────────────────────────────────────────────────────────
# Environment
# ─────────────────────────────────────────────────────────────────────────────
E2B_API_KEY = os.environ.get("E2B_API_KEY", "").strip()

# Multiple keys can be comma-separated; we just pick the first non-empty.
def _first_key(*env_names: str) -> str:
    for name in env_names:
        v = os.environ.get(name, "")
        if v:
            return v.split(",")[0].strip()
    return ""

SAMBANOVA_KEY = _first_key("SAMBANOVA_KEY", "SAMBANOVA_API_KEY", "SAMBANOVA_API_KEYS")
GEMINI_KEY    = _first_key("GEMINI_KEY", "GEMINI_API_KEY", "GEMINI_API_KEYS")
GITHUB_LLM_KEY= _first_key("GITHUB_KEY", "GITHUB_API_KEY", "GITHUB_MODELS_TOKEN", "GITHUB_API_KEYS")
OPENAI_KEY    = _first_key("OPENAI_API_KEY")
GROQ_KEY      = _first_key("GROQ_API_KEY")
ANTHROPIC_KEY = _first_key("ANTHROPIC_API_KEY")
HF_TOKEN      = _first_key("HF_TOKEN", "HUGGINGFACE_TOKEN")

VERSION = "13.0.0-phase1"

# ─────────────────────────────────────────────────────────────────────────────
# LLM Providers β€” OpenAI-compatible streaming (most stable, no tool-calling)
# ─────────────────────────────────────────────────────────────────────────────
# Order: most stable & fastest first. We deliberately do NOT use Gemini for
# tool-calling here β€” Phase 1 is text-only streaming.
PROVIDERS: List[Dict[str, Any]] = [
    {
        "name": "sambanova",
        "key": SAMBANOVA_KEY,
        "url": "https://api.sambanova.ai/v1/chat/completions",
        "model": "Meta-Llama-3.3-70B-Instruct",
    },
    {
        "name": "groq",
        "key": GROQ_KEY,
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "model": "llama-3.3-70b-versatile",
    },
    {
        "name": "github",
        "key": GITHUB_LLM_KEY,
        "url": "https://models.inference.ai.azure.com/chat/completions",
        "model": "gpt-4o-mini",
    },
    {
        "name": "openai",
        "key": OPENAI_KEY,
        "url": "https://api.openai.com/v1/chat/completions",
        "model": "gpt-4o-mini",
    },
    {
        "name": "anthropic",
        "key": ANTHROPIC_KEY,
        "url": "https://api.anthropic.com/v1/messages",
        "model": "claude-3-5-haiku-20241022",
    },
    {
        "name": "gemini",
        "key": GEMINI_KEY,
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent",
        "model": "gemini-2.0-flash",
    },
]

def active_providers() -> List[str]:
    return [p["name"] for p in PROVIDERS if p["key"]]

# ... [full file truncated for brevity] ...
# Full app.py content from GitHub is deployed.