Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ That's it! When your application calls `copilot.NewClient` without a `CLIPath` n
- `ResumeSessionWithOptions(sessionID string, config *ResumeSessionConfig) (*Session, error)` - Resume with additional configuration
- `ListSessions(filter *SessionListFilter) ([]SessionMetadata, error)` - List sessions (with optional filter)
- `DeleteSession(sessionID string) error` - Delete a session permanently
- `GetLastSessionID(ctx context.Context) (*string, error)` - Get the ID of the most recently updated session
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API reference list is inconsistent about whether context.Context parameters are shown (e.g., DeleteSession(sessionID string) omits ctx while GetLastSessionID(ctx context.Context) includes it). Please align this new entry with the surrounding documentation style so the list is consistent and not misleading about call signatures.

Suggested change
- `GetLastSessionID(ctx context.Context) (*string, error)` - Get the ID of the most recently updated session
- `GetLastSessionID() (*string, error)` - Get the ID of the most recently updated session

Copilot uses AI. Check for mistakes.
- `GetState() ConnectionState` - Get connection state
- `Ping(message string) (*PingResponse, error)` - Ping the server
- `GetForegroundSessionID(ctx context.Context) (*string, error)` - Get the session ID currently displayed in TUI (TUI+server mode only)
Expand Down
34 changes: 34 additions & 0 deletions go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,40 @@ func (c *Client) DeleteSession(ctx context.Context, sessionID string) error {
return nil
}

// GetLastSessionID returns the ID of the most recently updated session.
//
// This is useful for resuming the last conversation when the session ID
// was not stored. Returns nil if no sessions exist.
//
// Example:
//
// lastID, err := client.GetLastSessionID(context.Background())
// if err != nil {
// log.Fatal(err)
// }
// if lastID != nil {
// session, err := client.ResumeSession(context.Background(), *lastID, &copilot.ResumeSessionConfig{
// OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
// })
// }
func (c *Client) GetLastSessionID(ctx context.Context) (*string, error) {
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetLastSessionID accepts a ctx but never uses it; if the client is disconnected and AutoStart is enabled, it will start using context.Background() via ensureConnected(), so caller cancellation/deadlines are ignored. Consider following the pattern used by GetForegroundSessionID (start with the provided ctx when c.client == nil) so the method respects the supplied context.

Suggested change
func (c *Client) GetLastSessionID(ctx context.Context) (*string, error) {
func (c *Client) GetLastSessionID(ctx context.Context) (*string, error) {
if ctx == nil {
ctx = context.Background()
}
// Respect caller cancellation/deadlines before attempting to connect.
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}

Copilot uses AI. Check for mistakes.
if err := c.ensureConnected(); err != nil {
return nil, err
}

result, err := c.client.Request("session.getLastId", getLastSessionIDRequest{})
if err != nil {
return nil, err
}

var response getLastSessionIDResponse
if err := json.Unmarshal(result, &response); err != nil {
return nil, fmt.Errorf("failed to unmarshal getLastId response: %w", err)
}

return response.SessionID, nil
}

// GetForegroundSessionID returns the ID of the session currently displayed in the TUI.
//
// This is only available when connecting to a server running in TUI+server mode
Expand Down
34 changes: 34 additions & 0 deletions go/internal/e2e/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,40 @@ func TestSession(t *testing.T) {
t.Error("Expected error when resuming deleted session")
}
})
t.Run("should get last session id", func(t *testing.T) {
ctx.ConfigureForTest(t)

// Create a session and send a message to persist it
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
}

_, err = session.SendAndWait(t.Context(), copilot.MessageOptions{Prompt: "Say hello"})
if err != nil {
t.Fatalf("Failed to send message: %v", err)
}

// Small delay to ensure session data is flushed to disk
time.Sleep(500 * time.Millisecond)

lastSessionID, err := client.GetLastSessionID(t.Context())
if err != nil {
t.Fatalf("Failed to get last session ID: %v", err)
}

if lastSessionID == nil {
t.Fatal("Expected last session ID to be non-nil")
}

if *lastSessionID != session.SessionID {
t.Errorf("Expected last session ID to be %s, got %s", session.SessionID, *lastSessionID)
}

if err := session.Destroy(); err != nil {
t.Fatalf("Failed to destroy session: %v", err)
}
})
}

func getSystemMessage(exchange testharness.ParsedHttpExchange) string {
Expand Down
8 changes: 8 additions & 0 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,14 @@ type deleteSessionResponse struct {
Error *string `json:"error,omitempty"`
}

// getLastSessionIDRequest is the request for session.getLastId
type getLastSessionIDRequest struct{}

// getLastSessionIDResponse is the response from session.getLastId
type getLastSessionIDResponse struct {
SessionID *string `json:"sessionId,omitempty"`
}

// getForegroundSessionRequest is the request for session.getForeground
type getForegroundSessionRequest struct{}

Expand Down
24 changes: 24 additions & 0 deletions python/copilot/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,30 @@ async def delete_session(self, session_id: str) -> None:
if session_id in self._sessions:
del self._sessions[session_id]

async def get_last_session_id(self) -> str | None:
"""
Get the ID of the most recently updated session.

This is useful for resuming the last conversation when the session ID
was not stored.

Returns:
The session ID, or None if no sessions exist.

Raises:
RuntimeError: If the client is not connected.

Example:
>>> last_id = await client.get_last_session_id()
>>> if last_id:
... session = await client.resume_session(last_id, {"on_permission_request": PermissionHandler.approve_all})
"""
if not self._client:
raise RuntimeError("Client not connected")

response = await self._client.request("session.getLastId", {})
return response.get("sessionId")

async def get_foreground_session_id(self) -> str | None:
"""
Get the ID of the session currently displayed in the TUI.
Expand Down
17 changes: 17 additions & 0 deletions python/e2e/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,23 @@ async def test_should_delete_session(self, ctx: E2ETestContext):
session_id, {"on_permission_request": PermissionHandler.approve_all}
)

async def test_should_get_last_session_id(self, ctx: E2ETestContext):
import asyncio

# Create a session and send a message to persist it
session = await ctx.client.create_session(
{"on_permission_request": PermissionHandler.approve_all}
)
await session.send_and_wait({"prompt": "Say hello"})

# Small delay to ensure session data is flushed to disk
await asyncio.sleep(0.5)

last_session_id = await ctx.client.get_last_session_id()
assert last_session_id == session.session_id

await session.destroy()

async def test_should_create_session_with_custom_tool(self, ctx: E2ETestContext):
# This test uses the low-level Tool() API to show that Pydantic is optional
def get_secret_number_handler(invocation):
Expand Down
10 changes: 10 additions & 0 deletions test/snapshots/session/should_get_last_session_id.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
models:
- claude-sonnet-4.5
conversations:
- messages:
- role: system
content: ${system}
- role: user
content: Say hello
- role: assistant
content: Hello! I'm GitHub Copilot CLI, ready to help with your software engineering tasks.
Loading