11"""
22HTTP клиент для OpenCode API
3+
4+ Использует opencode API (/session/...) вместо v2 API (/api/session/...).
5+ v2 API был для lildax и использует SessionExecution.noopLayer - agent loop не работает.
6+ opencode API использует SessionPrompt с реальным agent loop.
37"""
48import json
59from typing import Dict , List , Optional
@@ -42,11 +46,11 @@ def session(self) -> ClientSession:
4246 async def create_session (self , model : str = None ) -> str :
4347 """Создаёт новую сессию и возвращает её ID."""
4448 data = model_to_api_format (model or config .CLI_MODEL )
45- async with self .session .post (f"{ self .base_url } /api/session" , json = data ) as resp :
49+ # opencode API: POST /session
50+ async with self .session .post (f"{ self .base_url } /session" , json = data ) as resp :
4651 resp .raise_for_status ()
4752 resp_data = await resp .json ()
48- # API возвращает {data: {id: ...}}
49- session_id = resp_data .get ("data" , resp_data ).get ("id" )
53+ session_id = resp_data .get ("id" , resp_data .get ("data" , {}).get ("id" ))
5054 logger .debug (f"Created OpenCode session { session_id } " )
5155 return session_id
5256
@@ -55,41 +59,47 @@ async def get_session_messages(
5559 ) -> Optional [List [dict ]]:
5660 """Получает сообщения сессии."""
5761 try :
58- url = f"{ self .base_url } /api/session/{ session_id } /message?limit={ limit } "
62+ # opencode API: GET /session/:sessionID/message
63+ url = f"{ self .base_url } /session/{ session_id } /message?limit={ limit } "
5964 async with self .session .get (url ) as resp :
6065 if resp .status != 200 :
6166 return None
6267 resp_data = await resp .json ()
63- # API возвращает {data: [...], cursor: {...}}
64- return resp_data .get ("data" , resp_data )
68+ return resp_data if isinstance (resp_data , list ) else resp_data .get ("data" , resp_data )
6569 except Exception as e :
6670 logger .warning (f"Failed to fetch messages for { session_id } : { e } " )
6771 return None
6872
6973 async def send_prompt (
7074 self , session_id : str , text : str
7175 ) -> bool :
72- """Отправляет промпт в сессию."""
73- url = f"{ self .base_url } /api/session/{ session_id } /prompt"
74- # API требует формат {prompt: {text: "..."}}
75- data = {"prompt" : {"text" : text }}
76+ """Отправляет промпт в сессию.
77+
78+ Использует /session/:sessionID/prompt_async endpoint opencode API,
79+ который реально запускает agent loop.
80+ """
81+ # opencode API: POST /session/:sessionID/prompt_async
82+ # Формат: {parts: [{type: "text", text: "..."}]}
83+ url = f"{ self .base_url } /session/{ session_id } /prompt_async"
84+ data = {"parts" : [{"type" : "text" , "text" : text }]}
7685 async with self .session .post (url , json = data ) as resp :
77- if resp .status == 200 :
86+ if resp .status in ( 200 , 204 ) :
7887 logger .debug (f"Prompt sent for session { session_id } " )
7988 return True
80- logger .error (f"Failed to send prompt: { resp .status } " )
89+ text = await resp .text ()
90+ logger .error (f"Failed to send prompt: { resp .status } - { text } " )
8191 return False
8292
8393 # ---------- Разрешения ----------
8494
8595 async def get_pending_permissions (self ) -> List [dict ]:
8696 """Получает список ожидающих разрешений."""
8797 try :
88- async with self .session .get (f"{ self .base_url } /api/permission/request" ) as resp :
98+ # opencode API: GET /permission/request (если есть)
99+ async with self .session .get (f"{ self .base_url } /permission/request" ) as resp :
89100 if resp .status != 200 :
90101 return []
91102 resp_data = await resp .json ()
92- # API возвращает {location: {...}, data: [...]}
93103 return resp_data .get ("data" , resp_data )
94104 except Exception as e :
95105 logger .warning (f"Error fetching permissions: { e } " )
@@ -104,8 +114,9 @@ async def send_permission_response(
104114 """
105115 reply_map = {"always" : "always" , "once" : "once" , "never" : "reject" }
106116 reply = reply_map .get (response , response )
107- url = f"{ self .base_url } /api/session/{ session_id } /permission/{ permission_id } /reply"
108- data = {"reply" : reply }
117+ # opencode API: POST /session/:sessionID/permissions/:permissionID
118+ url = f"{ self .base_url } /session/{ session_id } /permissions/{ permission_id } "
119+ data = {"response" : reply }
109120 async with self .session .post (url , json = data ) as resp :
110121 if resp .status in (200 , 204 ):
111122 logger .debug (f"Permission { permission_id } answered: { reply } " )
@@ -118,11 +129,10 @@ async def send_permission_response(
118129 async def get_pending_questions (self ) -> List [dict ]:
119130 """Получает список ожидающих вопросов."""
120131 try :
121- async with self .session .get (f"{ self .base_url } /api/ question/request" ) as resp :
132+ async with self .session .get (f"{ self .base_url } /question/request" ) as resp :
122133 if resp .status != 200 :
123134 return []
124135 resp_data = await resp .json ()
125- # API возвращает {location: {...}, data: [...]}
126136 return resp_data .get ("data" , resp_data )
127137 except Exception as e :
128138 logger .warning (f"Error fetching questions: { e } " )
@@ -132,7 +142,7 @@ async def send_question_answer(
132142 self , session_id : str , question_id : str , answer : str
133143 ) -> bool :
134144 """Отправляет ответ на вопрос."""
135- url = f"{ self .base_url } /api/ session/{ session_id } /question/{ question_id } /reply"
145+ url = f"{ self .base_url } /session/{ session_id } /question/{ question_id } /reply"
136146 data = {"answers" : [[answer ]]}
137147 async with self .session .post (url , json = data ) as resp :
138148 if resp .status in (200 , 204 ):
@@ -146,12 +156,12 @@ async def send_question_answer(
146156 async def get_all_sessions (self ) -> List [dict ]:
147157 """Получает список всех сессий."""
148158 try :
149- async with self .session .get (f"{ self .base_url } /api/session" ) as resp :
159+ # opencode API: GET /session
160+ async with self .session .get (f"{ self .base_url } /session" ) as resp :
150161 if resp .status != 200 :
151162 return []
152163 resp_data = await resp .json ()
153- # API возвращает {data: [...], cursor: {...}}
154- return resp_data .get ("data" , resp_data )
164+ return resp_data if isinstance (resp_data , list ) else resp_data .get ("data" , resp_data )
155165 except Exception as e :
156166 logger .warning (f"Error fetching sessions list: { e } " )
157167 return []
@@ -164,7 +174,8 @@ async def get_child_sessions(self, parent_id: str) -> List[dict]:
164174 async def delete_session (self , session_id : str ) -> bool :
165175 """Удаляет сессию по ID."""
166176 try :
167- async with self .session .delete (f"{ self .base_url } /api/session/{ session_id } " ) as resp :
177+ # opencode API: DELETE /session/:sessionID
178+ async with self .session .delete (f"{ self .base_url } /session/{ session_id } " ) as resp :
168179 if resp .status in (200 , 204 ):
169180 logger .debug (f"Deleted session { session_id } " )
170181 return True
0 commit comments