zen-ai-pentest 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. agents/__init__.py +28 -0
  2. agents/agent_base.py +239 -0
  3. agents/agent_orchestrator.py +346 -0
  4. agents/analysis_agent.py +225 -0
  5. agents/cli.py +258 -0
  6. agents/exploit_agent.py +224 -0
  7. agents/integration.py +211 -0
  8. agents/post_scan_agent.py +937 -0
  9. agents/react_agent.py +384 -0
  10. agents/react_agent_enhanced.py +616 -0
  11. agents/react_agent_vm.py +298 -0
  12. agents/research_agent.py +176 -0
  13. api/__init__.py +11 -0
  14. api/auth.py +123 -0
  15. api/main.py +1027 -0
  16. api/schemas.py +357 -0
  17. api/websocket.py +97 -0
  18. autonomous/__init__.py +122 -0
  19. autonomous/agent.py +253 -0
  20. autonomous/agent_loop.py +1370 -0
  21. autonomous/exploit_validator.py +1537 -0
  22. autonomous/memory.py +448 -0
  23. autonomous/react.py +339 -0
  24. autonomous/tool_executor.py +488 -0
  25. backends/__init__.py +16 -0
  26. backends/chatgpt_direct.py +133 -0
  27. backends/claude_direct.py +130 -0
  28. backends/duckduckgo.py +138 -0
  29. backends/openrouter.py +120 -0
  30. benchmarks/__init__.py +149 -0
  31. benchmarks/benchmark_engine.py +904 -0
  32. benchmarks/ci_benchmark.py +785 -0
  33. benchmarks/comparison.py +729 -0
  34. benchmarks/metrics.py +553 -0
  35. benchmarks/run_benchmarks.py +809 -0
  36. ci_cd/__init__.py +2 -0
  37. core/__init__.py +17 -0
  38. core/async_pool.py +282 -0
  39. core/asyncio_fix.py +222 -0
  40. core/cache.py +472 -0
  41. core/container.py +277 -0
  42. core/database.py +114 -0
  43. core/input_validator.py +353 -0
  44. core/models.py +288 -0
  45. core/orchestrator.py +611 -0
  46. core/plugin_manager.py +571 -0
  47. core/rate_limiter.py +405 -0
  48. core/secure_config.py +328 -0
  49. core/shield_integration.py +296 -0
  50. modules/__init__.py +46 -0
  51. modules/cve_database.py +362 -0
  52. modules/exploit_assist.py +330 -0
  53. modules/nuclei_integration.py +480 -0
  54. modules/osint.py +604 -0
  55. modules/protonvpn.py +554 -0
  56. modules/recon.py +165 -0
  57. modules/sql_injection_db.py +826 -0
  58. modules/tool_orchestrator.py +498 -0
  59. modules/vuln_scanner.py +292 -0
  60. modules/wordlist_generator.py +566 -0
  61. risk_engine/__init__.py +99 -0
  62. risk_engine/business_impact.py +267 -0
  63. risk_engine/business_impact_calculator.py +563 -0
  64. risk_engine/cvss.py +156 -0
  65. risk_engine/epss.py +190 -0
  66. risk_engine/example_usage.py +294 -0
  67. risk_engine/false_positive_engine.py +1073 -0
  68. risk_engine/scorer.py +304 -0
  69. web_ui/backend/main.py +471 -0
  70. zen_ai_pentest-2.0.0.dist-info/METADATA +795 -0
  71. zen_ai_pentest-2.0.0.dist-info/RECORD +75 -0
  72. zen_ai_pentest-2.0.0.dist-info/WHEEL +5 -0
  73. zen_ai_pentest-2.0.0.dist-info/entry_points.txt +2 -0
  74. zen_ai_pentest-2.0.0.dist-info/licenses/LICENSE +21 -0
  75. zen_ai_pentest-2.0.0.dist-info/top_level.txt +10 -0
@@ -0,0 +1,488 @@
1
+ """
2
+ Real Tool Execution Framework
3
+
4
+ Executes actual security tools (nmap, nuclei, sqlmap, etc.) in sandboxed environments.
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import logging
10
+ import os
11
+ import subprocess
12
+ import tempfile
13
+ from abc import ABC, abstractmethod
14
+ from dataclasses import dataclass, field
15
+ from datetime import datetime
16
+ from enum import Enum
17
+ from typing import Any, Dict, List, Optional, Callable
18
+ import shlex
19
+
20
+
21
+ class SafetyLevel(Enum):
22
+ """Safety levels for tool execution."""
23
+ READ_ONLY = 1 # Passive recon only (nmap -sS)
24
+ NON_DESTRUCTIVE = 2 # Active but safe (nuclei, ffuf)
25
+ DESTRUCTIVE = 3 # May modify state (sqlmap --dump)
26
+ EXPLOIT = 4 # Full exploitation (metasploit)
27
+
28
+
29
+ @dataclass
30
+ class ToolResult:
31
+ """Result of tool execution."""
32
+ tool: str
33
+ command: str
34
+ return_code: int
35
+ stdout: str
36
+ stderr: str
37
+ duration: float
38
+ parsed_output: Dict[str, Any] = field(default_factory=dict)
39
+ findings: List[Dict] = field(default_factory=list)
40
+ timestamp: datetime = field(default_factory=datetime.now)
41
+ success: bool = True
42
+ error_message: Optional[str] = None
43
+
44
+
45
+ @dataclass
46
+ class ToolDefinition:
47
+ """Definition of a security tool."""
48
+ name: str
49
+ description: str
50
+ command_template: str
51
+ safety_level: SafetyLevel
52
+ category: str
53
+ needs_docker: bool = False
54
+ output_parser: Optional[Callable] = None
55
+ timeout: int = 300
56
+ installed: bool = False
57
+
58
+
59
+ class ToolRegistry:
60
+ """Registry of available security tools."""
61
+
62
+ def __init__(self):
63
+ self.tools: Dict[str, ToolDefinition] = {}
64
+ self._register_default_tools()
65
+
66
+ def _register_default_tools(self):
67
+ """Register default security tools."""
68
+
69
+ # Network Reconnaissance
70
+ self.register(ToolDefinition(
71
+ name="nmap",
72
+ description="Network discovery and security auditing",
73
+ command_template="nmap {options} {target}",
74
+ safety_level=SafetyLevel.READ_ONLY,
75
+ category="recon",
76
+ timeout=600
77
+ ))
78
+
79
+ self.register(ToolDefinition(
80
+ name="masscan",
81
+ description="Fast port scanner",
82
+ command_template="masscan {target} -p{ports} {options}",
83
+ safety_level=SafetyLevel.READ_ONLY,
84
+ category="recon",
85
+ timeout=300
86
+ ))
87
+
88
+ # Web Scanning
89
+ self.register(ToolDefinition(
90
+ name="nuclei",
91
+ description="Fast vulnerability scanner",
92
+ command_template="nuclei -u {target} {options}",
93
+ safety_level=SafetyLevel.NON_DESTRUCTIVE,
94
+ category="web",
95
+ timeout=600
96
+ ))
97
+
98
+ self.register(ToolDefinition(
99
+ name="ffuf",
100
+ description="Fast web fuzzer",
101
+ command_template="ffuf -u {target}/FUZZ {options}",
102
+ safety_level=SafetyLevel.NON_DESTRUCTIVE,
103
+ category="web",
104
+ timeout=300
105
+ ))
106
+
107
+ self.register(ToolDefinition(
108
+ name="gospider",
109
+ description="Web crawler",
110
+ command_template="gospider -s {target} {options}",
111
+ safety_level=SafetyLevel.READ_ONLY,
112
+ category="web",
113
+ timeout=300
114
+ ))
115
+
116
+ # SQL Injection
117
+ self.register(ToolDefinition(
118
+ name="sqlmap",
119
+ description="Automatic SQL injection tool",
120
+ command_template="sqlmap -u {target} {options}",
121
+ safety_level=SafetyLevel.DESTRUCTIVE,
122
+ category="exploitation",
123
+ timeout=600
124
+ ))
125
+
126
+ # Subdomain Enumeration
127
+ self.register(ToolDefinition(
128
+ name="subfinder",
129
+ description="Subdomain discovery",
130
+ command_template="subfinder -d {target} {options}",
131
+ safety_level=SafetyLevel.READ_ONLY,
132
+ category="recon",
133
+ timeout=300
134
+ ))
135
+
136
+ self.register(ToolDefinition(
137
+ name="amass",
138
+ description="In-depth attack surface mapping",
139
+ command_template="amass enum -d {target} {options}",
140
+ safety_level=SafetyLevel.READ_ONLY,
141
+ category="recon",
142
+ timeout=600
143
+ ))
144
+
145
+ # DNS
146
+ self.register(ToolDefinition(
147
+ name="dnsrecon",
148
+ description="DNS enumeration",
149
+ command_template="dnsrecon -d {target} {options}",
150
+ safety_level=SafetyLevel.READ_ONLY,
151
+ category="recon",
152
+ timeout=300
153
+ ))
154
+
155
+ # SSL/TLS
156
+ self.register(ToolDefinition(
157
+ name="sslscan",
158
+ description="SSL/TLS scanner",
159
+ command_template="sslscan {target} {options}",
160
+ safety_level=SafetyLevel.READ_ONLY,
161
+ category="web",
162
+ timeout=120
163
+ ))
164
+
165
+ # Content Discovery
166
+ self.register(ToolDefinition(
167
+ name="gobuster",
168
+ description="Directory/file brute forcer",
169
+ command_template="gobuster dir -u {target} {options}",
170
+ safety_level=SafetyLevel.NON_DESTRUCTIVE,
171
+ category="web",
172
+ timeout=300
173
+ ))
174
+
175
+ # Vulnerability Scanning
176
+ self.register(ToolDefinition(
177
+ name="nikto",
178
+ description="Web server scanner",
179
+ command_template="nikto -h {target} {options}",
180
+ safety_level=SafetyLevel.NON_DESTRUCTIVE,
181
+ category="web",
182
+ timeout=600
183
+ ))
184
+
185
+ def register(self, tool: ToolDefinition):
186
+ """Register a new tool."""
187
+ self.tools[tool.name] = tool
188
+
189
+ def get(self, name: str) -> Optional[ToolDefinition]:
190
+ """Get a tool by name."""
191
+ return self.tools.get(name)
192
+
193
+ def list_tools(self, category: Optional[str] = None, safety: Optional[SafetyLevel] = None) -> List[ToolDefinition]:
194
+ """List available tools with optional filtering."""
195
+ tools = list(self.tools.values())
196
+
197
+ if category:
198
+ tools = [t for t in tools if t.category == category]
199
+
200
+ if safety:
201
+ tools = [t for t in tools if t.safety_level.value <= safety.value]
202
+
203
+ return tools
204
+
205
+ def check_installed(self, name: str) -> bool:
206
+ """Check if a tool is installed."""
207
+ try:
208
+ result = subprocess.run(
209
+ ['which', name],
210
+ capture_output=True,
211
+ timeout=5
212
+ )
213
+ return result.return_code == 0
214
+ except:
215
+ return False
216
+
217
+
218
+ class ToolExecutor:
219
+ """Executes security tools with safety controls."""
220
+
221
+ def __init__(
222
+ self,
223
+ registry: Optional[ToolRegistry] = None,
224
+ safety_level: SafetyLevel = SafetyLevel.NON_DESTRUCTIVE,
225
+ use_docker: bool = False,
226
+ output_dir: Optional[str] = None
227
+ ):
228
+ self.registry = registry or ToolRegistry()
229
+ self.max_safety = safety_level
230
+ self.use_docker = use_docker
231
+ self.output_dir = output_dir or tempfile.gettempdir()
232
+ self.logger = logging.getLogger(__name__)
233
+
234
+ # Track execution for audit
235
+ self.execution_log: List[Dict] = []
236
+
237
+ def get_available_tools(self) -> List[Dict]:
238
+ """Get list of available tools for LLM."""
239
+ tools = self.registry.list_tools(safety=self.max_safety)
240
+ return [
241
+ {
242
+ 'name': t.name,
243
+ 'description': t.description,
244
+ 'category': t.category,
245
+ 'safety': t.safety_level.name,
246
+ 'installed': self.registry.check_installed(t.name)
247
+ }
248
+ for t in tools
249
+ ]
250
+
251
+ async def execute(
252
+ self,
253
+ tool_name: str,
254
+ parameters: Dict[str, Any],
255
+ timeout: Optional[int] = None
256
+ ) -> ToolResult:
257
+ """
258
+ Execute a tool with the given parameters.
259
+
260
+ Args:
261
+ tool_name: Name of the tool to execute
262
+ parameters: Tool parameters (target, options, etc.)
263
+ timeout: Override default timeout
264
+
265
+ Returns:
266
+ ToolResult with output and parsed findings
267
+ """
268
+ tool = self.registry.get(tool_name)
269
+ if not tool:
270
+ return ToolResult(
271
+ tool=tool_name,
272
+ command="",
273
+ return_code=-1,
274
+ stdout="",
275
+ stderr=f"Tool '{tool_name}' not found in registry",
276
+ duration=0,
277
+ success=False,
278
+ error_message=f"Unknown tool: {tool_name}"
279
+ )
280
+
281
+ # Safety check
282
+ if tool.safety_level.value > self.max_safety.value:
283
+ return ToolResult(
284
+ tool=tool_name,
285
+ command="",
286
+ return_code=-1,
287
+ stdout="",
288
+ stderr=f"Tool '{tool_name}' exceeds safety level ({tool.safety_level.name} > {self.max_safety.name})",
289
+ duration=0,
290
+ success=False,
291
+ error_message=f"Safety violation: {tool_name}"
292
+ )
293
+
294
+ # Build command
295
+ try:
296
+ command = self._build_command(tool, parameters)
297
+ except Exception as e:
298
+ return ToolResult(
299
+ tool=tool_name,
300
+ command="",
301
+ return_code=-1,
302
+ stdout="",
303
+ stderr=str(e),
304
+ duration=0,
305
+ success=False,
306
+ error_message=f"Command build failed: {str(e)}"
307
+ )
308
+
309
+ self.logger.info(f"Executing: {command}")
310
+
311
+ # Execute
312
+ start_time = datetime.now()
313
+ try:
314
+ if self.use_docker:
315
+ stdout, stderr, return_code = await self._execute_docker(tool, command, timeout or tool.timeout)
316
+ else:
317
+ stdout, stderr, return_code = await self._execute_local(command, timeout or tool.timeout)
318
+
319
+ duration = (datetime.now() - start_time).total_seconds()
320
+
321
+ # Parse output
322
+ parsed_output = self._parse_output(tool_name, stdout)
323
+ findings = self._extract_findings(tool_name, stdout, parsed_output)
324
+
325
+ # Log execution
326
+ self.execution_log.append({
327
+ 'tool': tool_name,
328
+ 'command': command,
329
+ 'timestamp': datetime.now().isoformat(),
330
+ 'duration': duration,
331
+ 'success': return_code == 0
332
+ })
333
+
334
+ return ToolResult(
335
+ tool=tool_name,
336
+ command=command,
337
+ return_code=return_code,
338
+ stdout=stdout,
339
+ stderr=stderr,
340
+ duration=duration,
341
+ parsed_output=parsed_output,
342
+ findings=findings,
343
+ success=return_code == 0
344
+ )
345
+
346
+ except asyncio.TimeoutError:
347
+ duration = (datetime.now() - start_time).total_seconds()
348
+ return ToolResult(
349
+ tool=tool_name,
350
+ command=command,
351
+ return_code=-1,
352
+ stdout="",
353
+ stderr=f"Timeout after {timeout or tool.timeout} seconds",
354
+ duration=duration,
355
+ success=False,
356
+ error_message="Execution timeout"
357
+ )
358
+ except Exception as e:
359
+ duration = (datetime.now() - start_time).total_seconds()
360
+ return ToolResult(
361
+ tool=tool_name,
362
+ command=command,
363
+ return_code=-1,
364
+ stdout="",
365
+ stderr=str(e),
366
+ duration=duration,
367
+ success=False,
368
+ error_message=str(e)
369
+ )
370
+
371
+ def _build_command(self, tool: ToolDefinition, parameters: Dict[str, Any]) -> str:
372
+ """Build the command string from template."""
373
+ target = parameters.get('target', '')
374
+ options = parameters.get('options', '')
375
+
376
+ # Sanitize inputs
377
+ target = shlex.quote(target) if target else ''
378
+
379
+ command = tool.command_template.format(
380
+ target=target,
381
+ options=options,
382
+ ports=parameters.get('ports', '1-65535'),
383
+ **parameters
384
+ )
385
+
386
+ return command
387
+
388
+ async def _execute_local(self, command: str, timeout: int) -> tuple:
389
+ """Execute command locally."""
390
+ process = await asyncio.create_subprocess_shell(
391
+ command,
392
+ stdout=asyncio.subprocess.PIPE,
393
+ stderr=asyncio.subprocess.PIPE
394
+ )
395
+
396
+ try:
397
+ stdout, stderr = await asyncio.wait_for(
398
+ process.communicate(),
399
+ timeout=timeout
400
+ )
401
+ return (
402
+ stdout.decode('utf-8', errors='ignore'),
403
+ stderr.decode('utf-8', errors='ignore'),
404
+ process.returncode
405
+ )
406
+ except asyncio.TimeoutError:
407
+ process.kill()
408
+ raise
409
+
410
+ async def _execute_docker(self, tool: ToolDefinition, command: str, timeout: int) -> tuple:
411
+ """Execute in Docker container."""
412
+ # Docker execution for isolation
413
+ docker_cmd = f"docker run --rm --network=host {tool.name} {command}"
414
+ return await self._execute_local(docker_cmd, timeout)
415
+
416
+ def _parse_output(self, tool_name: str, stdout: str) -> Dict[str, Any]:
417
+ """Parse tool output into structured format."""
418
+ parsers = {
419
+ 'nmap': self._parse_nmap,
420
+ 'nuclei': self._parse_nuclei,
421
+ 'subfinder': self._parse_subfinder,
422
+ # Add more parsers
423
+ }
424
+
425
+ parser = parsers.get(tool_name)
426
+ if parser:
427
+ try:
428
+ return parser(stdout)
429
+ except Exception as e:
430
+ self.logger.error(f"Parse error for {tool_name}: {e}")
431
+
432
+ return {'raw': stdout}
433
+
434
+ def _parse_nmap(self, output: str) -> Dict:
435
+ """Parse nmap output."""
436
+ ports = []
437
+ for line in output.split('\n'):
438
+ if '/tcp' in line and 'open' in line:
439
+ parts = line.split()
440
+ if len(parts) >= 3:
441
+ port = parts[0].split('/')[0]
442
+ service = parts[2]
443
+ version = ' '.join(parts[3:]) if len(parts) > 3 else ''
444
+ ports.append({
445
+ 'port': port,
446
+ 'service': service,
447
+ 'version': version
448
+ })
449
+
450
+ return {'open_ports': ports}
451
+
452
+ def _parse_nuclei(self, output: str) -> Dict:
453
+ """Parse nuclei output."""
454
+ findings = []
455
+ for line in output.split('\n'):
456
+ if '[' in line and ']' in line:
457
+ # Basic parsing, enhance as needed
458
+ findings.append({'raw': line})
459
+
460
+ return {'findings': findings}
461
+
462
+ def _parse_subfinder(self, output: str) -> Dict:
463
+ """Parse subfinder output."""
464
+ subdomains = [line.strip() for line in output.split('\n') if line.strip()]
465
+ return {'subdomains': subdomains}
466
+
467
+ def _extract_findings(self, tool_name: str, stdout: str, parsed: Dict) -> List[Dict]:
468
+ """Extract security findings from output."""
469
+ findings = []
470
+
471
+ # Add generic findings based on tool
472
+ if tool_name == 'nmap' and parsed.get('open_ports'):
473
+ for port in parsed['open_ports']:
474
+ findings.append({
475
+ 'type': 'open_port',
476
+ 'severity': 'info',
477
+ 'details': port
478
+ })
479
+
480
+ elif tool_name == 'nuclei' and parsed.get('findings'):
481
+ for finding in parsed['findings']:
482
+ findings.append({
483
+ 'type': 'vulnerability',
484
+ 'severity': 'unknown',
485
+ 'details': finding
486
+ })
487
+
488
+ return findings
backends/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ """
2
+ LLM Backends for Zen AI
3
+ Supports multiple free and authenticated LLM providers
4
+ """
5
+
6
+ from .chatgpt_direct import ChatGPTDirectBackend
7
+ from .claude_direct import ClaudeDirectBackend
8
+ from .duckduckgo import DuckDuckGoBackend
9
+ from .openrouter import OpenRouterBackend
10
+
11
+ __all__ = [
12
+ "DuckDuckGoBackend",
13
+ "OpenRouterBackend",
14
+ "ChatGPTDirectBackend",
15
+ "ClaudeDirectBackend",
16
+ ]
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ChatGPT Direct Backend (Reverse Engineered)
4
+ Uses internal WebSocket/HTTP API with session token
5
+ Requires manual token extraction once
6
+ """
7
+
8
+ import logging
9
+ import os
10
+ import random
11
+ import sys
12
+ from typing import Optional
13
+
14
+ import aiohttp
15
+
16
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
17
+ from utils.async_fixes import safe_close_session
18
+
19
+ logger = logging.getLogger("ZenAI")
20
+
21
+
22
+ class ChatGPTDirectBackend:
23
+ """
24
+ Direct ChatGPT API Backend
25
+ - Uses access token from browser session
26
+ - No browser automation overhead
27
+ - Requires token renewal every 2-4 weeks
28
+ """
29
+
30
+ def __init__(self, access_token: str = None):
31
+ self.name = "ChatGPT-Direct"
32
+ self.priority = 3 # Lower priority (requires manual setup)
33
+ self.access_token = access_token
34
+ self.refresh_token = None
35
+ self.conversation_id = None
36
+ self.session: Optional[aiohttp.ClientSession] = None
37
+
38
+ async def __aenter__(self):
39
+ self.session = aiohttp.ClientSession()
40
+ return self
41
+
42
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
43
+ if self.session:
44
+ await safe_close_session(self.session)
45
+
46
+ async def chat(self, prompt: str, context: str = "") -> Optional[str]:
47
+ """Send chat request via direct API"""
48
+ if not self.access_token:
49
+ logger.warning("[ChatGPT-Direct] No access token provided")
50
+ return None
51
+
52
+ try:
53
+ headers = {
54
+ "Authorization": f"Bearer {self.access_token}",
55
+ "Content-Type": "application/json",
56
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
57
+ }
58
+
59
+ # Generate unique IDs
60
+ msg_id = str(random.randint(1000000000, 9999999999))
61
+ parent_id = str(random.randint(1000000000, 9999999999))
62
+
63
+ payload = {
64
+ "action": "next",
65
+ "messages": [
66
+ {
67
+ "id": msg_id,
68
+ "author": {"role": "user"},
69
+ "content": {"content_type": "text", "parts": [prompt]},
70
+ }
71
+ ],
72
+ "conversation_id": self.conversation_id,
73
+ "parent_message_id": parent_id,
74
+ "model": "text-davinci-002-render-sha",
75
+ "timezone_offset_min": -120,
76
+ "history_and_training_disabled": False,
77
+ }
78
+
79
+ logger.info("[ChatGPT-Direct] Sending request...")
80
+
81
+ async with self.session.post(
82
+ "https://chat.openai.com/backend-api/conversation",
83
+ json=payload,
84
+ headers=headers,
85
+ timeout=aiohttp.ClientTimeout(total=60),
86
+ ) as resp:
87
+ if resp.status == 401:
88
+ logger.error("[ChatGPT-Direct] Token expired!")
89
+ return None
90
+ elif resp.status == 429:
91
+ logger.warning("[ChatGPT-Direct] Rate limited")
92
+ return None
93
+ elif resp.status != 200:
94
+ logger.error(f"[ChatGPT-Direct] HTTP Error: {resp.status}")
95
+ return None
96
+
97
+ # ChatGPT streams events
98
+ text = await resp.text()
99
+ lines = text.split("\n")
100
+
101
+ full_response = ""
102
+ for line in lines:
103
+ if line.startswith("data: "):
104
+ try:
105
+ import json
106
+
107
+ data = json.loads(line[6:])
108
+ if data.get("message") and data["message"].get("content"):
109
+ full_response = data["message"]["content"]["parts"][0]
110
+ # Update conversation ID for context
111
+ if data.get("conversation_id"):
112
+ self.conversation_id = data["conversation_id"]
113
+ except:
114
+ continue
115
+
116
+ return full_response
117
+
118
+ except Exception as e:
119
+ logger.error(f"[ChatGPT-Direct] Error: {e}")
120
+ return None
121
+
122
+ async def health_check(self) -> bool:
123
+ """Check if backend is available"""
124
+ if not self.access_token:
125
+ return False
126
+ try:
127
+ headers = {"Authorization": f"Bearer {self.access_token}"}
128
+ async with self.session.get(
129
+ "https://chat.openai.com/backend-api/models", headers=headers
130
+ ) as resp:
131
+ return resp.status == 200
132
+ except:
133
+ return False