11from __future__ import annotations
22
3- import asyncio
43import json
54import logging
65from typing import Any , AsyncGenerator
@@ -35,81 +34,151 @@ async def _get(self, path: str, **kwargs: Any) -> httpx.Response:
3534 resp .raise_for_status ()
3635 return resp
3736
38- async def _post (self , path : str , ** kwargs : Any ) -> httpx .Response :
39- resp = await self ._client .post (path , ** kwargs )
37+ async def _post (self , path : str , json : dict | None = None , ** kwargs : Any ) -> httpx .Response :
38+ resp = await self ._client .post (path , json = json , ** kwargs )
39+ resp .raise_for_status ()
40+ return resp
41+
42+ async def _delete (self , path : str , ** kwargs : Any ) -> httpx .Response :
43+ resp = await self ._client .delete (path , ** kwargs )
44+ resp .raise_for_status ()
45+ return resp
46+
47+ async def _patch (self , path : str , json : dict | None = None , ** kwargs : Any ) -> httpx .Response :
48+ resp = await self ._client .patch (path , json = json , ** kwargs )
4049 resp .raise_for_status ()
4150 return resp
4251
4352 async def health (self ) -> dict [str , Any ]:
44- resp = await self ._get ("/health" )
53+ resp = await self ._get ("/global/ health" )
4554 return resp .json ()
4655
4756 async def get_projects (self ) -> list [dict [str , Any ]]:
48- resp = await self ._get ("/api/projects " )
57+ resp = await self ._get ("/project " )
4958 data = resp .json ()
5059 return data if isinstance (data , list ) else []
5160
5261 async def get_current_project (self ) -> dict [str , Any ]:
53- resp = await self ._get ("/api/project" )
54- return resp .json ()
55-
56- async def switch_project (self , project_id : str ) -> dict [str , Any ]:
57- resp = await self ._post ("/api/project/switch" , json = {"id" : project_id })
62+ resp = await self ._get ("/project/current" )
5863 return resp .json ()
5964
6065 async def get_sessions (self ) -> list [dict [str , Any ]]:
61- resp = await self ._get ("/api/sessions " )
66+ resp = await self ._get ("/session " )
6267 data = resp .json ()
6368 return data if isinstance (data , list ) else []
6469
65- async def create_session (self , agent : str = "build" ) -> dict [str , Any ]:
66- resp = await self ._post ("/api/sessions" , json = {"agent" : agent })
70+ async def create_session (
71+ self ,
72+ title : str | None = None ,
73+ parent_id : str | None = None ,
74+ ) -> dict [str , Any ]:
75+ body : dict [str , Any ] = {}
76+ if title :
77+ body ["title" ] = title
78+ if parent_id :
79+ body ["parentID" ] = parent_id
80+ resp = await self ._post ("/session" , json = body )
6781 return resp .json ()
6882
6983 async def get_session (self , session_id : str ) -> dict [str , Any ]:
70- resp = await self ._get (f"/api/sessions/{ session_id } " )
71- return resp .json ()
72-
73- async def delete_session (self , session_id : str ) -> dict [str , Any ]:
74- resp = await self ._post (f"/api/sessions/{ session_id } /delete" )
84+ resp = await self ._get (f"/session/{ session_id } " )
7585 return resp .json ()
7686
77- async def compact_session (self , session_id : str ) -> dict [ str , Any ] :
78- resp = await self ._post (f"/api/sessions/ { session_id } /compact " )
87+ async def delete_session (self , session_id : str ) -> bool :
88+ resp = await self ._delete (f"/session/ { session_id } " )
7989 return resp .json ()
8090
8191 async def rename_session (self , session_id : str , title : str ) -> dict [str , Any ]:
82- resp = await self ._post (f"/api/sessions/ { session_id } /rename " , json = {"title" : title })
92+ resp = await self ._patch (f"/session/ { session_id } " , json = {"title" : title })
8393 return resp .json ()
8494
85- async def abort_session (self , session_id : str ) -> dict [ str , Any ] :
86- resp = await self ._post (f"/api/sessions /{ session_id } /abort" )
95+ async def abort_session (self , session_id : str ) -> bool :
96+ resp = await self ._post (f"/session /{ session_id } /abort" )
8797 return resp .json ()
8898
89- async def get_models (self ) -> dict [ str , Any ] :
90- resp = await self ._get ( "/api/models" )
99+ async def summarize_session (self , session_id : str ) -> bool :
100+ resp = await self ._post ( f"/session/ { session_id } /summarize" , json = {} )
91101 return resp .json ()
92102
93- async def switch_model (self , provider : str , model_id : str ) -> dict [str , Any ]:
94- resp = await self ._post ("/api/models/switch" , json = { "provider" : provider , "model" : model_id } )
103+ async def get_session_status (self ) -> dict [str , Any ]:
104+ resp = await self ._get ("/session/status" )
95105 return resp .json ()
96106
107+ async def get_session_todo (self , session_id : str ) -> list [dict [str , Any ]]:
108+ resp = await self ._get (f"/session/{ session_id } /todo" )
109+ data = resp .json ()
110+ return data if isinstance (data , list ) else []
111+
112+ async def get_messages (self , session_id : str , limit : int = 50 ) -> list [dict [str , Any ]]:
113+ resp = await self ._get (f"/session/{ session_id } /message" , params = {"limit" : limit })
114+ data = resp .json ()
115+ return data if isinstance (data , list ) else []
116+
97117 async def send_message (
98118 self ,
99119 session_id : str ,
100120 message : str ,
101- files : list [dict [str , str ]] | None = None ,
121+ model : str | None = None ,
122+ agent : str | None = None ,
102123 ) -> dict [str , Any ]:
103- payload : dict [str , Any ] = {"message" : message }
104- if files :
105- payload ["files" ] = files
106- resp = await self ._post (f"/api/sessions/{ session_id } /message" , json = payload )
124+ body : dict [str , Any ] = {
125+ "parts" : [{"type" : "text" , "text" : message }]
126+ }
127+ if model :
128+ body ["model" ] = model
129+ if agent :
130+ body ["agent" ] = agent
131+ resp = await self ._post (f"/session/{ session_id } /message" , json = body )
107132 return resp .json ()
108133
109- async def stream_session_events (self , session_id : str ) -> AsyncGenerator [dict [str , Any ], None ]:
110- """Subscribe to SSE events for a session."""
111- url = f"/api/sessions/{ session_id } /events"
112- async with self ._client .stream ("GET" , url ) as resp :
134+ async def send_message_async (
135+ self ,
136+ session_id : str ,
137+ message : str ,
138+ model : str | None = None ,
139+ agent : str | None = None ,
140+ ) -> None :
141+ body : dict [str , Any ] = {
142+ "parts" : [{"type" : "text" , "text" : message }]
143+ }
144+ if model :
145+ body ["model" ] = model
146+ if agent :
147+ body ["agent" ] = agent
148+ await self ._post (f"/session/{ session_id } /prompt_async" , json = body )
149+
150+ async def get_providers (self ) -> dict [str , Any ]:
151+ resp = await self ._get ("/provider" )
152+ return resp .json ()
153+
154+ async def get_config_providers (self ) -> dict [str , Any ]:
155+ resp = await self ._get ("/config/providers" )
156+ return resp .json ()
157+
158+ async def get_agents (self ) -> list [dict [str , Any ]]:
159+ resp = await self ._get ("/agent" )
160+ data = resp .json ()
161+ return data if isinstance (data , list ) else []
162+
163+ async def get_commands (self ) -> list [dict [str , Any ]]:
164+ resp = await self ._get ("/command" )
165+ data = resp .json ()
166+ return data if isinstance (data , list ) else []
167+
168+ async def run_command (
169+ self ,
170+ session_id : str ,
171+ command : str ,
172+ arguments : str = "" ,
173+ ) -> dict [str , Any ]:
174+ body : dict [str , Any ] = {"command" : command }
175+ if arguments :
176+ body ["arguments" ] = arguments
177+ resp = await self ._post (f"/session/{ session_id } /command" , json = body )
178+ return resp .json ()
179+
180+ async def stream_events (self ) -> AsyncGenerator [dict [str , Any ], None ]:
181+ async with self ._client .stream ("GET" , "/event" ) as resp :
113182 resp .raise_for_status ()
114183 buffer = ""
115184 async for chunk in resp .aiter_text ():
@@ -121,6 +190,13 @@ async def stream_session_events(self, session_id: str) -> AsyncGenerator[dict[st
121190 if event :
122191 yield event
123192
193+ async def stream_session_events (self , session_id : str ) -> AsyncGenerator [dict [str , Any ], None ]:
194+ async for event in self .stream_events ():
195+ data = event .get ("data" , {})
196+ props = data .get ("properties" , {})
197+ if props .get ("sessionID" ) == session_id :
198+ yield event
199+
124200 @staticmethod
125201 def _parse_sse_event (text : str ) -> dict [str , Any ] | None :
126202 event_type = None
@@ -138,22 +214,24 @@ def _parse_sse_event(text: str) -> dict[str, Any] | None:
138214 return None
139215 return None
140216
141- async def get_session_status (self , session_id : str ) -> dict [str , Any ]:
142- try :
143- session = await self .get_session (session_id )
144- return {
145- "session_id" : session_id ,
146- "status" : session .get ("status" , "idle" ),
147- "model" : session .get ("model" , "" ),
148- "tokens" : session .get ("tokens" , {}),
149- "agent" : session .get ("agent" , "build" ),
150- }
151- except Exception :
152- return {"session_id" : session_id , "status" : "error" }
153-
154- async def run_custom_command (self , command : str , session_id : str | None = None ) -> dict [str , Any ]:
155- payload = {"command" : command }
156- if session_id :
157- payload ["session_id" ] = session_id
158- resp = await self ._post ("/api/commands/run" , json = payload )
159- return resp .json ()
217+ @staticmethod
218+ def extract_text_from_response (response : dict [str , Any ]) -> str :
219+ parts = response .get ("parts" , [])
220+ text_parts = []
221+ for part in parts :
222+ if part .get ("type" ) == "text" :
223+ text_parts .append (part .get ("text" , "" ))
224+ return "\n " .join (text_parts )
225+
226+ @staticmethod
227+ def extract_tool_calls (response : dict [str , Any ]) -> list [dict [str , Any ]]:
228+ parts = response .get ("parts" , [])
229+ tool_calls = []
230+ for part in parts :
231+ if part .get ("type" ) == "tool" :
232+ tool_calls .append ({
233+ "name" : part .get ("name" , part .get ("toolName" , "unknown" )),
234+ "input" : part .get ("input" , {}),
235+ "status" : part .get ("status" , "unknown" ),
236+ })
237+ return tool_calls
0 commit comments