Skip to content

Commit ef5001c

Browse files
committed
Expose session context in listSessions and add filtering
Adds SessionContext to SessionMetadata so SDK consumers can see the working directory and repository information for each session. Also adds optional filter parameter to listSessions() for filtering by context fields (cwd, gitRoot, repository, branch). Implemented in all SDK clients: - Node.js - Python - Go - .NET Fixes #413 Fixes #200
1 parent ccb7d5f commit ef5001c

File tree

16 files changed

+267
-19
lines changed

16 files changed

+267
-19
lines changed

cookbook/nodejs/multiple-sessions.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ console.log(session.sessionId); // "user-123-chat"
6363
const sessions = await client.listSessions();
6464
console.log(sessions);
6565
// [{ sessionId: "user-123-chat", ... }, ...]
66+
67+
// Sessions include context (cwd, git info) from when they were created
68+
for (const s of sessions) {
69+
console.log(`${s.sessionId} - ${s.context?.cwd ?? "unknown"}`);
70+
}
6671
```
6772
6873
## Deleting sessions

cookbook/nodejs/persisting-sessions.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ console.log(sessions);
6464
// { sessionId: "user-123-conversation", ... },
6565
// { sessionId: "user-456-conversation", ... },
6666
// ]
67+
68+
// Each session includes context from when it was created
69+
for (const session of sessions) {
70+
console.log(`Session: ${session.sessionId}`);
71+
if (session.context) {
72+
console.log(` Directory: ${session.context.cwd}`);
73+
console.log(` Repository: ${session.context.repository}`);
74+
console.log(` Branch: ${session.context.branch}`);
75+
}
76+
}
6777
```
6878
6979
### Deleting a session permanently

dotnet/src/Client.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ public async Task DeleteSessionAsync(string sessionId, CancellationToken cancell
613613
/// <summary>
614614
/// Lists all sessions known to the Copilot server.
615615
/// </summary>
616+
/// <param name="filter">Optional filter to narrow down the session list by cwd, git root, repository, or branch.</param>
616617
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
617618
/// <returns>A task that resolves with a list of <see cref="SessionMetadata"/> for all available sessions.</returns>
618619
/// <exception cref="InvalidOperationException">Thrown when the client is not connected.</exception>
@@ -625,12 +626,12 @@ public async Task DeleteSessionAsync(string sessionId, CancellationToken cancell
625626
/// }
626627
/// </code>
627628
/// </example>
628-
public async Task<List<SessionMetadata>> ListSessionsAsync(CancellationToken cancellationToken = default)
629+
public async Task<List<SessionMetadata>> ListSessionsAsync(SessionListFilter? filter = null, CancellationToken cancellationToken = default)
629630
{
630631
var connection = await EnsureConnectedAsync(cancellationToken);
631632

632633
var response = await InvokeRpcAsync<ListSessionsResponse>(
633-
connection.Rpc, "session.list", [], cancellationToken);
634+
connection.Rpc, "session.list", [new ListSessionsRequest(filter)], cancellationToken);
634635

635636
return response.Sessions;
636637
}
@@ -1149,6 +1150,9 @@ internal record DeleteSessionResponse(
11491150
bool Success,
11501151
string? Error);
11511152

1153+
internal record ListSessionsRequest(
1154+
SessionListFilter? Filter);
1155+
11521156
internal record ListSessionsResponse(
11531157
List<SessionMetadata> Sessions);
11541158

@@ -1218,6 +1222,7 @@ public override void WriteLine(string? message) =>
12181222
[JsonSerializable(typeof(DeleteSessionResponse))]
12191223
[JsonSerializable(typeof(GetLastSessionIdResponse))]
12201224
[JsonSerializable(typeof(HooksInvokeResponse))]
1225+
[JsonSerializable(typeof(ListSessionsRequest))]
12211226
[JsonSerializable(typeof(ListSessionsResponse))]
12221227
[JsonSerializable(typeof(PermissionRequestResponse))]
12231228
[JsonSerializable(typeof(PermissionRequestResult))]

dotnet/src/Types.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,13 +832,45 @@ public class MessageOptions
832832

833833
public delegate void SessionEventHandler(SessionEvent sessionEvent);
834834

835+
/// <summary>
836+
/// Working directory context for a session.
837+
/// </summary>
838+
public class SessionContext
839+
{
840+
/// <summary>Working directory where the session was created.</summary>
841+
public string Cwd { get; set; } = string.Empty;
842+
/// <summary>Git repository root (if in a git repo).</summary>
843+
public string? GitRoot { get; set; }
844+
/// <summary>GitHub repository in "owner/repo" format.</summary>
845+
public string? Repository { get; set; }
846+
/// <summary>Current git branch.</summary>
847+
public string? Branch { get; set; }
848+
}
849+
850+
/// <summary>
851+
/// Filter options for listing sessions.
852+
/// </summary>
853+
public class SessionListFilter
854+
{
855+
/// <summary>Filter by exact cwd match.</summary>
856+
public string? Cwd { get; set; }
857+
/// <summary>Filter by git root.</summary>
858+
public string? GitRoot { get; set; }
859+
/// <summary>Filter by repository (owner/repo format).</summary>
860+
public string? Repository { get; set; }
861+
/// <summary>Filter by branch.</summary>
862+
public string? Branch { get; set; }
863+
}
864+
835865
public class SessionMetadata
836866
{
837867
public string SessionId { get; set; } = string.Empty;
838868
public DateTime StartTime { get; set; }
839869
public DateTime ModifiedTime { get; set; }
840870
public string? Summary { get; set; }
841871
public bool IsRemote { get; set; }
872+
/// <summary>Working directory context (cwd, git info) from session creation.</summary>
873+
public SessionContext? Context { get; set; }
842874
}
843875

844876
internal class PingRequest
@@ -1025,6 +1057,8 @@ public class GetModelsResponse
10251057
[JsonSerializable(typeof(PingRequest))]
10261058
[JsonSerializable(typeof(PingResponse))]
10271059
[JsonSerializable(typeof(ProviderConfig))]
1060+
[JsonSerializable(typeof(SessionContext))]
1061+
[JsonSerializable(typeof(SessionListFilter))]
10281062
[JsonSerializable(typeof(SessionMetadata))]
10291063
[JsonSerializable(typeof(SystemMessageConfig))]
10301064
[JsonSerializable(typeof(ToolBinaryResult))]

go/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func main() {
8080
- `CreateSession(config *SessionConfig) (*Session, error)` - Create a new session
8181
- `ResumeSession(sessionID string) (*Session, error)` - Resume an existing session
8282
- `ResumeSessionWithOptions(sessionID string, config *ResumeSessionConfig) (*Session, error)` - Resume with additional configuration
83-
- `ListSessions() ([]SessionMetadata, error)` - List all sessions known to the server
83+
- `ListSessions(filter *SessionListFilter) ([]SessionMetadata, error)` - List sessions (with optional filter)
8484
- `DeleteSession(sessionID string) error` - Delete a session permanently
8585
- `GetState() ConnectionState` - Get connection state
8686
- `Ping(message string) (*PingResponse, error)` - Ping the server

go/client.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -791,18 +791,24 @@ func (c *Client) ResumeSessionWithOptions(sessionID string, config *ResumeSessio
791791
// ListSessions returns metadata about all sessions known to the server.
792792
//
793793
// Returns a list of SessionMetadata for all available sessions, including their IDs,
794-
// timestamps, and optional summaries.
794+
// timestamps, optional summaries, and context information.
795+
//
796+
// An optional filter can be provided to filter sessions by cwd, git root, repository, or branch.
795797
//
796798
// Example:
797799
//
798-
// sessions, err := client.ListSessions()
800+
// sessions, err := client.ListSessions(nil)
799801
// if err != nil {
800802
// log.Fatal(err)
801803
// }
802804
// for _, session := range sessions {
803805
// fmt.Printf("Session: %s\n", session.SessionID)
804806
// }
805-
func (c *Client) ListSessions() ([]SessionMetadata, error) {
807+
//
808+
// Example with filter:
809+
//
810+
// sessions, err := client.ListSessions(&SessionListFilter{Repository: "owner/repo"})
811+
func (c *Client) ListSessions(filter *SessionListFilter) ([]SessionMetadata, error) {
806812
if c.client == nil {
807813
if c.autoStart {
808814
if err := c.Start(); err != nil {
@@ -813,7 +819,11 @@ func (c *Client) ListSessions() ([]SessionMetadata, error) {
813819
}
814820
}
815821

816-
result, err := c.client.Request("session.list", map[string]interface{}{})
822+
params := map[string]interface{}{}
823+
if filter != nil {
824+
params["filter"] = filter
825+
}
826+
result, err := c.client.Request("session.list", params)
817827
if err != nil {
818828
return nil, err
819829
}

go/e2e/session_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ func TestSession(t *testing.T) {
779779
time.Sleep(200 * time.Millisecond)
780780

781781
// List sessions and verify they're included
782-
sessions, err := client.ListSessions()
782+
sessions, err := client.ListSessions(nil)
783783
if err != nil {
784784
t.Fatalf("Failed to list sessions: %v", err)
785785
}
@@ -838,7 +838,7 @@ func TestSession(t *testing.T) {
838838
time.Sleep(200 * time.Millisecond)
839839

840840
// Verify session exists in the list
841-
sessions, err := client.ListSessions()
841+
sessions, err := client.ListSessions(nil)
842842
if err != nil {
843843
t.Fatalf("Failed to list sessions: %v", err)
844844
}
@@ -859,7 +859,7 @@ func TestSession(t *testing.T) {
859859
}
860860

861861
// Verify session no longer exists in the list
862-
sessionsAfter, err := client.ListSessions()
862+
sessionsAfter, err := client.ListSessions(nil)
863863
if err != nil {
864864
t.Fatalf("Failed to list sessions after delete: %v", err)
865865
}

go/types.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -552,13 +552,38 @@ type GetModelsResponse struct {
552552
Models []ModelInfo `json:"models"`
553553
}
554554

555+
// SessionContext contains working directory context for a session
556+
type SessionContext struct {
557+
// Cwd is the working directory where the session was created
558+
Cwd string `json:"cwd"`
559+
// GitRoot is the git repository root (if in a git repo)
560+
GitRoot string `json:"gitRoot,omitempty"`
561+
// Repository is the GitHub repository in "owner/repo" format
562+
Repository string `json:"repository,omitempty"`
563+
// Branch is the current git branch
564+
Branch string `json:"branch,omitempty"`
565+
}
566+
567+
// SessionListFilter contains filter options for listing sessions
568+
type SessionListFilter struct {
569+
// Cwd filters by exact working directory match
570+
Cwd string `json:"cwd,omitempty"`
571+
// GitRoot filters by git root
572+
GitRoot string `json:"gitRoot,omitempty"`
573+
// Repository filters by repository (owner/repo format)
574+
Repository string `json:"repository,omitempty"`
575+
// Branch filters by branch
576+
Branch string `json:"branch,omitempty"`
577+
}
578+
555579
// SessionMetadata contains metadata about a session
556580
type SessionMetadata struct {
557-
SessionID string `json:"sessionId"`
558-
StartTime string `json:"startTime"`
559-
ModifiedTime string `json:"modifiedTime"`
560-
Summary *string `json:"summary,omitempty"`
561-
IsRemote bool `json:"isRemote"`
581+
SessionID string `json:"sessionId"`
582+
StartTime string `json:"startTime"`
583+
ModifiedTime string `json:"modifiedTime"`
584+
Summary *string `json:"summary,omitempty"`
585+
IsRemote bool `json:"isRemote"`
586+
Context *SessionContext `json:"context,omitempty"`
562587
}
563588

564589
// ListSessionsResponse is the response from session.list

nodejs/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,22 @@ Get current connection state.
111111

112112
List all available sessions.
113113

114+
**SessionMetadata:**
115+
116+
- `sessionId: string` - Unique session identifier
117+
- `startTime: Date` - When the session was created
118+
- `modifiedTime: Date` - When the session was last modified
119+
- `summary?: string` - Optional session summary
120+
- `isRemote: boolean` - Whether the session is remote
121+
- `context?: SessionContext` - Working directory context from session creation
122+
123+
**SessionContext:**
124+
125+
- `cwd: string` - Working directory where the session was created
126+
- `gitRoot?: string` - Git repository root (if in a git repo)
127+
- `repository?: string` - GitHub repository in "owner/repo" format
128+
- `branch?: string` - Current git branch
129+
114130
##### `deleteSession(sessionId: string): Promise<void>`
115131

116132
Delete a session and its data from disk.

nodejs/src/client.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type {
3030
ResumeSessionConfig,
3131
SessionConfig,
3232
SessionEvent,
33+
SessionListFilter,
3334
SessionMetadata,
3435
Tool,
3536
ToolCallRequestPayload,
@@ -749,19 +750,25 @@ export class CopilotClient {
749750
* }
750751
* ```
751752
*/
752-
async listSessions(): Promise<SessionMetadata[]> {
753+
async listSessions(filter?: SessionListFilter): Promise<SessionMetadata[]> {
753754
if (!this.connection) {
754755
throw new Error("Client not connected");
755756
}
756757

757-
const response = await this.connection.sendRequest("session.list", {});
758+
const response = await this.connection.sendRequest("session.list", { filter });
758759
const { sessions } = response as {
759760
sessions: Array<{
760761
sessionId: string;
761762
startTime: string;
762763
modifiedTime: string;
763764
summary?: string;
764765
isRemote: boolean;
766+
context?: {
767+
cwd: string;
768+
gitRoot?: string;
769+
repository?: string;
770+
branch?: string;
771+
};
765772
}>;
766773
};
767774

@@ -771,6 +778,7 @@ export class CopilotClient {
771778
modifiedTime: new Date(s.modifiedTime),
772779
summary: s.summary,
773780
isRemote: s.isRemote,
781+
context: s.context,
774782
}));
775783
}
776784

0 commit comments

Comments
 (0)