Skip to content

Commit 8f3e4ba

Browse files
Copilotfriggeri
andauthored
Add missing list_sessions() method to Python SDK client (github#153)
* Initial plan * Add list_sessions method to Python SDK client Co-authored-by: friggeri <106686+friggeri@users.noreply.github.com> * Improve type annotations for list_sessions method Co-authored-by: friggeri <106686+friggeri@users.noreply.github.com> * Add delete_session method to Python SDK client Co-authored-by: friggeri <106686+friggeri@users.noreply.github.com> * Use RuntimeError for consistent exception handling in delete_session Co-authored-by: friggeri <106686+friggeri@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: friggeri <106686+friggeri@users.noreply.github.com>
1 parent badba6c commit 8f3e4ba

File tree

4 files changed

+116
-0
lines changed

4 files changed

+116
-0
lines changed

python/copilot/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
ResumeSessionConfig,
2929
SessionConfig,
3030
SessionEvent,
31+
SessionMetadata,
3132
Tool,
3233
ToolHandler,
3334
ToolInvocation,
@@ -59,6 +60,7 @@
5960
"ResumeSessionConfig",
6061
"SessionConfig",
6162
"SessionEvent",
63+
"SessionMetadata",
6264
"Tool",
6365
"ToolHandler",
6466
"ToolInvocation",

python/copilot/client.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
ModelInfo,
3434
ResumeSessionConfig,
3535
SessionConfig,
36+
SessionMetadata,
3637
ToolHandler,
3738
ToolInvocation,
3839
ToolResult,
@@ -633,6 +634,62 @@ async def list_models(self) -> List["ModelInfo"]:
633634
response = await self._client.request("models.list", {})
634635
return response.get("models", [])
635636

637+
async def list_sessions(self) -> List["SessionMetadata"]:
638+
"""
639+
List all available sessions known to the server.
640+
641+
Returns metadata about each session including ID, timestamps, and summary.
642+
643+
Returns:
644+
A list of session metadata dictionaries with keys: sessionId (str),
645+
startTime (str), modifiedTime (str), summary (str, optional),
646+
and isRemote (bool).
647+
648+
Raises:
649+
RuntimeError: If the client is not connected.
650+
651+
Example:
652+
>>> sessions = await client.list_sessions()
653+
>>> for session in sessions:
654+
... print(f"Session: {session['sessionId']}")
655+
"""
656+
if not self._client:
657+
raise RuntimeError("Client not connected")
658+
659+
response = await self._client.request("session.list", {})
660+
return response.get("sessions", [])
661+
662+
async def delete_session(self, session_id: str) -> None:
663+
"""
664+
Delete a session permanently.
665+
666+
This permanently removes the session and all its conversation history.
667+
The session cannot be resumed after deletion.
668+
669+
Args:
670+
session_id: The ID of the session to delete.
671+
672+
Raises:
673+
RuntimeError: If the client is not connected or deletion fails.
674+
675+
Example:
676+
>>> await client.delete_session("session-123")
677+
"""
678+
if not self._client:
679+
raise RuntimeError("Client not connected")
680+
681+
response = await self._client.request("session.delete", {"sessionId": session_id})
682+
683+
success = response.get("success", False)
684+
if not success:
685+
error = response.get("error", "Unknown error")
686+
raise RuntimeError(f"Failed to delete session {session_id}: {error}")
687+
688+
# Remove from local sessions map if present
689+
with self._sessions_lock:
690+
if session_id in self._sessions:
691+
del self._sessions[session_id]
692+
636693
async def _verify_protocol_version(self) -> None:
637694
"""Verify that the server's protocol version matches the SDK's expected version."""
638695
expected_version = get_sdk_protocol_version()

python/copilot/types.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,3 +359,13 @@ class GetModelsResponse(TypedDict):
359359
"""Response from models.list"""
360360

361361
models: List[ModelInfo]
362+
363+
364+
class SessionMetadata(TypedDict):
365+
"""Metadata about a session"""
366+
367+
sessionId: str # Session identifier
368+
startTime: str # ISO 8601 timestamp when session was created
369+
modifiedTime: str # ISO 8601 timestamp when session was last modified
370+
summary: NotRequired[str] # Optional summary of the session
371+
isRemote: bool # Whether the session is remote

python/e2e/test_session.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,53 @@ async def test_should_throw_error_resuming_nonexistent_session(self, ctx: E2ETes
180180
with pytest.raises(Exception):
181181
await ctx.client.resume_session("non-existent-session-id")
182182

183+
async def test_should_list_sessions(self, ctx: E2ETestContext):
184+
# Create a couple of sessions
185+
session1 = await ctx.client.create_session()
186+
session2 = await ctx.client.create_session()
187+
188+
# List sessions and verify they're included
189+
sessions = await ctx.client.list_sessions()
190+
assert isinstance(sessions, list)
191+
192+
session_ids = [s["sessionId"] for s in sessions]
193+
assert session1.session_id in session_ids
194+
assert session2.session_id in session_ids
195+
196+
# Verify session metadata structure
197+
for session_data in sessions:
198+
assert "sessionId" in session_data
199+
assert "startTime" in session_data
200+
assert "modifiedTime" in session_data
201+
assert "isRemote" in session_data
202+
# summary is optional
203+
assert isinstance(session_data["sessionId"], str)
204+
assert isinstance(session_data["startTime"], str)
205+
assert isinstance(session_data["modifiedTime"], str)
206+
assert isinstance(session_data["isRemote"], bool)
207+
208+
async def test_should_delete_session(self, ctx: E2ETestContext):
209+
# Create a session
210+
session = await ctx.client.create_session()
211+
session_id = session.session_id
212+
213+
# Verify session exists in the list
214+
sessions = await ctx.client.list_sessions()
215+
session_ids = [s["sessionId"] for s in sessions]
216+
assert session_id in session_ids
217+
218+
# Delete the session
219+
await ctx.client.delete_session(session_id)
220+
221+
# Verify session no longer exists in the list
222+
sessions_after = await ctx.client.list_sessions()
223+
session_ids_after = [s["sessionId"] for s in sessions_after]
224+
assert session_id not in session_ids_after
225+
226+
# Verify we cannot resume the deleted session
227+
with pytest.raises(Exception):
228+
await ctx.client.resume_session(session_id)
229+
183230
async def test_should_create_session_with_custom_tool(self, ctx: E2ETestContext):
184231
# This test uses the low-level Tool() API to show that Pydantic is optional
185232
def get_secret_number_handler(invocation):

0 commit comments

Comments
 (0)