RobocodersAPI.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. from __future__ import annotations
  2. import json
  3. import aiohttp
  4. from pathlib import Path
  5. try:
  6. from bs4 import BeautifulSoup
  7. HAS_BEAUTIFULSOUP = True
  8. except ImportError:
  9. HAS_BEAUTIFULSOUP = False
  10. BeautifulSoup = None
  11. from aiohttp import ClientTimeout
  12. from ...errors import MissingRequirementsError
  13. from ...typing import AsyncResult, Messages
  14. from ...cookies import get_cookies_dir
  15. from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
  16. from ..helper import format_prompt
  17. from ... import debug
  18. class RobocodersAPI(AsyncGeneratorProvider, ProviderModelMixin):
  19. label = "API Robocoders AI"
  20. url = "https://api.robocoders.ai/docs"
  21. api_endpoint = "https://api.robocoders.ai/chat"
  22. working = False
  23. supports_message_history = True
  24. default_model = 'GeneralCodingAgent'
  25. agent = [default_model, "RepoAgent", "FrontEndAgent"]
  26. models = [*agent]
  27. CACHE_DIR = Path(get_cookies_dir())
  28. CACHE_FILE = CACHE_DIR / "robocoders.json"
  29. @classmethod
  30. async def create_async_generator(
  31. cls,
  32. model: str,
  33. messages: Messages,
  34. proxy: str = None,
  35. **kwargs
  36. ) -> AsyncResult:
  37. timeout = ClientTimeout(total=600)
  38. async with aiohttp.ClientSession(timeout=timeout) as session:
  39. # Load or create access token and session ID
  40. access_token, session_id = await cls._get_or_create_access_and_session(session)
  41. if not access_token or not session_id:
  42. raise Exception("Failed to initialize API interaction")
  43. headers = {
  44. "Content-Type": "application/json",
  45. "Authorization": f"Bearer {access_token}"
  46. }
  47. prompt = format_prompt(messages)
  48. data = {
  49. "sid": session_id,
  50. "prompt": prompt,
  51. "agent": model
  52. }
  53. async with session.post(cls.api_endpoint, headers=headers, json=data, proxy=proxy) as response:
  54. if response.status == 401: # Unauthorized, refresh token
  55. cls._clear_cached_data()
  56. raise Exception("Unauthorized: Invalid token, please retry.")
  57. elif response.status == 422:
  58. raise Exception("Validation Error: Invalid input.")
  59. elif response.status >= 500:
  60. raise Exception(f"Server Error: {response.status}")
  61. elif response.status != 200:
  62. raise Exception(f"Unexpected Error: {response.status}")
  63. async for line in response.content:
  64. if line:
  65. try:
  66. # Decode bytes into a string
  67. line_str = line.decode('utf-8')
  68. response_data = json.loads(line_str)
  69. # Get the message from the 'args.content' or 'message' field
  70. message = (response_data.get('args', {}).get('content') or
  71. response_data.get('message', ''))
  72. if message:
  73. yield message
  74. # Check for reaching the resource limit
  75. if (response_data.get('action') == 'message' and
  76. response_data.get('args', {}).get('wait_for_response')):
  77. # Automatically continue the dialog
  78. continue_data = {
  79. "sid": session_id,
  80. "prompt": "continue",
  81. "agent": model
  82. }
  83. async with session.post(
  84. cls.api_endpoint,
  85. headers=headers,
  86. json=continue_data,
  87. proxy=proxy
  88. ) as continue_response:
  89. if continue_response.status == 200:
  90. async for continue_line in continue_response.content:
  91. if continue_line:
  92. try:
  93. continue_line_str = continue_line.decode('utf-8')
  94. continue_data = json.loads(continue_line_str)
  95. continue_message = (
  96. continue_data.get('args', {}).get('content') or
  97. continue_data.get('message', '')
  98. )
  99. if continue_message:
  100. yield continue_message
  101. except json.JSONDecodeError:
  102. debug.log(f"Failed to decode continue JSON: {continue_line}")
  103. except Exception as e:
  104. debug.log(f"Error processing continue response: {e}")
  105. except json.JSONDecodeError:
  106. debug.log(f"Failed to decode JSON: {line}")
  107. except Exception as e:
  108. debug.log(f"Error processing response: {e}")
  109. @staticmethod
  110. async def _get_or_create_access_and_session(session: aiohttp.ClientSession):
  111. RobocodersAPI.CACHE_DIR.mkdir(exist_ok=True) # Ensure cache directory exists
  112. # Load data from cache
  113. if RobocodersAPI.CACHE_FILE.exists():
  114. with open(RobocodersAPI.CACHE_FILE, "r") as f:
  115. data = json.load(f)
  116. access_token = data.get("access_token")
  117. session_id = data.get("sid")
  118. # Validate loaded data
  119. if access_token and session_id:
  120. return access_token, session_id
  121. # If data not valid, create new access token and session ID
  122. access_token = await RobocodersAPI._fetch_and_cache_access_token(session)
  123. session_id = await RobocodersAPI._create_and_cache_session(session, access_token)
  124. return access_token, session_id
  125. @staticmethod
  126. async def _fetch_and_cache_access_token(session: aiohttp.ClientSession) -> str:
  127. if not HAS_BEAUTIFULSOUP:
  128. raise MissingRequirementsError('Install "beautifulsoup4" package | pip install -U beautifulsoup4')
  129. return token
  130. url_auth = 'https://api.robocoders.ai/auth'
  131. headers_auth = {
  132. 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
  133. 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
  134. }
  135. async with session.get(url_auth, headers=headers_auth) as response:
  136. if response.status == 200:
  137. html = await response.text()
  138. soup = BeautifulSoup(html, 'html.parser')
  139. token_element = soup.find('pre', id='token')
  140. if token_element:
  141. token = token_element.text.strip()
  142. # Cache the token
  143. RobocodersAPI._save_cached_data({"access_token": token})
  144. return token
  145. return None
  146. @staticmethod
  147. async def _create_and_cache_session(session: aiohttp.ClientSession, access_token: str) -> str:
  148. url_create_session = 'https://api.robocoders.ai/create-session'
  149. headers_create_session = {
  150. 'Authorization': f'Bearer {access_token}'
  151. }
  152. async with session.get(url_create_session, headers=headers_create_session) as response:
  153. if response.status == 200:
  154. data = await response.json()
  155. session_id = data.get('sid')
  156. # Cache session ID
  157. RobocodersAPI._update_cached_data({"sid": session_id})
  158. return session_id
  159. elif response.status == 401:
  160. RobocodersAPI._clear_cached_data()
  161. raise Exception("Unauthorized: Invalid token during session creation.")
  162. elif response.status == 422:
  163. raise Exception("Validation Error: Check input parameters.")
  164. return None
  165. @staticmethod
  166. def _save_cached_data(new_data: dict):
  167. """Save new data to cache file"""
  168. RobocodersAPI.CACHE_DIR.mkdir(exist_ok=True)
  169. RobocodersAPI.CACHE_FILE.touch(exist_ok=True)
  170. with open(RobocodersAPI.CACHE_FILE, "w") as f:
  171. json.dump(new_data, f)
  172. @staticmethod
  173. def _update_cached_data(updated_data: dict):
  174. """Update existing cache data with new values"""
  175. data = {}
  176. if RobocodersAPI.CACHE_FILE.exists():
  177. with open(RobocodersAPI.CACHE_FILE, "r") as f:
  178. try:
  179. data = json.load(f)
  180. except json.JSONDecodeError:
  181. # If cache file is corrupted, start with empty dict
  182. data = {}
  183. data.update(updated_data)
  184. with open(RobocodersAPI.CACHE_FILE, "w") as f:
  185. json.dump(data, f)
  186. @staticmethod
  187. def _clear_cached_data():
  188. """Remove cache file"""
  189. try:
  190. if RobocodersAPI.CACHE_FILE.exists():
  191. RobocodersAPI.CACHE_FILE.unlink()
  192. except Exception as e:
  193. debug.log(f"Error clearing cache: {e}")
  194. @staticmethod
  195. def _get_cached_data() -> dict:
  196. """Get all cached data"""
  197. if RobocodersAPI.CACHE_FILE.exists():
  198. try:
  199. with open(RobocodersAPI.CACHE_FILE, "r") as f:
  200. return json.load(f)
  201. except json.JSONDecodeError:
  202. return {}
  203. return {}