From 4377e73f5db849e380a803d975632d7cb44ec85f Mon Sep 17 00:00:00 2001 From: Patrick Nikoletich Date: Thu, 26 Feb 2026 21:15:40 -0800 Subject: [PATCH 1/5] docs: clarify session destroy vs delete semantics across all SDKs Clarify the distinction between destroy() (closes session, releases in-memory resources, preserves disk state for resumption) and deleteSession() (permanently removes all data from disk). Update doc comments across all four SDK languages (Go, Node.js, Python, .NET) and the session persistence guide to make the behavioral difference explicit and help users choose the right method. Fixes #526 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/guides/session-persistence.md | 21 ++++++++++++++++----- dotnet/src/Client.cs | 17 ++++++++++++----- dotnet/src/Session.cs | 15 +++++++++------ go/client.go | 11 +++++++++-- go/session.go | 14 +++++++++----- nodejs/src/client.ts | 14 ++++++++++---- nodejs/src/session.ts | 17 +++++++++++------ python/copilot/client.py | 14 ++++++++++---- python/copilot/session.py | 15 ++++++++++----- 9 files changed, 96 insertions(+), 42 deletions(-) diff --git a/docs/guides/session-persistence.md b/docs/guides/session-persistence.md index 527f5ecc7..dec91d5fb 100644 --- a/docs/guides/session-persistence.md +++ b/docs/guides/session-persistence.md @@ -325,16 +325,16 @@ async function cleanupExpiredSessions(maxAgeMs: number) { await cleanupExpiredSessions(24 * 60 * 60 * 1000); ``` -### Explicit Session Destruction +### Closing a Session (`destroy`) -When a task completes, destroy the session explicitly rather than waiting for timeouts: +When a task completes, close the session explicitly rather than waiting for timeouts. This releases in-memory resources but **preserves session data on disk**, so the session can still be resumed later: ```typescript try { // Do work... await session.sendAndWait({ prompt: "Complete the task" }); - // Task complete - clean up + // Task complete — release in-memory resources (session can be resumed later) await session.destroy(); } catch (error) { // Clean up even on error @@ -343,6 +343,17 @@ try { } ``` +### Permanently Deleting a Session (`deleteSession`) + +To permanently remove a session and all its data from disk (conversation history, planning state, artifacts), use `deleteSession`. This is irreversible — the session **cannot** be resumed after deletion: + +```typescript +// Permanently remove session data +await client.deleteSession("user-123-task-456"); +``` + +> **`destroy()` vs `deleteSession()`:** `destroy()` releases in-memory resources but keeps session data on disk for later resumption. `deleteSession()` permanently removes everything, including files on disk. + ## Automatic Cleanup: Idle Timeout The CLI has a built-in 30-minute idle timeout. Sessions without activity are automatically cleaned up: @@ -526,8 +537,8 @@ await withSessionLock("user-123-task-456", async () => { | **Resume session** | `client.resumeSession(sessionId)` | | **BYOK resume** | Re-provide `provider` config | | **List sessions** | `client.listSessions(filter?)` | -| **Delete session** | `client.deleteSession(sessionId)` | -| **Destroy active session** | `session.destroy()` | +| **Close active session** | `session.destroy()` — releases in-memory resources; session data on disk is preserved for resumption | +| **Delete session permanently** | `client.deleteSession(sessionId)` — permanently removes all session data from disk; cannot be resumed | | **Containerized deployment** | Mount `~/.copilot/session-state/` to persistent storage | ## Next Steps diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 1f3a7fb43..40e153260 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -210,18 +210,23 @@ async Task StartCoreAsync(CancellationToken ct) } /// - /// Disconnects from the Copilot server and stops all active sessions. + /// Disconnects from the Copilot server and closes all active sessions. /// /// A representing the asynchronous operation. /// /// /// This method performs graceful cleanup: /// - /// Destroys all active sessions + /// Closes all active sessions (releases in-memory resources) /// Closes the JSON-RPC connection /// Terminates the CLI server process (if spawned by this client) /// /// + /// + /// Note: session data on disk is preserved, so sessions can be resumed later. + /// To permanently remove session data before stopping, call + /// for each session first. + /// /// /// Thrown when multiple errors occur during cleanup. /// @@ -655,15 +660,17 @@ public async Task> ListModelsAsync(CancellationToken cancellatio } /// - /// Deletes a Copilot session by its ID. + /// Permanently deletes a session and all its data from disk, including + /// conversation history, planning state, and artifacts. /// /// The ID of the session to delete. /// A that can be used to cancel the operation. /// A task that represents the asynchronous delete operation. /// Thrown when the session does not exist or deletion fails. /// - /// This permanently removes the session and all its conversation history. - /// The session cannot be resumed after deletion. + /// Unlike , which only releases in-memory + /// resources and preserves session data for later resumption, this method is + /// irreversible. The session cannot be resumed after deletion. /// /// /// diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 923b193cc..02bb276dc 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -541,22 +541,25 @@ await InvokeRpcAsync( } /// - /// Disposes the and releases all associated resources. + /// Closes this session and releases all in-memory resources (event handlers, + /// tool handlers, permission handlers). /// /// A task representing the dispose operation. /// /// - /// After calling this method, the session can no longer be used. All event handlers - /// and tool handlers are cleared. + /// Session state on disk (conversation history, planning state, artifacts) is + /// preserved, so the conversation can be resumed later by calling + /// with the session ID. To + /// permanently remove all session data including files on disk, use + /// instead. /// /// - /// To continue the conversation, use - /// with the session ID. + /// After calling this method, the session object can no longer be used. /// /// /// /// - /// // Using 'await using' for automatic disposal + /// // Using 'await using' for automatic disposal — session can still be resumed later /// await using var session = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); /// /// // Or manually dispose diff --git a/go/client.go b/go/client.go index c88a68ac3..f140127b6 100644 --- a/go/client.go +++ b/go/client.go @@ -293,10 +293,14 @@ func (c *Client) Start(ctx context.Context) error { // Stop stops the CLI server and closes all active sessions. // // This method performs graceful cleanup: -// 1. Destroys all active sessions +// 1. Closes all active sessions (releases in-memory resources) // 2. Closes the JSON-RPC connection // 3. Terminates the CLI server process (if spawned by this client) // +// Note: session data on disk is preserved, so sessions can be resumed later. +// To permanently remove session data before stopping, call [Client.DeleteSession] +// for each session first. +// // Returns an error that aggregates all errors encountered during cleanup. // // Example: @@ -685,8 +689,11 @@ func (c *Client) ListSessions(ctx context.Context, filter *SessionListFilter) ([ return response.Sessions, nil } -// DeleteSession permanently deletes a session and all its conversation history. +// DeleteSession permanently deletes a session and all its data from disk, +// including conversation history, planning state, and artifacts. // +// Unlike [Session.Destroy], which only releases in-memory resources and +// preserves session data for later resumption, DeleteSession is irreversible. // The session cannot be resumed after deletion. If the session is in the local // sessions map, it will be removed. // diff --git a/go/session.go b/go/session.go index 12d1b1afa..561e566d1 100644 --- a/go/session.go +++ b/go/session.go @@ -511,17 +511,21 @@ func (s *Session) GetMessages(ctx context.Context) ([]SessionEvent, error) { return response.Events, nil } -// Destroy destroys this session and releases all associated resources. +// Destroy closes this session and releases all in-memory resources (event +// handlers, tool handlers, permission handlers). // -// After calling this method, the session can no longer be used. All event -// handlers and tool handlers are cleared. To continue the conversation, -// use [Client.ResumeSession] with the session ID. +// Session state on disk (conversation history, planning state, artifacts) is +// preserved, so the conversation can be resumed later by calling +// [Client.ResumeSession] with the session ID. To permanently remove all +// session data including files on disk, use [Client.DeleteSession] instead. +// +// After calling this method, the session object can no longer be used. // // Returns an error if the connection fails. // // Example: // -// // Clean up when done +// // Clean up when done — session can still be resumed later // if err := session.Destroy(); err != nil { // log.Printf("Failed to destroy session: %v", err) // } diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 6d841c7cc..c8c81dc74 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -307,10 +307,14 @@ export class CopilotClient { * Stops the CLI server and closes all active sessions. * * This method performs graceful cleanup: - * 1. Destroys all active sessions with retry logic + * 1. Closes all active sessions (releases in-memory resources) * 2. Closes the JSON-RPC connection * 3. Terminates the CLI server process (if spawned by this client) * + * Note: session data on disk is preserved, so sessions can be resumed later. + * To permanently remove session data before stopping, call + * {@link deleteSession} for each session first. + * * @returns A promise that resolves with an array of errors encountered during cleanup. * An empty array indicates all cleanup succeeded. * @@ -823,10 +827,12 @@ export class CopilotClient { } /** - * Deletes a session and its data from disk. + * Permanently deletes a session and all its data from disk, including + * conversation history, planning state, and artifacts. * - * This permanently removes the session and all its conversation history. - * The session cannot be resumed after deletion. + * Unlike {@link CopilotSession.destroy}, which only releases in-memory + * resources and preserves session data for later resumption, this method + * is irreversible. The session cannot be resumed after deletion. * * @param sessionId - The ID of the session to delete * @returns A promise that resolves when the session is deleted diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index 04525d2bb..6e32ee5f9 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -499,18 +499,23 @@ export class CopilotSession { } /** - * Destroys this session and releases all associated resources. + * Closes this session and releases all in-memory resources (event handlers, + * tool handlers, permission handlers). * - * After calling this method, the session can no longer be used. All event - * handlers and tool handlers are cleared. To continue the conversation, - * use {@link CopilotClient.resumeSession} with the session ID. + * Session state on disk (conversation history, planning state, artifacts) is + * preserved, so the conversation can be resumed later by calling + * {@link CopilotClient.resumeSession} with the session ID. To permanently + * remove all session data including files on disk, use + * {@link CopilotClient.deleteSession} instead. * - * @returns A promise that resolves when the session is destroyed + * After calling this method, the session object can no longer be used. + * + * @returns A promise that resolves when the session is closed * @throws Error if the connection fails * * @example * ```typescript - * // Clean up when done + * // Clean up when done — session can still be resumed later * await session.destroy(); * ``` */ diff --git a/python/copilot/client.py b/python/copilot/client.py index c25e68096..2d50c73e5 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -320,10 +320,14 @@ async def stop(self) -> list["StopError"]: Stop the CLI server and close all active sessions. This method performs graceful cleanup: - 1. Destroys all active sessions + 1. Closes all active sessions (releases in-memory resources) 2. Closes the JSON-RPC connection 3. Terminates the CLI server process (if spawned by this client) + Note: session data on disk is preserved, so sessions can be resumed + later. To permanently remove session data before stopping, call + :meth:`delete_session` for each session first. + Returns: A list of StopError objects containing error messages that occurred during cleanup. An empty list indicates all cleanup succeeded. @@ -928,10 +932,12 @@ async def list_sessions( async def delete_session(self, session_id: str) -> None: """ - Delete a session permanently. + Permanently delete a session and all its data from disk, including + conversation history, planning state, and artifacts. - This permanently removes the session and all its conversation history. - The session cannot be resumed after deletion. + Unlike :meth:`CopilotSession.destroy`, which only releases in-memory + resources and preserves session data for later resumption, this method + is irreversible. The session cannot be resumed after deletion. Args: session_id: The ID of the session to delete. diff --git a/python/copilot/session.py b/python/copilot/session.py index a02dcf1e9..72c085304 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -476,17 +476,22 @@ async def get_messages(self) -> list[SessionEvent]: async def destroy(self) -> None: """ - Destroy this session and release all associated resources. + Close this session and release all in-memory resources (event handlers, + tool handlers, permission handlers). - After calling this method, the session can no longer be used. All event - handlers and tool handlers are cleared. To continue the conversation, - use :meth:`CopilotClient.resume_session` with the session ID. + Session state on disk (conversation history, planning state, artifacts) + is preserved, so the conversation can be resumed later by calling + :meth:`CopilotClient.resume_session` with the session ID. To + permanently remove all session data including files on disk, use + :meth:`CopilotClient.delete_session` instead. + + After calling this method, the session object can no longer be used. Raises: Exception: If the connection fails. Example: - >>> # Clean up when done + >>> # Clean up when done — session can still be resumed later >>> await session.destroy() """ await self._client.request("session.destroy", {"sessionId": self.session_id}) From 1e6f1ee98be8425eef852e16928390fca7e44af1 Mon Sep 17 00:00:00 2001 From: Patrick Nikoletich Date: Wed, 4 Mar 2026 19:23:35 -0800 Subject: [PATCH 2/5] feat: add disconnect() method, deprecate destroy() across all SDKs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add disconnect() as the preferred method for closing sessions across all four SDK languages, marking destroy() as deprecated: - Node.js: disconnect() + Symbol.asyncDispose support, destroy() delegates - Python: disconnect() + __aenter__/__aexit__ context manager, destroy() emits DeprecationWarning - Go: Disconnect() method, Destroy() marked with Deprecated godoc tag - .NET: DisconnectAsync() method, DisposeAsync() delegates to it Update all samples, READMEs, and documentation guides to use the new disconnect() terminology. Internal stop() methods now call disconnect(). Resolves PR #599 comments: - Rename destroy → disconnect for clarity - Define IDisposable behavior in .NET (DisposeAsync delegates to DisconnectAsync) - Add idiomatic cleanup patterns (async context managers, Symbol.asyncDispose) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/auth/byok.md | 2 +- docs/debugging.md | 4 +- docs/guides/session-persistence.md | 14 +++--- docs/guides/setup/azure-managed-identity.md | 2 +- docs/guides/setup/backend-services.md | 2 +- docs/guides/setup/scaling.md | 4 +- docs/mcp/overview.md | 4 +- dotnet/src/Client.cs | 6 +-- dotnet/src/Session.cs | 48 ++++++++++++++++----- go/README.md | 4 +- go/client.go | 8 ++-- go/samples/chat.go | 2 +- go/session.go | 26 +++++++---- nodejs/README.md | 2 +- nodejs/examples/basic-example.ts | 2 +- nodejs/src/client.ts | 10 ++--- nodejs/src/session.ts | 36 ++++++++++++---- python/README.md | 6 +-- python/copilot/client.py | 8 ++-- python/copilot/session.py | 42 +++++++++++++++--- 20 files changed, 157 insertions(+), 75 deletions(-) diff --git a/docs/auth/byok.md b/docs/auth/byok.md index 13ad8b055..ca7861c16 100644 --- a/docs/auth/byok.md +++ b/docs/auth/byok.md @@ -54,7 +54,7 @@ async def main(): await session.send({"prompt": "What is 2+2?"}) await done.wait() - await session.destroy() + await session.disconnect() await client.stop() asyncio.run(main()) diff --git a/docs/debugging.md b/docs/debugging.md index 6183cccdf..bf953b2ff 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -248,9 +248,9 @@ var client = new CopilotClient(new CopilotClientOptions **Solution:** -1. Ensure you're not calling methods after `destroy()`: +1. Ensure you're not calling methods after `disconnect()`: ```typescript - await session.destroy(); + await session.disconnect(); // Don't use session after this! ``` diff --git a/docs/guides/session-persistence.md b/docs/guides/session-persistence.md index dec91d5fb..c34ee5dd2 100644 --- a/docs/guides/session-persistence.md +++ b/docs/guides/session-persistence.md @@ -325,9 +325,9 @@ async function cleanupExpiredSessions(maxAgeMs: number) { await cleanupExpiredSessions(24 * 60 * 60 * 1000); ``` -### Closing a Session (`destroy`) +### Disconnecting from a Session (`disconnect`) -When a task completes, close the session explicitly rather than waiting for timeouts. This releases in-memory resources but **preserves session data on disk**, so the session can still be resumed later: +When a task completes, disconnect from the session explicitly rather than waiting for timeouts. This releases in-memory resources but **preserves session data on disk**, so the session can still be resumed later: ```typescript try { @@ -335,14 +335,16 @@ try { await session.sendAndWait({ prompt: "Complete the task" }); // Task complete — release in-memory resources (session can be resumed later) - await session.destroy(); + await session.disconnect(); } catch (error) { // Clean up even on error - await session.destroy(); + await session.disconnect(); throw error; } ``` +> **Note:** `destroy()` is deprecated in favor of `disconnect()`. Existing code using `destroy()` will continue to work but should be migrated. + ### Permanently Deleting a Session (`deleteSession`) To permanently remove a session and all its data from disk (conversation history, planning state, artifacts), use `deleteSession`. This is irreversible — the session **cannot** be resumed after deletion: @@ -352,7 +354,7 @@ To permanently remove a session and all its data from disk (conversation history await client.deleteSession("user-123-task-456"); ``` -> **`destroy()` vs `deleteSession()`:** `destroy()` releases in-memory resources but keeps session data on disk for later resumption. `deleteSession()` permanently removes everything, including files on disk. +> **`disconnect()` vs `deleteSession()`:** `disconnect()` releases in-memory resources but keeps session data on disk for later resumption. `deleteSession()` permanently removes everything, including files on disk. ## Automatic Cleanup: Idle Timeout @@ -537,7 +539,7 @@ await withSessionLock("user-123-task-456", async () => { | **Resume session** | `client.resumeSession(sessionId)` | | **BYOK resume** | Re-provide `provider` config | | **List sessions** | `client.listSessions(filter?)` | -| **Close active session** | `session.destroy()` — releases in-memory resources; session data on disk is preserved for resumption | +| **Disconnect from active session** | `session.disconnect()` — releases in-memory resources; session data on disk is preserved for resumption | | **Delete session permanently** | `client.deleteSession(sessionId)` — permanently removes all session data from disk; cannot be resumed | | **Containerized deployment** | Mount `~/.copilot/session-state/` to persistent storage | diff --git a/docs/guides/setup/azure-managed-identity.md b/docs/guides/setup/azure-managed-identity.md index bfafc6f91..9ad1ddb15 100644 --- a/docs/guides/setup/azure-managed-identity.md +++ b/docs/guides/setup/azure-managed-identity.md @@ -118,7 +118,7 @@ class ManagedIdentityCopilotAgent: session = await self.client.create_session(config) response = await session.send_and_wait({"prompt": prompt}) - await session.destroy() + await session.disconnect() return response.data.content if response else "" ``` diff --git a/docs/guides/setup/backend-services.md b/docs/guides/setup/backend-services.md index c9bc13f8d..e0d0975db 100644 --- a/docs/guides/setup/backend-services.md +++ b/docs/guides/setup/backend-services.md @@ -319,7 +319,7 @@ async function processJob(job: Job) { }); await saveResult(job.id, response?.data.content); - await session.destroy(); // Clean up after job completes + await session.disconnect(); // Clean up after job completes } ``` diff --git a/docs/guides/setup/scaling.md b/docs/guides/setup/scaling.md index fcdb716da..5b74dee55 100644 --- a/docs/guides/setup/scaling.md +++ b/docs/guides/setup/scaling.md @@ -413,7 +413,7 @@ class SessionManager { const [oldestId] = this.activeSessions.keys(); const session = this.activeSessions.get(oldestId)!; // Session state is persisted automatically — safe to destroy - await session.destroy(); + await session.disconnect(); this.activeSessions.delete(oldestId); } } @@ -457,7 +457,7 @@ app.post("/api/analyze", async (req, res) => { }); res.json({ result: response?.data.content }); } finally { - await session.destroy(); // Clean up immediately + await session.disconnect(); // Clean up immediately } }); ``` diff --git a/docs/mcp/overview.md b/docs/mcp/overview.md index aa2fba668..5ad8b1df3 100644 --- a/docs/mcp/overview.md +++ b/docs/mcp/overview.md @@ -132,7 +132,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() // Use the session... } @@ -191,7 +191,7 @@ async function main() { console.log("Response:", result?.data?.content); - await session.destroy(); + await session.disconnect(); await client.stop(); } diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 40e153260..50eb93900 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -242,11 +242,11 @@ public async Task StopAsync() { try { - await session.DisposeAsync(); + await session.DisconnectAsync(); } catch (Exception ex) { - errors.Add(new Exception($"Failed to destroy session {session.SessionId}: {ex.Message}", ex)); + errors.Add(new Exception($"Failed to disconnect session {session.SessionId}: {ex.Message}", ex)); } } @@ -668,7 +668,7 @@ public async Task> ListModelsAsync(CancellationToken cancellatio /// A task that represents the asynchronous delete operation. /// Thrown when the session does not exist or deletion fails. /// - /// Unlike , which only releases in-memory + /// Unlike , which only releases in-memory /// resources and preserves session data for later resumption, this method is /// irreversible. The session cannot be resumed after deletion. /// diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 02bb276dc..b03a0de79 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -541,10 +541,11 @@ await InvokeRpcAsync( } /// - /// Closes this session and releases all in-memory resources (event handlers, + /// Disconnects this session and releases all in-memory resources (event handlers, /// tool handlers, permission handlers). /// - /// A task representing the dispose operation. + /// A that can be used to cancel the operation. + /// A task representing the disconnect operation. /// /// /// Session state on disk (conversation history, planning state, artifacts) is @@ -559,16 +560,14 @@ await InvokeRpcAsync( /// /// /// - /// // Using 'await using' for automatic disposal — session can still be resumed later - /// await using var session = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); + /// // Disconnect when done — session can still be resumed later + /// await session.DisconnectAsync(); /// - /// // Or manually dispose - /// var session2 = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); - /// // ... use the session ... - /// await session2.DisposeAsync(); + /// // Or use 'await using' for automatic disconnection + /// await using var session = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); /// /// - public async ValueTask DisposeAsync() + public async Task DisconnectAsync(CancellationToken cancellationToken = default) { if (Interlocked.Exchange(ref _isDisposed, 1) == 1) { @@ -578,7 +577,7 @@ public async ValueTask DisposeAsync() try { await InvokeRpcAsync( - "session.destroy", [new SessionDestroyRequest() { SessionId = SessionId }], CancellationToken.None); + "session.destroy", [new SessionDestroyRequest() { SessionId = SessionId }], cancellationToken); } catch (ObjectDisposedException) { @@ -592,7 +591,7 @@ await InvokeRpcAsync( _eventHandlers.Clear(); _toolHandlers.Clear(); - await _permissionHandlerLock.WaitAsync(); + await _permissionHandlerLock.WaitAsync(cancellationToken); try { _permissionHandler = null; @@ -603,6 +602,33 @@ await InvokeRpcAsync( } } + /// + /// Disposes the by disconnecting and releasing all resources. + /// + /// A task representing the dispose operation. + /// + /// + /// This method calls to perform cleanup. It is the + /// implementation of and enables the + /// await using pattern for automatic resource management. + /// + /// + /// + /// + /// // Using 'await using' for automatic disposal — session can still be resumed later + /// await using var session = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); + /// + /// // Or manually dispose + /// var session2 = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); + /// // ... use the session ... + /// await session2.DisposeAsync(); + /// + /// + public async ValueTask DisposeAsync() + { + await DisconnectAsync(); + } + private class OnDisposeCall(Action callback) : IDisposable { public void Dispose() => callback(); diff --git a/go/README.md b/go/README.md index b010fc211..1ba496aaf 100644 --- a/go/README.md +++ b/go/README.md @@ -51,7 +51,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() // Set up event handler done := make(chan bool) @@ -297,7 +297,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() done := make(chan bool) diff --git a/go/client.go b/go/client.go index f140127b6..144c90e4c 100644 --- a/go/client.go +++ b/go/client.go @@ -311,7 +311,7 @@ func (c *Client) Start(ctx context.Context) error { func (c *Client) Stop() error { var errs []error - // Destroy all active sessions + // Disconnect all active sessions c.sessionsMux.Lock() sessions := make([]*Session, 0, len(c.sessions)) for _, session := range c.sessions { @@ -320,8 +320,8 @@ func (c *Client) Stop() error { c.sessionsMux.Unlock() for _, session := range sessions { - if err := session.Destroy(); err != nil { - errs = append(errs, fmt.Errorf("failed to destroy session %s: %w", session.SessionID, err)) + if err := session.Disconnect(); err != nil { + errs = append(errs, fmt.Errorf("failed to disconnect session %s: %w", session.SessionID, err)) } } @@ -692,7 +692,7 @@ func (c *Client) ListSessions(ctx context.Context, filter *SessionListFilter) ([ // DeleteSession permanently deletes a session and all its data from disk, // including conversation history, planning state, and artifacts. // -// Unlike [Session.Destroy], which only releases in-memory resources and +// Unlike [Session.Disconnect], which only releases in-memory resources and // preserves session data for later resumption, DeleteSession is irreversible. // The session cannot be resumed after deletion. If the session is in the local // sessions map, it will be removed. diff --git a/go/samples/chat.go b/go/samples/chat.go index 4fc11ffda..f984f758a 100644 --- a/go/samples/chat.go +++ b/go/samples/chat.go @@ -30,7 +30,7 @@ func main() { if err != nil { panic(err) } - defer session.Destroy() + defer session.Disconnect() session.On(func(event copilot.SessionEvent) { var output string diff --git a/go/session.go b/go/session.go index 561e566d1..d913c20cf 100644 --- a/go/session.go +++ b/go/session.go @@ -34,7 +34,7 @@ type sessionHandler struct { // if err != nil { // log.Fatal(err) // } -// defer session.Destroy() +// defer session.Disconnect() // // // Subscribe to events // unsubscribe := session.On(func(event copilot.SessionEvent) { @@ -97,7 +97,7 @@ func newSession(sessionID string, client *jsonrpc2.Client, workspacePath string) // - options: The message options including the prompt and optional attachments. // // Returns the message ID of the response, which can be used to correlate events, -// or an error if the session has been destroyed or the connection fails. +// or an error if the session has been disconnected or the connection fails. // // Example: // @@ -483,7 +483,7 @@ func (s *Session) dispatchEvent(event SessionEvent) { // assistant responses, tool executions, and other session events in // chronological order. // -// Returns an error if the session has been destroyed or the connection fails. +// Returns an error if the session has been disconnected or the connection fails. // // Example: // @@ -511,7 +511,7 @@ func (s *Session) GetMessages(ctx context.Context) ([]SessionEvent, error) { return response.Events, nil } -// Destroy closes this session and releases all in-memory resources (event +// Disconnect closes this session and releases all in-memory resources (event // handlers, tool handlers, permission handlers). // // Session state on disk (conversation history, planning state, artifacts) is @@ -526,13 +526,13 @@ func (s *Session) GetMessages(ctx context.Context) ([]SessionEvent, error) { // Example: // // // Clean up when done — session can still be resumed later -// if err := session.Destroy(); err != nil { -// log.Printf("Failed to destroy session: %v", err) +// if err := session.Disconnect(); err != nil { +// log.Printf("Failed to disconnect session: %v", err) // } -func (s *Session) Destroy() error { +func (s *Session) Disconnect() error { _, err := s.client.Request("session.destroy", sessionDestroyRequest{SessionID: s.SessionID}) if err != nil { - return fmt.Errorf("failed to destroy session: %w", err) + return fmt.Errorf("failed to disconnect session: %w", err) } // Clear handlers @@ -551,12 +551,20 @@ func (s *Session) Destroy() error { return nil } +// Deprecated: Use [Session.Disconnect] instead. Destroy will be removed in a future release. +// +// Destroy closes this session and releases all in-memory resources. +// Session data on disk is preserved for later resumption. +func (s *Session) Destroy() error { + return s.Disconnect() +} + // Abort aborts the currently processing message in this session. // // Use this to cancel a long-running request. The session remains valid // and can continue to be used for new messages. // -// Returns an error if the session has been destroyed or the connection fails. +// Returns an error if the session has been disconnected or the connection fails. // // Example: // diff --git a/nodejs/README.md b/nodejs/README.md index 31558b8ab..64f39674f 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -52,7 +52,7 @@ await session.send({ prompt: "What is 2+2?" }); await done; // Clean up -await session.destroy(); +await session.disconnect(); await client.stop(); ``` diff --git a/nodejs/examples/basic-example.ts b/nodejs/examples/basic-example.ts index b0b993138..c20a85af0 100644 --- a/nodejs/examples/basic-example.ts +++ b/nodejs/examples/basic-example.ts @@ -41,6 +41,6 @@ const result2 = await session.sendAndWait({ prompt: "Use lookup_fact to tell me console.log("📝 Response:", result2?.data.content); // Clean up -await session.destroy(); +await session.disconnect(); await client.stop(); console.log("✅ Done!"); diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index c8c81dc74..21d772514 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -102,7 +102,7 @@ function toJsonSchema(parameters: Tool["parameters"]): Record | * await session.send({ prompt: "Hello!" }); * * // Clean up - * await session.destroy(); + * await session.disconnect(); * await client.stop(); * ``` */ @@ -329,7 +329,7 @@ export class CopilotClient { async stop(): Promise { const errors: Error[] = []; - // Destroy all active sessions with retry logic + // Disconnect all active sessions with retry logic for (const session of this.sessions.values()) { const sessionId = session.sessionId; let lastError: Error | null = null; @@ -337,7 +337,7 @@ export class CopilotClient { // Try up to 3 times with exponential backoff for (let attempt = 1; attempt <= 3; attempt++) { try { - await session.destroy(); + await session.disconnect(); lastError = null; break; // Success } catch (error) { @@ -354,7 +354,7 @@ export class CopilotClient { if (lastError) { errors.push( new Error( - `Failed to destroy session ${sessionId} after 3 attempts: ${lastError.message}` + `Failed to disconnect session ${sessionId} after 3 attempts: ${lastError.message}` ) ); } @@ -830,7 +830,7 @@ export class CopilotClient { * Permanently deletes a session and all its data from disk, including * conversation history, planning state, and artifacts. * - * Unlike {@link CopilotSession.destroy}, which only releases in-memory + * Unlike {@link CopilotSession.disconnect}, which only releases in-memory * resources and preserves session data for later resumption, this method * is irreversible. The session cannot be resumed after deletion. * diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index 6e32ee5f9..49b575b02 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -52,7 +52,7 @@ export type AssistantMessageEvent = Extract { + async disconnect(): Promise { await this.connection.sendRequest("session.destroy", { sessionId: this.sessionId, }); @@ -529,6 +529,24 @@ export class CopilotSession { this.permissionHandler = undefined; } + /** + * @deprecated Use {@link disconnect} instead. This method will be removed in a future release. + * + * Disconnects this session and releases all in-memory resources. + * Session data on disk is preserved for later resumption. + * + * @returns A promise that resolves when the session is disconnected + * @throws Error if the connection fails + */ + async destroy(): Promise { + return this.disconnect(); + } + + /** Enables `await using session = ...` syntax for automatic cleanup. */ + async [Symbol.asyncDispose](): Promise { + return this.disconnect(); + } + /** * Aborts the currently processing message in this session. * @@ -536,7 +554,7 @@ export class CopilotSession { * and can continue to be used for new messages. * * @returns A promise that resolves when the abort request is acknowledged - * @throws Error if the session has been destroyed or the connection fails + * @throws Error if the session has been disconnected or the connection fails * * @example * ```typescript diff --git a/python/README.md b/python/README.md index 3a1c4c73c..a62bfd8bd 100644 --- a/python/README.md +++ b/python/README.md @@ -51,7 +51,7 @@ async def main(): await done.wait() # Clean up - await session.destroy() + await session.disconnect() await client.stop() asyncio.run(main()) @@ -90,7 +90,7 @@ await session.send({"prompt": "Hello!"}) # ... wait for events ... -await session.destroy() +await session.disconnect() await client.stop() ``` @@ -277,7 +277,7 @@ async def main(): await session.send({"prompt": "Tell me a short story"}) await done.wait() # Wait for streaming to complete - await session.destroy() + await session.disconnect() await client.stop() asyncio.run(main()) diff --git a/python/copilot/client.py b/python/copilot/client.py index 2d50c73e5..32e05bc58 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -100,7 +100,7 @@ class CopilotClient: >>> await session.send({"prompt": "Hello!"}) >>> >>> # Clean up - >>> await session.destroy() + >>> await session.disconnect() >>> await client.stop() >>> # Or connect to an existing server @@ -348,10 +348,10 @@ async def stop(self) -> list["StopError"]: for session in sessions_to_destroy: try: - await session.destroy() + await session.disconnect() except Exception as e: errors.append( - StopError(message=f"Failed to destroy session {session.session_id}: {e}") + StopError(message=f"Failed to disconnect session {session.session_id}: {e}") ) # Close client @@ -935,7 +935,7 @@ async def delete_session(self, session_id: str) -> None: Permanently delete a session and all its data from disk, including conversation history, planning state, and artifacts. - Unlike :meth:`CopilotSession.destroy`, which only releases in-memory + Unlike :meth:`CopilotSession.disconnect`, which only releases in-memory resources and preserves session data for later resumption, this method is irreversible. The session cannot be resumed after deletion. diff --git a/python/copilot/session.py b/python/copilot/session.py index 72c085304..870511105 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -118,7 +118,7 @@ async def send(self, options: MessageOptions) -> str: The message ID of the response, which can be used to correlate events. Raises: - Exception: If the session has been destroyed or the connection fails. + Exception: If the session has been disconnected or the connection fails. Example: >>> message_id = await session.send({ @@ -159,7 +159,7 @@ async def send_and_wait( Raises: TimeoutError: If the timeout is reached before session becomes idle. - Exception: If the session has been destroyed or the connection fails. + Exception: If the session has been disconnected or the connection fails. Example: >>> response = await session.send_and_wait({"prompt": "What is 2+2?"}) @@ -461,7 +461,7 @@ async def get_messages(self) -> list[SessionEvent]: A list of all session events in chronological order. Raises: - Exception: If the session has been destroyed or the connection fails. + Exception: If the session has been disconnected or the connection fails. Example: >>> events = await session.get_messages() @@ -474,9 +474,9 @@ async def get_messages(self) -> list[SessionEvent]: events_dicts = response["events"] return [session_event_from_dict(event_dict) for event_dict in events_dicts] - async def destroy(self) -> None: + async def disconnect(self) -> None: """ - Close this session and release all in-memory resources (event handlers, + Disconnect this session and release all in-memory resources (event handlers, tool handlers, permission handlers). Session state on disk (conversation history, planning state, artifacts) @@ -492,7 +492,7 @@ async def destroy(self) -> None: Example: >>> # Clean up when done — session can still be resumed later - >>> await session.destroy() + >>> await session.disconnect() """ await self._client.request("session.destroy", {"sessionId": self.session_id}) with self._event_handlers_lock: @@ -502,6 +502,34 @@ async def destroy(self) -> None: with self._permission_handler_lock: self._permission_handler = None + async def destroy(self) -> None: + """ + .. deprecated:: + Use :meth:`disconnect` instead. This method will be removed in a future release. + + Disconnect this session and release all in-memory resources. + Session data on disk is preserved for later resumption. + + Raises: + Exception: If the connection fails. + """ + import warnings + + warnings.warn( + "destroy() is deprecated, use disconnect() instead", + DeprecationWarning, + stacklevel=2, + ) + await self.disconnect() + + async def __aenter__(self) -> "CopilotSession": + """Enable use as an async context manager.""" + return self + + async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: + """Disconnect the session when exiting the context manager.""" + await self.disconnect() + async def abort(self) -> None: """ Abort the currently processing message in this session. @@ -510,7 +538,7 @@ async def abort(self) -> None: and can continue to be used for new messages. Raises: - Exception: If the session has been destroyed or the connection fails. + Exception: If the session has been disconnected or the connection fails. Example: >>> import asyncio From b760022909ec5cca0c265c94beee41bbdc51a17e Mon Sep 17 00:00:00 2001 From: Patrick Nikoletich Date: Wed, 4 Mar 2026 19:30:10 -0800 Subject: [PATCH 3/5] chore: update all tests, scenarios, and docs to use disconnect() Migrate all test scenarios, e2e tests, READMEs, and documentation references from destroy()/Destroy() to disconnect()/Disconnect(). - 90 test scenario files across Go/Python/TypeScript/C# - 15 Node.js e2e test files - 8 Python e2e test files - 3 Go e2e test files - 1 .NET test file - READMEs and compatibility docs updated with new API reference - Agent docs updated with new method names - Reconnect scenario log messages updated to 'disconnected' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/agents/docs-maintenance.agent.md | 6 +++--- docs/compatibility.md | 3 ++- docs/guides/setup/scaling.md | 2 +- dotnet/test/SessionTests.cs | 2 +- go/README.md | 3 ++- go/internal/e2e/mcp_and_agents_test.go | 20 +++++++++---------- go/internal/e2e/session_test.go | 8 ++++---- go/internal/e2e/skills_test.go | 6 +++--- nodejs/README.md | 8 ++++++-- nodejs/test/e2e/agent_and_compact_rpc.test.ts | 12 +++++------ nodejs/test/e2e/ask_user.test.ts | 6 +++--- nodejs/test/e2e/client.test.ts | 2 +- nodejs/test/e2e/client_lifecycle.test.ts | 4 ++-- nodejs/test/e2e/error_resilience.test.ts | 12 +++++------ nodejs/test/e2e/event_fidelity.test.ts | 8 ++++---- nodejs/test/e2e/hooks.test.ts | 8 ++++---- nodejs/test/e2e/hooks_extended.test.ts | 10 +++++----- nodejs/test/e2e/mcp_and_agents.test.ts | 20 +++++++++---------- nodejs/test/e2e/permissions.test.ts | 18 ++++++++--------- nodejs/test/e2e/session.test.ts | 8 ++++---- nodejs/test/e2e/session_config.test.ts | 8 ++++---- nodejs/test/e2e/session_lifecycle.test.ts | 12 +++++------ nodejs/test/e2e/skills.test.ts | 6 +++--- nodejs/test/e2e/streaming_fidelity.test.ts | 4 ++-- nodejs/test/e2e/tool_results.test.ts | 6 +++--- python/e2e/test_agent_and_compact_rpc.py | 12 +++++------ python/e2e/test_ask_user.py | 6 +++--- python/e2e/test_client.py | 2 +- python/e2e/test_hooks.py | 8 ++++---- python/e2e/test_mcp_and_agents.py | 12 +++++------ python/e2e/test_permissions.py | 18 ++++++++--------- python/e2e/test_rpc.py | 6 +++--- python/e2e/test_session.py | 8 ++++---- python/e2e/test_skills.py | 6 +++--- test/scenarios/auth/byok-anthropic/go/main.go | 2 +- .../auth/byok-anthropic/python/main.py | 2 +- .../byok-anthropic/typescript/src/index.ts | 2 +- test/scenarios/auth/byok-azure/go/main.go | 2 +- test/scenarios/auth/byok-azure/python/main.py | 2 +- .../auth/byok-azure/typescript/src/index.ts | 2 +- test/scenarios/auth/byok-ollama/go/main.go | 2 +- .../scenarios/auth/byok-ollama/python/main.py | 2 +- .../auth/byok-ollama/typescript/src/index.ts | 2 +- test/scenarios/auth/byok-openai/go/main.go | 2 +- .../scenarios/auth/byok-openai/python/main.py | 2 +- .../auth/byok-openai/typescript/src/index.ts | 2 +- test/scenarios/auth/gh-app/go/main.go | 2 +- test/scenarios/auth/gh-app/python/main.py | 2 +- .../auth/gh-app/typescript/src/index.ts | 2 +- .../bundling/app-backend-to-server/go/main.go | 2 +- .../app-backend-to-server/python/main.py | 2 +- .../typescript/src/index.ts | 2 +- .../bundling/app-direct-server/go/main.go | 2 +- .../bundling/app-direct-server/python/main.py | 2 +- .../app-direct-server/typescript/src/index.ts | 2 +- .../bundling/container-proxy/go/main.go | 2 +- .../bundling/container-proxy/python/main.py | 2 +- .../container-proxy/typescript/src/index.ts | 2 +- .../bundling/fully-bundled/go/main.go | 2 +- .../bundling/fully-bundled/python/main.py | 2 +- .../fully-bundled/typescript/src/index.ts | 2 +- test/scenarios/callbacks/hooks/go/main.go | 2 +- test/scenarios/callbacks/hooks/python/main.py | 2 +- .../callbacks/hooks/typescript/src/index.ts | 2 +- .../callbacks/permissions/go/main.go | 2 +- .../callbacks/permissions/python/main.py | 2 +- .../permissions/typescript/src/index.ts | 2 +- .../scenarios/callbacks/user-input/go/main.go | 2 +- .../callbacks/user-input/python/main.py | 2 +- .../user-input/typescript/src/index.ts | 2 +- test/scenarios/modes/default/go/main.go | 2 +- test/scenarios/modes/default/python/main.py | 2 +- .../modes/default/typescript/src/index.ts | 2 +- test/scenarios/modes/minimal/go/main.go | 2 +- test/scenarios/modes/minimal/python/main.py | 2 +- .../modes/minimal/typescript/src/index.ts | 2 +- test/scenarios/prompts/attachments/go/main.go | 2 +- .../prompts/attachments/python/main.py | 2 +- .../attachments/typescript/src/index.ts | 2 +- .../prompts/reasoning-effort/go/main.go | 2 +- .../prompts/reasoning-effort/python/main.py | 2 +- .../reasoning-effort/typescript/src/index.ts | 2 +- .../prompts/system-message/go/main.go | 2 +- .../prompts/system-message/python/main.py | 2 +- .../system-message/typescript/src/index.ts | 2 +- .../sessions/concurrent-sessions/go/main.go | 4 ++-- .../concurrent-sessions/python/main.py | 2 +- .../typescript/src/index.ts | 2 +- .../sessions/infinite-sessions/go/main.go | 2 +- .../sessions/infinite-sessions/python/main.py | 2 +- .../infinite-sessions/typescript/src/index.ts | 2 +- .../sessions/multi-user-short-lived/README.md | 2 +- .../sessions/session-resume/go/main.go | 4 ++-- .../sessions/session-resume/python/main.py | 4 ++-- .../session-resume/typescript/src/index.ts | 4 ++-- test/scenarios/sessions/streaming/go/main.go | 2 +- .../sessions/streaming/python/main.py | 2 +- .../streaming/typescript/src/index.ts | 2 +- test/scenarios/tools/custom-agents/go/main.go | 2 +- .../tools/custom-agents/python/main.py | 2 +- .../custom-agents/typescript/src/index.ts | 2 +- test/scenarios/tools/mcp-servers/go/main.go | 2 +- .../tools/mcp-servers/python/main.py | 2 +- .../tools/mcp-servers/typescript/src/index.ts | 2 +- test/scenarios/tools/no-tools/go/main.go | 2 +- test/scenarios/tools/no-tools/python/main.py | 2 +- .../tools/no-tools/typescript/src/index.ts | 2 +- test/scenarios/tools/skills/go/main.go | 2 +- test/scenarios/tools/skills/python/main.py | 2 +- .../tools/skills/typescript/src/index.ts | 2 +- .../scenarios/tools/tool-filtering/go/main.go | 2 +- .../tools/tool-filtering/python/main.py | 2 +- .../tool-filtering/typescript/src/index.ts | 2 +- .../tools/virtual-filesystem/go/main.go | 2 +- .../tools/virtual-filesystem/python/main.py | 2 +- .../typescript/src/index.ts | 2 +- test/scenarios/transport/reconnect/README.md | 4 ++-- .../transport/reconnect/csharp/Program.cs | 4 ++-- test/scenarios/transport/reconnect/go/main.go | 8 ++++---- .../transport/reconnect/python/main.py | 8 ++++---- .../reconnect/typescript/src/index.ts | 8 ++++---- test/scenarios/transport/stdio/go/main.go | 2 +- test/scenarios/transport/stdio/python/main.py | 2 +- .../transport/stdio/typescript/src/index.ts | 2 +- test/scenarios/transport/tcp/go/main.go | 2 +- test/scenarios/transport/tcp/python/main.py | 2 +- .../transport/tcp/typescript/src/index.ts | 2 +- 127 files changed, 251 insertions(+), 245 deletions(-) diff --git a/.github/agents/docs-maintenance.agent.md b/.github/agents/docs-maintenance.agent.md index 9b605c265..9b97fecf4 100644 --- a/.github/agents/docs-maintenance.agent.md +++ b/.github/agents/docs-maintenance.agent.md @@ -344,7 +344,7 @@ cat nodejs/src/types.ts | grep -A 10 "export interface ExportSessionOptions" **Must match:** - `CopilotClient` constructor options: `cliPath`, `cliUrl`, `useStdio`, `port`, `logLevel`, `autoStart`, `autoRestart`, `env`, `githubToken`, `useLoggedInUser` - `createSession()` config: `model`, `tools`, `hooks`, `systemMessage`, `mcpServers`, `availableTools`, `excludedTools`, `streaming`, `reasoningEffort`, `provider`, `infiniteSessions`, `customAgents`, `workingDirectory` -- `CopilotSession` methods: `send()`, `sendAndWait()`, `getMessages()`, `destroy()`, `abort()`, `on()`, `once()`, `off()` +- `CopilotSession` methods: `send()`, `sendAndWait()`, `getMessages()`, `disconnect()`, `abort()`, `on()`, `once()`, `off()` - Hook names: `onPreToolUse`, `onPostToolUse`, `onUserPromptSubmitted`, `onSessionStart`, `onSessionEnd`, `onErrorOccurred` #### Python Validation @@ -362,7 +362,7 @@ cat python/copilot/types.py | grep -A 15 "class SessionHooks" **Must match (snake_case):** - `CopilotClient` options: `cli_path`, `cli_url`, `use_stdio`, `port`, `log_level`, `auto_start`, `auto_restart`, `env`, `github_token`, `use_logged_in_user` - `create_session()` config keys: `model`, `tools`, `hooks`, `system_message`, `mcp_servers`, `available_tools`, `excluded_tools`, `streaming`, `reasoning_effort`, `provider`, `infinite_sessions`, `custom_agents`, `working_directory` -- `CopilotSession` methods: `send()`, `send_and_wait()`, `get_messages()`, `destroy()`, `abort()`, `export_session()` +- `CopilotSession` methods: `send()`, `send_and_wait()`, `get_messages()`, `disconnect()`, `abort()`, `export_session()` - Hook names: `on_pre_tool_use`, `on_post_tool_use`, `on_user_prompt_submitted`, `on_session_start`, `on_session_end`, `on_error_occurred` #### Go Validation @@ -380,7 +380,7 @@ cat go/types.go | grep -A 15 "type SessionHooks struct" **Must match (PascalCase for exported):** - `ClientOptions` fields: `CLIPath`, `CLIUrl`, `UseStdio`, `Port`, `LogLevel`, `AutoStart`, `AutoRestart`, `Env`, `GithubToken`, `UseLoggedInUser` - `SessionConfig` fields: `Model`, `Tools`, `Hooks`, `SystemMessage`, `MCPServers`, `AvailableTools`, `ExcludedTools`, `Streaming`, `ReasoningEffort`, `Provider`, `InfiniteSessions`, `CustomAgents`, `WorkingDirectory` -- `Session` methods: `Send()`, `SendAndWait()`, `GetMessages()`, `Destroy()`, `Abort()`, `ExportSession()` +- `Session` methods: `Send()`, `SendAndWait()`, `GetMessages()`, `Disconnect()`, `Abort()`, `ExportSession()` - Hook fields: `OnPreToolUse`, `OnPostToolUse`, `OnUserPromptSubmitted`, `OnSessionStart`, `OnSessionEnd`, `OnErrorOccurred` #### .NET Validation diff --git a/docs/compatibility.md b/docs/compatibility.md index 268c077a3..bfd17915b 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -15,7 +15,8 @@ The Copilot SDK communicates with the CLI via JSON-RPC protocol. Features must b | **Session Management** | | | | Create session | `createSession()` | Full config support | | Resume session | `resumeSession()` | With infinite session workspaces | -| Destroy session | `destroy()` | Clean up resources | +| Disconnect session | `disconnect()` | Release in-memory resources | +| Destroy session *(deprecated)* | `destroy()` | Use `disconnect()` instead | | Delete session | `deleteSession()` | Remove from storage | | List sessions | `listSessions()` | All stored sessions | | Get last session | `getLastSessionId()` | For quick resume | diff --git a/docs/guides/setup/scaling.md b/docs/guides/setup/scaling.md index 5b74dee55..974276e5e 100644 --- a/docs/guides/setup/scaling.md +++ b/docs/guides/setup/scaling.md @@ -412,7 +412,7 @@ class SessionManager { private async evictOldestSession(): Promise { const [oldestId] = this.activeSessions.keys(); const session = this.activeSessions.get(oldestId)!; - // Session state is persisted automatically — safe to destroy + // Session state is persisted automatically — safe to disconnect await session.disconnect(); this.activeSessions.delete(oldestId); } diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index e4b13fff7..5391ee775 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -13,7 +13,7 @@ namespace GitHub.Copilot.SDK.Test; public class SessionTests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "session", output) { [Fact] - public async Task ShouldCreateAndDestroySessions() + public async Task ShouldCreateAndDisconnectSessions() { var session = await CreateSessionAsync(new SessionConfig { Model = "fake-test-model" }); diff --git a/go/README.md b/go/README.md index 1ba496aaf..0411a74f0 100644 --- a/go/README.md +++ b/go/README.md @@ -168,7 +168,8 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec - `On(handler SessionEventHandler) func()` - Subscribe to events (returns unsubscribe function) - `Abort(ctx context.Context) error` - Abort the currently processing message - `GetMessages(ctx context.Context) ([]SessionEvent, error)` - Get message history -- `Destroy() error` - Destroy the session +- `Disconnect() error` - Disconnect the session (releases in-memory resources, preserves disk state) +- `Destroy() error` - *(Deprecated)* Use `Disconnect()` instead ### Helper Functions diff --git a/go/internal/e2e/mcp_and_agents_test.go b/go/internal/e2e/mcp_and_agents_test.go index 0f49a05c0..079d26e9f 100644 --- a/go/internal/e2e/mcp_and_agents_test.go +++ b/go/internal/e2e/mcp_and_agents_test.go @@ -55,7 +55,7 @@ func TestMCPServers(t *testing.T) { t.Errorf("Expected message to contain '4', got: %v", message.Data.Content) } - session.Destroy() + session.Disconnect() }) t.Run("accept MCP server config on resume", func(t *testing.T) { @@ -104,7 +104,7 @@ func TestMCPServers(t *testing.T) { t.Errorf("Expected message to contain '6', got: %v", message.Data.Content) } - session2.Destroy() + session2.Disconnect() }) t.Run("should pass literal env values to MCP server subprocess", func(t *testing.T) { @@ -150,7 +150,7 @@ func TestMCPServers(t *testing.T) { t.Errorf("Expected message to contain 'hunter2', got: %v", message.Data.Content) } - session.Destroy() + session.Disconnect() }) t.Run("handle multiple MCP servers", func(t *testing.T) { @@ -183,7 +183,7 @@ func TestMCPServers(t *testing.T) { t.Error("Expected non-empty session ID") } - session.Destroy() + session.Disconnect() }) } @@ -235,7 +235,7 @@ func TestCustomAgents(t *testing.T) { t.Errorf("Expected message to contain '10', got: %v", message.Data.Content) } - session.Destroy() + session.Disconnect() }) t.Run("accept custom agent config on resume", func(t *testing.T) { @@ -284,7 +284,7 @@ func TestCustomAgents(t *testing.T) { t.Errorf("Expected message to contain '12', got: %v", message.Data.Content) } - session2.Destroy() + session2.Disconnect() }) t.Run("handle custom agent with tools", func(t *testing.T) { @@ -314,7 +314,7 @@ func TestCustomAgents(t *testing.T) { t.Error("Expected non-empty session ID") } - session.Destroy() + session.Disconnect() }) t.Run("handle custom agent with MCP servers", func(t *testing.T) { @@ -349,7 +349,7 @@ func TestCustomAgents(t *testing.T) { t.Error("Expected non-empty session ID") } - session.Destroy() + session.Disconnect() }) t.Run("handle multiple custom agents", func(t *testing.T) { @@ -386,7 +386,7 @@ func TestCustomAgents(t *testing.T) { t.Error("Expected non-empty session ID") } - session.Destroy() + session.Disconnect() }) } @@ -445,6 +445,6 @@ func TestCombinedConfiguration(t *testing.T) { t.Errorf("Expected message to contain '14', got: %v", message.Data.Content) } - session.Destroy() + session.Disconnect() }) } diff --git a/go/internal/e2e/session_test.go b/go/internal/e2e/session_test.go index f04307c2d..24388ad2c 100644 --- a/go/internal/e2e/session_test.go +++ b/go/internal/e2e/session_test.go @@ -15,7 +15,7 @@ func TestSession(t *testing.T) { client := ctx.NewClient() t.Cleanup(func() { client.ForceStop() }) - t.Run("should create and destroy sessions", func(t *testing.T) { + t.Run("should create and disconnect sessions", func(t *testing.T) { ctx.ConfigureForTest(t) session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll, Model: "fake-test-model"}) @@ -45,13 +45,13 @@ func TestSession(t *testing.T) { t.Errorf("Expected selectedModel to be 'fake-test-model', got %v", messages[0].Data.SelectedModel) } - if err := session.Destroy(); err != nil { - t.Fatalf("Failed to destroy session: %v", err) + if err := session.Disconnect(); err != nil { + t.Fatalf("Failed to disconnect session: %v", err) } _, err = session.GetMessages(t.Context()) if err == nil || !strings.Contains(err.Error(), "not found") { - t.Errorf("Expected GetMessages to fail with 'not found' after destroy, got %v", err) + t.Errorf("Expected GetMessages to fail with 'not found' after disconnect, got %v", err) } }) diff --git a/go/internal/e2e/skills_test.go b/go/internal/e2e/skills_test.go index 10cd50028..524280fd8 100644 --- a/go/internal/e2e/skills_test.go +++ b/go/internal/e2e/skills_test.go @@ -76,7 +76,7 @@ func TestSkills(t *testing.T) { t.Errorf("Expected message to contain skill marker '%s', got: %v", skillMarker, message.Data.Content) } - session.Destroy() + session.Disconnect() }) t.Run("should not apply skill when disabled via disabledSkills", func(t *testing.T) { @@ -105,7 +105,7 @@ func TestSkills(t *testing.T) { t.Errorf("Expected message to NOT contain skill marker '%s' when disabled, got: %v", skillMarker, *message.Data.Content) } - session.Destroy() + session.Disconnect() }) t.Run("should apply skill on session resume with skillDirectories", func(t *testing.T) { @@ -154,6 +154,6 @@ func TestSkills(t *testing.T) { t.Errorf("Expected message to contain skill marker '%s' after resume, got: %v", skillMarker, message2.Data.Content) } - session2.Destroy() + session2.Disconnect() }) } diff --git a/nodejs/README.md b/nodejs/README.md index 64f39674f..9c91aef2d 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -265,9 +265,13 @@ Abort the currently processing message in this session. Get all events/messages from this session. -##### `destroy(): Promise` +##### `disconnect(): Promise` -Destroy the session and free resources. +Disconnect the session and free resources. Session data on disk is preserved for later resumption. + +##### `destroy(): Promise` *(deprecated)* + +Deprecated — use `disconnect()` instead. --- diff --git a/nodejs/test/e2e/agent_and_compact_rpc.test.ts b/nodejs/test/e2e/agent_and_compact_rpc.test.ts index 47fc83229..336cd69b6 100644 --- a/nodejs/test/e2e/agent_and_compact_rpc.test.ts +++ b/nodejs/test/e2e/agent_and_compact_rpc.test.ts @@ -40,7 +40,7 @@ describe("Agent Selection RPC", async () => { expect(result.agents[0].description).toBe("A test agent"); expect(result.agents[1].name).toBe("another-agent"); - await session.destroy(); + await session.disconnect(); }); it("should return null when no agent is selected", async () => { @@ -61,7 +61,7 @@ describe("Agent Selection RPC", async () => { const result = await session.rpc.agent.getCurrent(); expect(result.agent).toBeNull(); - await session.destroy(); + await session.disconnect(); }); it("should select and get current agent", async () => { @@ -90,7 +90,7 @@ describe("Agent Selection RPC", async () => { expect(currentResult.agent).not.toBeNull(); expect(currentResult.agent!.name).toBe("test-agent"); - await session.destroy(); + await session.disconnect(); }); it("should deselect current agent", async () => { @@ -116,7 +116,7 @@ describe("Agent Selection RPC", async () => { const currentResult = await session.rpc.agent.getCurrent(); expect(currentResult.agent).toBeNull(); - await session.destroy(); + await session.disconnect(); }); it("should return empty list when no custom agents configured", async () => { @@ -125,7 +125,7 @@ describe("Agent Selection RPC", async () => { const result = await session.rpc.agent.list(); expect(result.agents).toEqual([]); - await session.destroy(); + await session.disconnect(); }); }); @@ -144,6 +144,6 @@ describe("Session Compact RPC", async () => { expect(typeof result.tokensRemoved).toBe("number"); expect(typeof result.messagesRemoved).toBe("number"); - await session.destroy(); + await session.disconnect(); }, 60000); }); diff --git a/nodejs/test/e2e/ask_user.test.ts b/nodejs/test/e2e/ask_user.test.ts index c58daa00c..deb0d788c 100644 --- a/nodejs/test/e2e/ask_user.test.ts +++ b/nodejs/test/e2e/ask_user.test.ts @@ -38,7 +38,7 @@ describe("User input (ask_user)", async () => { // The request should have a question expect(userInputRequests.some((req) => req.question && req.question.length > 0)).toBe(true); - await session.destroy(); + await session.disconnect(); }); it("should receive choices in user input request", async () => { @@ -69,7 +69,7 @@ describe("User input (ask_user)", async () => { ); expect(requestWithChoices).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); it("should handle freeform user input response", async () => { @@ -99,6 +99,6 @@ describe("User input (ask_user)", async () => { // (This is a soft check since the model may paraphrase) expect(response).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); }); diff --git a/nodejs/test/e2e/client.test.ts b/nodejs/test/e2e/client.test.ts index c7539fc0b..9d71ee726 100644 --- a/nodejs/test/e2e/client.test.ts +++ b/nodejs/test/e2e/client.test.ts @@ -62,7 +62,7 @@ describe("Client", () => { const errors = await client.stop(); expect(errors.length).toBeGreaterThan(0); - expect(errors[0].message).toContain("Failed to destroy session"); + expect(errors[0].message).toContain("Failed to disconnect session"); }); it("should forceStop without cleanup", async () => { diff --git a/nodejs/test/e2e/client_lifecycle.test.ts b/nodejs/test/e2e/client_lifecycle.test.ts index 1e6f451e3..beb654321 100644 --- a/nodejs/test/e2e/client_lifecycle.test.ts +++ b/nodejs/test/e2e/client_lifecycle.test.ts @@ -20,7 +20,7 @@ describe("Client Lifecycle", async () => { const lastSessionId = await client.getLastSessionId(); expect(lastSessionId).toBe(session.sessionId); - await session.destroy(); + await session.disconnect(); }); it("should return undefined for getLastSessionId with no sessions", async () => { @@ -49,7 +49,7 @@ describe("Client Lifecycle", async () => { expect(sessionEvents.length).toBeGreaterThan(0); } - await session.destroy(); + await session.disconnect(); } finally { unsubscribe(); } diff --git a/nodejs/test/e2e/error_resilience.test.ts b/nodejs/test/e2e/error_resilience.test.ts index bf908560d..183ea1188 100644 --- a/nodejs/test/e2e/error_resilience.test.ts +++ b/nodejs/test/e2e/error_resilience.test.ts @@ -9,16 +9,16 @@ import { createSdkTestContext } from "./harness/sdkTestContext"; describe("Error Resilience", async () => { const { copilotClient: client } = await createSdkTestContext(); - it("should throw when sending to destroyed session", async () => { + it("should throw when sending to disconnected session", async () => { const session = await client.createSession({ onPermissionRequest: approveAll }); - await session.destroy(); + await session.disconnect(); await expect(session.sendAndWait({ prompt: "Hello" })).rejects.toThrow(); }); - it("should throw when getting messages from destroyed session", async () => { + it("should throw when getting messages from disconnected session", async () => { const session = await client.createSession({ onPermissionRequest: approveAll }); - await session.destroy(); + await session.disconnect(); await expect(session.getMessages()).rejects.toThrow(); }); @@ -31,8 +31,8 @@ describe("Error Resilience", async () => { // Second abort should not throw await session.abort(); - // Session should still be destroyable - await session.destroy(); + // Session should still be disconnectable + await session.disconnect(); }); it("should throw when resuming non-existent session", async () => { diff --git a/nodejs/test/e2e/event_fidelity.test.ts b/nodejs/test/e2e/event_fidelity.test.ts index a9e9b77aa..7cd65b6fc 100644 --- a/nodejs/test/e2e/event_fidelity.test.ts +++ b/nodejs/test/e2e/event_fidelity.test.ts @@ -39,7 +39,7 @@ describe("Event Fidelity", async () => { const idleIdx = types.lastIndexOf("session.idle"); expect(idleIdx).toBe(types.length - 1); - await session.destroy(); + await session.disconnect(); }); it("should include valid fields on all events", async () => { @@ -74,7 +74,7 @@ describe("Event Fidelity", async () => { expect(assistantEvent?.data.messageId).toBeDefined(); expect(assistantEvent?.data.content).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); it("should emit tool execution events with correct fields", async () => { @@ -106,7 +106,7 @@ describe("Event Fidelity", async () => { const firstComplete = toolCompletes[0]!; expect(firstComplete.data.toolCallId).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); it("should emit assistant.message with messageId", async () => { @@ -129,6 +129,6 @@ describe("Event Fidelity", async () => { expect(typeof msg.data.messageId).toBe("string"); expect(msg.data.content).toContain("pong"); - await session.destroy(); + await session.disconnect(); }); }); diff --git a/nodejs/test/e2e/hooks.test.ts b/nodejs/test/e2e/hooks.test.ts index b7d8d4dcd..9743d91f3 100644 --- a/nodejs/test/e2e/hooks.test.ts +++ b/nodejs/test/e2e/hooks.test.ts @@ -45,7 +45,7 @@ describe("Session hooks", async () => { // Should have received the tool name expect(preToolUseInputs.some((input) => input.toolName)).toBe(true); - await session.destroy(); + await session.disconnect(); }); it("should invoke postToolUse hook after model runs a tool", async () => { @@ -76,7 +76,7 @@ describe("Session hooks", async () => { expect(postToolUseInputs.some((input) => input.toolName)).toBe(true); expect(postToolUseInputs.some((input) => input.toolResult !== undefined)).toBe(true); - await session.destroy(); + await session.disconnect(); }); it("should invoke both preToolUse and postToolUse hooks for a single tool call", async () => { @@ -113,7 +113,7 @@ describe("Session hooks", async () => { const commonTool = preToolNames.find((name) => postToolNames.includes(name)); expect(commonTool).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); it("should deny tool execution when preToolUse returns deny", async () => { @@ -145,6 +145,6 @@ describe("Session hooks", async () => { // At minimum, we verify the hook was invoked expect(response).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); }); diff --git a/nodejs/test/e2e/hooks_extended.test.ts b/nodejs/test/e2e/hooks_extended.test.ts index b97356635..9b12c4418 100644 --- a/nodejs/test/e2e/hooks_extended.test.ts +++ b/nodejs/test/e2e/hooks_extended.test.ts @@ -37,7 +37,7 @@ describe("Extended session hooks", async () => { expect(sessionStartInputs[0].timestamp).toBeGreaterThan(0); expect(sessionStartInputs[0].cwd).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); it("should invoke onUserPromptSubmitted hook when sending a message", async () => { @@ -62,10 +62,10 @@ describe("Extended session hooks", async () => { expect(userPromptInputs[0].timestamp).toBeGreaterThan(0); expect(userPromptInputs[0].cwd).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); - it("should invoke onSessionEnd hook when session is destroyed", async () => { + it("should invoke onSessionEnd hook when session is disconnected", async () => { const sessionEndInputs: SessionEndHookInput[] = []; const session = await client.createSession({ @@ -82,7 +82,7 @@ describe("Extended session hooks", async () => { prompt: "Say hi", }); - await session.destroy(); + await session.disconnect(); // Wait briefly for async hook await new Promise((resolve) => setTimeout(resolve, 100)); @@ -120,6 +120,6 @@ describe("Extended session hooks", async () => { // If the hook did fire, the assertions inside it would have run. expect(session.sessionId).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); }); diff --git a/nodejs/test/e2e/mcp_and_agents.test.ts b/nodejs/test/e2e/mcp_and_agents.test.ts index cc626e325..28ebf28b5 100644 --- a/nodejs/test/e2e/mcp_and_agents.test.ts +++ b/nodejs/test/e2e/mcp_and_agents.test.ts @@ -40,7 +40,7 @@ describe("MCP Servers and Custom Agents", async () => { }); expect(message?.data.content).toContain("4"); - await session.destroy(); + await session.disconnect(); }); it("should accept MCP server configuration on session resume", async () => { @@ -71,7 +71,7 @@ describe("MCP Servers and Custom Agents", async () => { }); expect(message?.data.content).toContain("6"); - await session2.destroy(); + await session2.disconnect(); }); it("should handle multiple MCP servers", async () => { @@ -96,7 +96,7 @@ describe("MCP Servers and Custom Agents", async () => { }); expect(session.sessionId).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); it("should pass literal env values to MCP server subprocess", async () => { @@ -122,7 +122,7 @@ describe("MCP Servers and Custom Agents", async () => { }); expect(message?.data.content).toContain("hunter2"); - await session.destroy(); + await session.disconnect(); }); }); @@ -151,7 +151,7 @@ describe("MCP Servers and Custom Agents", async () => { }); expect(message?.data.content).toContain("10"); - await session.destroy(); + await session.disconnect(); }); it("should accept custom agent configuration on session resume", async () => { @@ -182,7 +182,7 @@ describe("MCP Servers and Custom Agents", async () => { }); expect(message?.data.content).toContain("12"); - await session2.destroy(); + await session2.disconnect(); }); it("should handle custom agent with tools configuration", async () => { @@ -203,7 +203,7 @@ describe("MCP Servers and Custom Agents", async () => { }); expect(session.sessionId).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); it("should handle custom agent with MCP servers", async () => { @@ -230,7 +230,7 @@ describe("MCP Servers and Custom Agents", async () => { }); expect(session.sessionId).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); it("should handle multiple custom agents", async () => { @@ -256,7 +256,7 @@ describe("MCP Servers and Custom Agents", async () => { }); expect(session.sessionId).toBeDefined(); - await session.destroy(); + await session.disconnect(); }); }); @@ -293,7 +293,7 @@ describe("MCP Servers and Custom Agents", async () => { }); expect(message?.data.content).toContain("14"); - await session.destroy(); + await session.disconnect(); }); }); }); diff --git a/nodejs/test/e2e/permissions.test.ts b/nodejs/test/e2e/permissions.test.ts index ea23bc071..2203e34a8 100644 --- a/nodejs/test/e2e/permissions.test.ts +++ b/nodejs/test/e2e/permissions.test.ts @@ -39,7 +39,7 @@ describe("Permission callbacks", async () => { const writeRequests = permissionRequests.filter((req) => req.kind === "write"); expect(writeRequests.length).toBeGreaterThan(0); - await session.destroy(); + await session.disconnect(); }); it("should deny permission when handler returns denied", async () => { @@ -61,7 +61,7 @@ describe("Permission callbacks", async () => { const content = await readFile(testFile, "utf-8"); expect(content).toBe(originalContent); - await session.destroy(); + await session.disconnect(); }); it("should deny tool operations when handler explicitly denies", async () => { @@ -86,7 +86,7 @@ describe("Permission callbacks", async () => { expect(permissionDenied).toBe(true); - await session.destroy(); + await session.disconnect(); }); it("should deny tool operations when handler explicitly denies after resume", async () => { @@ -114,7 +114,7 @@ describe("Permission callbacks", async () => { expect(permissionDenied).toBe(true); - await session2.destroy(); + await session2.disconnect(); }); it("should work with approve-all permission handler", async () => { @@ -125,7 +125,7 @@ describe("Permission callbacks", async () => { }); expect(message?.data.content).toContain("4"); - await session.destroy(); + await session.disconnect(); }); it("should handle async permission handler", async () => { @@ -148,7 +148,7 @@ describe("Permission callbacks", async () => { expect(permissionRequests.length).toBeGreaterThan(0); - await session.destroy(); + await session.disconnect(); }); it("should resume session with permission handler", async () => { @@ -174,7 +174,7 @@ describe("Permission callbacks", async () => { // Should have permission requests from resumed session expect(permissionRequests.length).toBeGreaterThan(0); - await session2.destroy(); + await session2.disconnect(); }); it("should handle permission handler errors gracefully", async () => { @@ -191,7 +191,7 @@ describe("Permission callbacks", async () => { // Should handle the error and deny permission expect(message?.data.content?.toLowerCase()).toMatch(/fail|cannot|unable|permission/); - await session.destroy(); + await session.disconnect(); }); it("should receive toolCallId in permission requests", async () => { @@ -214,6 +214,6 @@ describe("Permission callbacks", async () => { expect(receivedToolCallId).toBe(true); - await session.destroy(); + await session.disconnect(); }); }); diff --git a/nodejs/test/e2e/session.test.ts b/nodejs/test/e2e/session.test.ts index 9d067a8ef..d89d81921 100644 --- a/nodejs/test/e2e/session.test.ts +++ b/nodejs/test/e2e/session.test.ts @@ -8,7 +8,7 @@ import { getFinalAssistantMessage, getNextEventOfType } from "./harness/sdkTestH describe("Sessions", async () => { const { copilotClient: client, openAiEndpoint, homeDir, env } = await createSdkTestContext(); - it("should create and destroy sessions", async () => { + it("should create and disconnect sessions", async () => { const session = await client.createSession({ onPermissionRequest: approveAll, model: "fake-test-model", @@ -22,7 +22,7 @@ describe("Sessions", async () => { }, ]); - await session.destroy(); + await session.disconnect(); await expect(() => session.getMessages()).rejects.toThrow(/Session not found/); }); @@ -155,8 +155,8 @@ describe("Sessions", async () => { ]); } - // All can be destroyed - await Promise.all([s1.destroy(), s2.destroy(), s3.destroy()]); + // All can be disconnected + await Promise.all([s1.disconnect(), s2.disconnect(), s3.disconnect()]); for (const s of [s1, s2, s3]) { await expect(() => s.getMessages()).rejects.toThrow(/Session not found/); } diff --git a/nodejs/test/e2e/session_config.test.ts b/nodejs/test/e2e/session_config.test.ts index ceb1f43f9..2984c3c04 100644 --- a/nodejs/test/e2e/session_config.test.ts +++ b/nodejs/test/e2e/session_config.test.ts @@ -22,7 +22,7 @@ describe("Session Configuration", async () => { }); expect(assistantMessage?.data.content).toContain("subdirectory"); - await session.destroy(); + await session.disconnect(); }); it("should create session with custom provider config", async () => { @@ -37,9 +37,9 @@ describe("Session Configuration", async () => { expect(session.sessionId).toMatch(/^[a-f0-9-]+$/); try { - await session.destroy(); + await session.disconnect(); } catch { - // destroy may fail since the provider is fake + // disconnect may fail since the provider is fake } }); @@ -54,6 +54,6 @@ describe("Session Configuration", async () => { }); // Just verify send doesn't throw — attachment support varies by runtime - await session.destroy(); + await session.disconnect(); }); }); diff --git a/nodejs/test/e2e/session_lifecycle.test.ts b/nodejs/test/e2e/session_lifecycle.test.ts index f41255cf7..355f89980 100644 --- a/nodejs/test/e2e/session_lifecycle.test.ts +++ b/nodejs/test/e2e/session_lifecycle.test.ts @@ -26,8 +26,8 @@ describe("Session Lifecycle", async () => { expect(sessionIds).toContain(session1.sessionId); expect(sessionIds).toContain(session2.sessionId); - await session1.destroy(); - await session2.destroy(); + await session1.disconnect(); + await session2.disconnect(); }); it("should delete session permanently", async () => { @@ -44,7 +44,7 @@ describe("Session Lifecycle", async () => { const before = await client.listSessions(); expect(before.map((s) => s.sessionId)).toContain(sessionId); - await session.destroy(); + await session.disconnect(); await client.deleteSession(sessionId); // After delete, the session should not be in the list @@ -68,7 +68,7 @@ describe("Session Lifecycle", async () => { expect(types).toContain("user.message"); expect(types).toContain("assistant.message"); - await session.destroy(); + await session.disconnect(); }); it("should support multiple concurrent sessions", async () => { @@ -84,7 +84,7 @@ describe("Session Lifecycle", async () => { expect(msg1?.data.content).toContain("2"); expect(msg2?.data.content).toContain("6"); - await session1.destroy(); - await session2.destroy(); + await session1.disconnect(); + await session2.disconnect(); }); }); diff --git a/nodejs/test/e2e/skills.test.ts b/nodejs/test/e2e/skills.test.ts index 654f429aa..a2173648f 100644 --- a/nodejs/test/e2e/skills.test.ts +++ b/nodejs/test/e2e/skills.test.ts @@ -58,7 +58,7 @@ IMPORTANT: You MUST include the exact text "${SKILL_MARKER}" somewhere in EVERY expect(message?.data.content).toContain(SKILL_MARKER); - await session.destroy(); + await session.disconnect(); }); it("should not apply skill when disabled via disabledSkills", async () => { @@ -78,7 +78,7 @@ IMPORTANT: You MUST include the exact text "${SKILL_MARKER}" somewhere in EVERY expect(message?.data.content).not.toContain(SKILL_MARKER); - await session.destroy(); + await session.disconnect(); }); // Skipped because the underlying feature doesn't work correctly yet. @@ -118,7 +118,7 @@ IMPORTANT: You MUST include the exact text "${SKILL_MARKER}" somewhere in EVERY expect(message2?.data.content).toContain(SKILL_MARKER); - await session2.destroy(); + await session2.disconnect(); }); }); }); diff --git a/nodejs/test/e2e/streaming_fidelity.test.ts b/nodejs/test/e2e/streaming_fidelity.test.ts index a5a2ead26..04148ad8c 100644 --- a/nodejs/test/e2e/streaming_fidelity.test.ts +++ b/nodejs/test/e2e/streaming_fidelity.test.ts @@ -43,7 +43,7 @@ describe("Streaming Fidelity", async () => { const lastAssistantIdx = types.lastIndexOf("assistant.message"); expect(firstDeltaIdx).toBeLessThan(lastAssistantIdx); - await session.destroy(); + await session.disconnect(); }); it("should not produce deltas when streaming is disabled", async () => { @@ -69,6 +69,6 @@ describe("Streaming Fidelity", async () => { const assistantEvents = events.filter((e) => e.type === "assistant.message"); expect(assistantEvents.length).toBeGreaterThanOrEqual(1); - await session.destroy(); + await session.disconnect(); }); }); diff --git a/nodejs/test/e2e/tool_results.test.ts b/nodejs/test/e2e/tool_results.test.ts index 88ebdb9a0..66e715490 100644 --- a/nodejs/test/e2e/tool_results.test.ts +++ b/nodejs/test/e2e/tool_results.test.ts @@ -35,7 +35,7 @@ describe("Tool Results", async () => { const content = assistantMessage?.data.content ?? ""; expect(content).toMatch(/sunny|72/i); - await session.destroy(); + await session.disconnect(); }); it("should handle tool result with failure resultType", async () => { @@ -60,7 +60,7 @@ describe("Tool Results", async () => { const failureContent = assistantMessage?.data.content ?? ""; expect(failureContent).toMatch(/service is down/i); - await session.destroy(); + await session.disconnect(); }); it("should pass validated Zod parameters to tool handler", async () => { @@ -96,6 +96,6 @@ describe("Tool Results", async () => { expect(assistantMessage?.data.content).toContain("42"); - await session.destroy(); + await session.disconnect(); }); }); diff --git a/python/e2e/test_agent_and_compact_rpc.py b/python/e2e/test_agent_and_compact_rpc.py index a960c8426..cee6814f1 100644 --- a/python/e2e/test_agent_and_compact_rpc.py +++ b/python/e2e/test_agent_and_compact_rpc.py @@ -46,7 +46,7 @@ async def test_should_list_available_custom_agents(self): assert result.agents[0].description == "A test agent" assert result.agents[1].name == "another-agent" - await session.destroy() + await session.disconnect() await client.stop() finally: await client.force_stop() @@ -75,7 +75,7 @@ async def test_should_return_null_when_no_agent_is_selected(self): result = await session.rpc.agent.get_current() assert result.agent is None - await session.destroy() + await session.disconnect() await client.stop() finally: await client.force_stop() @@ -114,7 +114,7 @@ async def test_should_select_and_get_current_agent(self): assert current_result.agent is not None assert current_result.agent.name == "test-agent" - await session.destroy() + await session.disconnect() await client.stop() finally: await client.force_stop() @@ -148,7 +148,7 @@ async def test_should_deselect_current_agent(self): current_result = await session.rpc.agent.get_current() assert current_result.agent is None - await session.destroy() + await session.disconnect() await client.stop() finally: await client.force_stop() @@ -167,7 +167,7 @@ async def test_should_return_empty_list_when_no_custom_agents_configured(self): result = await session.rpc.agent.list() assert result.agents == [] - await session.destroy() + await session.disconnect() await client.stop() finally: await client.force_stop() @@ -190,4 +190,4 @@ async def test_should_compact_session_history_after_messages(self, ctx: E2ETestC assert isinstance(result.tokens_removed, (int, float)) assert isinstance(result.messages_removed, (int, float)) - await session.destroy() + await session.disconnect() diff --git a/python/e2e/test_ask_user.py b/python/e2e/test_ask_user.py index f409e460c..bddc062df 100644 --- a/python/e2e/test_ask_user.py +++ b/python/e2e/test_ask_user.py @@ -53,7 +53,7 @@ async def on_user_input_request(request, invocation): req.get("question") and len(req.get("question")) > 0 for req in user_input_requests ) - await session.destroy() + await session.disconnect() async def test_should_receive_choices_in_user_input_request(self, ctx: E2ETestContext): """Test that choices are received in user input request""" @@ -94,7 +94,7 @@ async def on_user_input_request(request, invocation): ) assert request_with_choices is not None - await session.destroy() + await session.disconnect() async def test_should_handle_freeform_user_input_response(self, ctx: E2ETestContext): """Test that freeform user input responses work""" @@ -132,4 +132,4 @@ async def on_user_input_request(request, invocation): # (This is a soft check since the model may paraphrase) assert response is not None - await session.destroy() + await session.disconnect() diff --git a/python/e2e/test_client.py b/python/e2e/test_client.py index cc5d31ac6..780b1103e 100644 --- a/python/e2e/test_client.py +++ b/python/e2e/test_client.py @@ -61,7 +61,7 @@ async def test_should_return_errors_on_failed_cleanup(self): errors = await client.stop() assert len(errors) > 0 - assert "Failed to destroy session" in errors[0].message + assert "Failed to disconnect session" in errors[0].message finally: await client.force_stop() diff --git a/python/e2e/test_hooks.py b/python/e2e/test_hooks.py index 8278fb33c..c886c6e27 100644 --- a/python/e2e/test_hooks.py +++ b/python/e2e/test_hooks.py @@ -43,7 +43,7 @@ async def on_pre_tool_use(input_data, invocation): # Should have received the tool name assert any(inp.get("toolName") for inp in pre_tool_use_inputs) - await session.destroy() + await session.disconnect() async def test_should_invoke_posttooluse_hook_after_model_runs_a_tool( self, ctx: E2ETestContext @@ -77,7 +77,7 @@ async def on_post_tool_use(input_data, invocation): assert any(inp.get("toolName") for inp in post_tool_use_inputs) assert any(inp.get("toolResult") is not None for inp in post_tool_use_inputs) - await session.destroy() + await session.disconnect() async def test_should_invoke_both_pretooluse_and_posttooluse_hooks_for_a_single_tool_call( self, ctx: E2ETestContext @@ -118,7 +118,7 @@ async def on_post_tool_use(input_data, invocation): common_tool = next((name for name in pre_tool_names if name in post_tool_names), None) assert common_tool is not None - await session.destroy() + await session.disconnect() async def test_should_deny_tool_execution_when_pretooluse_returns_deny( self, ctx: E2ETestContext @@ -153,4 +153,4 @@ async def on_pre_tool_use(input_data, invocation): # At minimum, we verify the hook was invoked assert response is not None - await session.destroy() + await session.disconnect() diff --git a/python/e2e/test_mcp_and_agents.py b/python/e2e/test_mcp_and_agents.py index b29a54827..fd99cc2c3 100644 --- a/python/e2e/test_mcp_and_agents.py +++ b/python/e2e/test_mcp_and_agents.py @@ -43,7 +43,7 @@ async def test_should_accept_mcp_server_configuration_on_session_create( assert message is not None assert "4" in message.data.content - await session.destroy() + await session.disconnect() async def test_should_accept_mcp_server_configuration_on_session_resume( self, ctx: E2ETestContext @@ -77,7 +77,7 @@ async def test_should_accept_mcp_server_configuration_on_session_resume( assert message is not None assert "6" in message.data.content - await session2.destroy() + await session2.disconnect() async def test_should_pass_literal_env_values_to_mcp_server_subprocess( self, ctx: E2ETestContext @@ -112,7 +112,7 @@ async def test_should_pass_literal_env_values_to_mcp_server_subprocess( assert message is not None assert "hunter2" in message.data.content - await session.destroy() + await session.disconnect() class TestCustomAgents: @@ -141,7 +141,7 @@ async def test_should_accept_custom_agent_configuration_on_session_create( assert message is not None assert "10" in message.data.content - await session.destroy() + await session.disconnect() async def test_should_accept_custom_agent_configuration_on_session_resume( self, ctx: E2ETestContext @@ -178,7 +178,7 @@ async def test_should_accept_custom_agent_configuration_on_session_resume( assert message is not None assert "12" in message.data.content - await session2.destroy() + await session2.disconnect() class TestCombinedConfiguration: @@ -216,4 +216,4 @@ async def test_should_accept_both_mcp_servers_and_custom_agents(self, ctx: E2ETe message = await get_final_assistant_message(session) assert "14" in message.data.content - await session.destroy() + await session.disconnect() diff --git a/python/e2e/test_permissions.py b/python/e2e/test_permissions.py index c116053ba..722ddc338 100644 --- a/python/e2e/test_permissions.py +++ b/python/e2e/test_permissions.py @@ -42,7 +42,7 @@ def on_permission_request( write_requests = [req for req in permission_requests if req.get("kind") == "write"] assert len(write_requests) > 0 - await session.destroy() + await session.disconnect() async def test_should_deny_permission_when_handler_returns_denied(self, ctx: E2ETestContext): """Test denying permissions""" @@ -66,7 +66,7 @@ def on_permission_request( content = read_file(ctx.work_dir, "protected.txt") assert content == original_content - await session.destroy() + await session.disconnect() async def test_should_deny_tool_operations_when_handler_explicitly_denies( self, ctx: E2ETestContext @@ -101,7 +101,7 @@ def on_event(event): assert len(denied_events) > 0 - await session.destroy() + await session.disconnect() async def test_should_deny_tool_operations_when_handler_explicitly_denies_after_resume( self, ctx: E2ETestContext @@ -141,7 +141,7 @@ def on_event(event): assert len(denied_events) > 0 - await session2.destroy() + await session2.disconnect() async def test_should_work_with_approve_all_permission_handler(self, ctx: E2ETestContext): """Test that sessions work with approve-all permission handler""" @@ -154,7 +154,7 @@ async def test_should_work_with_approve_all_permission_handler(self, ctx: E2ETes assert message is not None assert "4" in message.data.content - await session.destroy() + await session.disconnect() async def test_should_handle_async_permission_handler(self, ctx: E2ETestContext): """Test async permission handler""" @@ -174,7 +174,7 @@ async def on_permission_request( assert len(permission_requests) > 0 - await session.destroy() + await session.disconnect() async def test_should_resume_session_with_permission_handler(self, ctx: E2ETestContext): """Test resuming session with permission handler""" @@ -203,7 +203,7 @@ def on_permission_request( # Should have permission requests from resumed session assert len(permission_requests) > 0 - await session2.destroy() + await session2.disconnect() async def test_should_handle_permission_handler_errors_gracefully(self, ctx: E2ETestContext): """Test that permission handler errors are handled gracefully""" @@ -224,7 +224,7 @@ def on_permission_request( content_lower = message.data.content.lower() assert any(word in content_lower for word in ["fail", "cannot", "unable", "permission"]) - await session.destroy() + await session.disconnect() async def test_should_receive_toolcallid_in_permission_requests(self, ctx: E2ETestContext): """Test that toolCallId is included in permission requests""" @@ -246,4 +246,4 @@ def on_permission_request( assert received_tool_call_id - await session.destroy() + await session.disconnect() diff --git a/python/e2e/test_rpc.py b/python/e2e/test_rpc.py index 240cd3730..1b455d632 100644 --- a/python/e2e/test_rpc.py +++ b/python/e2e/test_rpc.py @@ -138,7 +138,7 @@ async def test_get_and_set_session_mode(self): ) assert interactive_result.mode == Mode.INTERACTIVE - await session.destroy() + await session.disconnect() await client.stop() finally: await client.force_stop() @@ -178,7 +178,7 @@ async def test_read_update_and_delete_plan(self): assert after_delete.exists is False assert after_delete.content is None - await session.destroy() + await session.disconnect() await client.stop() finally: await client.force_stop() @@ -228,7 +228,7 @@ async def test_create_list_and_read_workspace_files(self): assert "test.txt" in after_nested.files assert any("nested.txt" in f for f in after_nested.files) - await session.destroy() + await session.disconnect() await client.stop() finally: await client.force_stop() diff --git a/python/e2e/test_session.py b/python/e2e/test_session.py index 47cb1b5ae..7d39be52c 100644 --- a/python/e2e/test_session.py +++ b/python/e2e/test_session.py @@ -13,7 +13,7 @@ class TestSessions: - async def test_should_create_and_destroy_sessions(self, ctx: E2ETestContext): + async def test_should_create_and_disconnect_sessions(self, ctx: E2ETestContext): session = await ctx.client.create_session( {"model": "fake-test-model", "on_permission_request": PermissionHandler.approve_all} ) @@ -25,7 +25,7 @@ async def test_should_create_and_destroy_sessions(self, ctx: E2ETestContext): assert messages[0].data.session_id == session.session_id assert messages[0].data.selected_model == "fake-test-model" - await session.destroy() + await session.disconnect() with pytest.raises(Exception, match="Session not found"): await session.get_messages() @@ -148,8 +148,8 @@ async def test_should_handle_multiple_concurrent_sessions(self, ctx: E2ETestCont assert messages[0].type.value == "session.start" assert messages[0].data.session_id == s.session_id - # All can be destroyed - await asyncio.gather(s1.destroy(), s2.destroy(), s3.destroy()) + # All can be disconnected + await asyncio.gather(s1.disconnect(), s2.disconnect(), s3.disconnect()) for s in [s1, s2, s3]: with pytest.raises(Exception, match="Session not found"): await s.get_messages() diff --git a/python/e2e/test_skills.py b/python/e2e/test_skills.py index 10d32695c..166840e57 100644 --- a/python/e2e/test_skills.py +++ b/python/e2e/test_skills.py @@ -69,7 +69,7 @@ async def test_should_load_and_apply_skill_from_skilldirectories(self, ctx: E2ET assert message is not None assert SKILL_MARKER in message.data.content - await session.destroy() + await session.disconnect() async def test_should_not_apply_skill_when_disabled_via_disabledskills( self, ctx: E2ETestContext @@ -91,7 +91,7 @@ async def test_should_not_apply_skill_when_disabled_via_disabledskills( assert message is not None assert SKILL_MARKER not in message.data.content - await session.destroy() + await session.disconnect() @pytest.mark.skip( reason="See the big comment around the equivalent test in the Node SDK. " @@ -130,4 +130,4 @@ async def test_should_apply_skill_on_session_resume_with_skilldirectories( assert message2 is not None assert SKILL_MARKER in message2.data.content - await session2.destroy() + await session2.disconnect() diff --git a/test/scenarios/auth/byok-anthropic/go/main.go b/test/scenarios/auth/byok-anthropic/go/main.go index a42f90b8c..048d20f6b 100644 --- a/test/scenarios/auth/byok-anthropic/go/main.go +++ b/test/scenarios/auth/byok-anthropic/go/main.go @@ -49,7 +49,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/auth/byok-anthropic/python/main.py b/test/scenarios/auth/byok-anthropic/python/main.py index 7f5e5834c..e50a33c16 100644 --- a/test/scenarios/auth/byok-anthropic/python/main.py +++ b/test/scenarios/auth/byok-anthropic/python/main.py @@ -40,7 +40,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/auth/byok-anthropic/typescript/src/index.ts b/test/scenarios/auth/byok-anthropic/typescript/src/index.ts index bd5f30dd0..a7f460d8f 100644 --- a/test/scenarios/auth/byok-anthropic/typescript/src/index.ts +++ b/test/scenarios/auth/byok-anthropic/typescript/src/index.ts @@ -36,7 +36,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/auth/byok-azure/go/main.go b/test/scenarios/auth/byok-azure/go/main.go index 8d385076e..03f3b9dcf 100644 --- a/test/scenarios/auth/byok-azure/go/main.go +++ b/test/scenarios/auth/byok-azure/go/main.go @@ -53,7 +53,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/auth/byok-azure/python/main.py b/test/scenarios/auth/byok-azure/python/main.py index 5376cac28..89f371789 100644 --- a/test/scenarios/auth/byok-azure/python/main.py +++ b/test/scenarios/auth/byok-azure/python/main.py @@ -44,7 +44,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/auth/byok-azure/typescript/src/index.ts b/test/scenarios/auth/byok-azure/typescript/src/index.ts index 450742f86..397a0a187 100644 --- a/test/scenarios/auth/byok-azure/typescript/src/index.ts +++ b/test/scenarios/auth/byok-azure/typescript/src/index.ts @@ -40,7 +40,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/auth/byok-ollama/go/main.go b/test/scenarios/auth/byok-ollama/go/main.go index 191d2eab7..b8b34c5b7 100644 --- a/test/scenarios/auth/byok-ollama/go/main.go +++ b/test/scenarios/auth/byok-ollama/go/main.go @@ -45,7 +45,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/auth/byok-ollama/python/main.py b/test/scenarios/auth/byok-ollama/python/main.py index 0f9df7f54..b86c76ba3 100644 --- a/test/scenarios/auth/byok-ollama/python/main.py +++ b/test/scenarios/auth/byok-ollama/python/main.py @@ -38,7 +38,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/auth/byok-ollama/typescript/src/index.ts b/test/scenarios/auth/byok-ollama/typescript/src/index.ts index 3ba9da89d..936d118a8 100644 --- a/test/scenarios/auth/byok-ollama/typescript/src/index.ts +++ b/test/scenarios/auth/byok-ollama/typescript/src/index.ts @@ -31,7 +31,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/auth/byok-openai/go/main.go b/test/scenarios/auth/byok-openai/go/main.go index bd418ab71..fc05c71b4 100644 --- a/test/scenarios/auth/byok-openai/go/main.go +++ b/test/scenarios/auth/byok-openai/go/main.go @@ -44,7 +44,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/auth/byok-openai/python/main.py b/test/scenarios/auth/byok-openai/python/main.py index 651a92cd6..b501bb10e 100644 --- a/test/scenarios/auth/byok-openai/python/main.py +++ b/test/scenarios/auth/byok-openai/python/main.py @@ -35,7 +35,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/auth/byok-openai/typescript/src/index.ts b/test/scenarios/auth/byok-openai/typescript/src/index.ts index 1d2d0aaf8..41eda577a 100644 --- a/test/scenarios/auth/byok-openai/typescript/src/index.ts +++ b/test/scenarios/auth/byok-openai/typescript/src/index.ts @@ -32,7 +32,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/auth/gh-app/go/main.go b/test/scenarios/auth/gh-app/go/main.go index 4aaad3b4b..d84d030cd 100644 --- a/test/scenarios/auth/gh-app/go/main.go +++ b/test/scenarios/auth/gh-app/go/main.go @@ -177,7 +177,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/auth/gh-app/python/main.py b/test/scenarios/auth/gh-app/python/main.py index 4568c82b2..4886fe07a 100644 --- a/test/scenarios/auth/gh-app/python/main.py +++ b/test/scenarios/auth/gh-app/python/main.py @@ -88,7 +88,7 @@ async def main(): response = await session.send_and_wait({"prompt": "What is the capital of France?"}) if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/auth/gh-app/typescript/src/index.ts b/test/scenarios/auth/gh-app/typescript/src/index.ts index 1c9cabde3..a5b8f28e2 100644 --- a/test/scenarios/auth/gh-app/typescript/src/index.ts +++ b/test/scenarios/auth/gh-app/typescript/src/index.ts @@ -121,7 +121,7 @@ async function main() { }); if (response) console.log(response.data.content); - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/bundling/app-backend-to-server/go/main.go b/test/scenarios/bundling/app-backend-to-server/go/main.go index afc8858f5..df2be62b9 100644 --- a/test/scenarios/bundling/app-backend-to-server/go/main.go +++ b/test/scenarios/bundling/app-backend-to-server/go/main.go @@ -70,7 +70,7 @@ func chatHandler(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusInternalServerError, chatResponse{Error: err.Error()}) return } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: req.Prompt, diff --git a/test/scenarios/bundling/app-backend-to-server/python/main.py b/test/scenarios/bundling/app-backend-to-server/python/main.py index 218505f4a..29563149a 100644 --- a/test/scenarios/bundling/app-backend-to-server/python/main.py +++ b/test/scenarios/bundling/app-backend-to-server/python/main.py @@ -20,7 +20,7 @@ async def ask_copilot(prompt: str) -> str: response = await session.send_and_wait({"prompt": prompt}) - await session.destroy() + await session.disconnect() if response: return response.data.content diff --git a/test/scenarios/bundling/app-backend-to-server/typescript/src/index.ts b/test/scenarios/bundling/app-backend-to-server/typescript/src/index.ts index 3394c0d3a..7ab734d1a 100644 --- a/test/scenarios/bundling/app-backend-to-server/typescript/src/index.ts +++ b/test/scenarios/bundling/app-backend-to-server/typescript/src/index.ts @@ -21,7 +21,7 @@ app.post("/chat", async (req, res) => { const response = await session.sendAndWait({ prompt }); - await session.destroy(); + await session.disconnect(); if (response?.data.content) { res.json({ response: response.data.content }); diff --git a/test/scenarios/bundling/app-direct-server/go/main.go b/test/scenarios/bundling/app-direct-server/go/main.go index 9a0b1be4e..8be7dd605 100644 --- a/test/scenarios/bundling/app-direct-server/go/main.go +++ b/test/scenarios/bundling/app-direct-server/go/main.go @@ -31,7 +31,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/bundling/app-direct-server/python/main.py b/test/scenarios/bundling/app-direct-server/python/main.py index 05aaa9270..c407d4fea 100644 --- a/test/scenarios/bundling/app-direct-server/python/main.py +++ b/test/scenarios/bundling/app-direct-server/python/main.py @@ -18,7 +18,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/bundling/app-direct-server/typescript/src/index.ts b/test/scenarios/bundling/app-direct-server/typescript/src/index.ts index 139e47a86..29a19dd10 100644 --- a/test/scenarios/bundling/app-direct-server/typescript/src/index.ts +++ b/test/scenarios/bundling/app-direct-server/typescript/src/index.ts @@ -19,7 +19,7 @@ async function main() { process.exit(1); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/bundling/container-proxy/go/main.go b/test/scenarios/bundling/container-proxy/go/main.go index 9a0b1be4e..8be7dd605 100644 --- a/test/scenarios/bundling/container-proxy/go/main.go +++ b/test/scenarios/bundling/container-proxy/go/main.go @@ -31,7 +31,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/bundling/container-proxy/python/main.py b/test/scenarios/bundling/container-proxy/python/main.py index 05aaa9270..c407d4fea 100644 --- a/test/scenarios/bundling/container-proxy/python/main.py +++ b/test/scenarios/bundling/container-proxy/python/main.py @@ -18,7 +18,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/bundling/container-proxy/typescript/src/index.ts b/test/scenarios/bundling/container-proxy/typescript/src/index.ts index 139e47a86..29a19dd10 100644 --- a/test/scenarios/bundling/container-proxy/typescript/src/index.ts +++ b/test/scenarios/bundling/container-proxy/typescript/src/index.ts @@ -19,7 +19,7 @@ async function main() { process.exit(1); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/bundling/fully-bundled/go/main.go b/test/scenarios/bundling/fully-bundled/go/main.go index e548a08e7..b8902fd99 100644 --- a/test/scenarios/bundling/fully-bundled/go/main.go +++ b/test/scenarios/bundling/fully-bundled/go/main.go @@ -27,7 +27,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/bundling/fully-bundled/python/main.py b/test/scenarios/bundling/fully-bundled/python/main.py index 138bb5646..d1441361f 100644 --- a/test/scenarios/bundling/fully-bundled/python/main.py +++ b/test/scenarios/bundling/fully-bundled/python/main.py @@ -19,7 +19,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/bundling/fully-bundled/typescript/src/index.ts b/test/scenarios/bundling/fully-bundled/typescript/src/index.ts index 989a0b9a6..bee246f64 100644 --- a/test/scenarios/bundling/fully-bundled/typescript/src/index.ts +++ b/test/scenarios/bundling/fully-bundled/typescript/src/index.ts @@ -17,7 +17,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/callbacks/hooks/go/main.go b/test/scenarios/callbacks/hooks/go/main.go index c084c3a79..44e6e0240 100644 --- a/test/scenarios/callbacks/hooks/go/main.go +++ b/test/scenarios/callbacks/hooks/go/main.go @@ -67,7 +67,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "List the files in the current directory using the glob tool with pattern '*.md'.", diff --git a/test/scenarios/callbacks/hooks/python/main.py b/test/scenarios/callbacks/hooks/python/main.py index a00c18af7..8df61b9d3 100644 --- a/test/scenarios/callbacks/hooks/python/main.py +++ b/test/scenarios/callbacks/hooks/python/main.py @@ -70,7 +70,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() print("\n--- Hook execution log ---") for entry in hook_log: diff --git a/test/scenarios/callbacks/hooks/typescript/src/index.ts b/test/scenarios/callbacks/hooks/typescript/src/index.ts index 52708d8fd..2a5cde585 100644 --- a/test/scenarios/callbacks/hooks/typescript/src/index.ts +++ b/test/scenarios/callbacks/hooks/typescript/src/index.ts @@ -44,7 +44,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); console.log("\n--- Hook execution log ---"); for (const entry of hookLog) { diff --git a/test/scenarios/callbacks/permissions/go/main.go b/test/scenarios/callbacks/permissions/go/main.go index 1ac49c04d..f70bdfbfb 100644 --- a/test/scenarios/callbacks/permissions/go/main.go +++ b/test/scenarios/callbacks/permissions/go/main.go @@ -44,7 +44,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "List the files in the current directory using glob with pattern '*.md'.", diff --git a/test/scenarios/callbacks/permissions/python/main.py b/test/scenarios/callbacks/permissions/python/main.py index 2da5133fa..9674da917 100644 --- a/test/scenarios/callbacks/permissions/python/main.py +++ b/test/scenarios/callbacks/permissions/python/main.py @@ -39,7 +39,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() print("\n--- Permission request log ---") for entry in permission_log: diff --git a/test/scenarios/callbacks/permissions/typescript/src/index.ts b/test/scenarios/callbacks/permissions/typescript/src/index.ts index a7e452cc7..6a163bc27 100644 --- a/test/scenarios/callbacks/permissions/typescript/src/index.ts +++ b/test/scenarios/callbacks/permissions/typescript/src/index.ts @@ -31,7 +31,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); console.log("\n--- Permission request log ---"); for (const entry of permissionLog) { diff --git a/test/scenarios/callbacks/user-input/go/main.go b/test/scenarios/callbacks/user-input/go/main.go index 91d0c86ec..50eb65a23 100644 --- a/test/scenarios/callbacks/user-input/go/main.go +++ b/test/scenarios/callbacks/user-input/go/main.go @@ -46,7 +46,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "I want to learn about a city. Use the ask_user tool to ask me " + diff --git a/test/scenarios/callbacks/user-input/python/main.py b/test/scenarios/callbacks/user-input/python/main.py index fb36eda5c..dc8d9fa9b 100644 --- a/test/scenarios/callbacks/user-input/python/main.py +++ b/test/scenarios/callbacks/user-input/python/main.py @@ -47,7 +47,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() print("\n--- User input log ---") for entry in input_log: diff --git a/test/scenarios/callbacks/user-input/typescript/src/index.ts b/test/scenarios/callbacks/user-input/typescript/src/index.ts index 4791fcf10..5964ce6c1 100644 --- a/test/scenarios/callbacks/user-input/typescript/src/index.ts +++ b/test/scenarios/callbacks/user-input/typescript/src/index.ts @@ -29,7 +29,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); console.log("\n--- User input log ---"); for (const entry of inputLog) { diff --git a/test/scenarios/modes/default/go/main.go b/test/scenarios/modes/default/go/main.go index dfae25178..dd2b45d33 100644 --- a/test/scenarios/modes/default/go/main.go +++ b/test/scenarios/modes/default/go/main.go @@ -26,7 +26,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "Use the grep tool to search for the word 'SDK' in README.md and show the matching lines.", diff --git a/test/scenarios/modes/default/python/main.py b/test/scenarios/modes/default/python/main.py index 0abc6b709..dadc0e7be 100644 --- a/test/scenarios/modes/default/python/main.py +++ b/test/scenarios/modes/default/python/main.py @@ -20,7 +20,7 @@ async def main(): print("Default mode test complete") - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/modes/default/typescript/src/index.ts b/test/scenarios/modes/default/typescript/src/index.ts index e10cb6cbc..89aab3598 100644 --- a/test/scenarios/modes/default/typescript/src/index.ts +++ b/test/scenarios/modes/default/typescript/src/index.ts @@ -21,7 +21,7 @@ async function main() { console.log("Default mode test complete"); - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/modes/minimal/go/main.go b/test/scenarios/modes/minimal/go/main.go index c39c24f65..c3624b114 100644 --- a/test/scenarios/modes/minimal/go/main.go +++ b/test/scenarios/modes/minimal/go/main.go @@ -31,7 +31,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "Use the grep tool to search for 'SDK' in README.md.", diff --git a/test/scenarios/modes/minimal/python/main.py b/test/scenarios/modes/minimal/python/main.py index 74a98ba0e..0b243cafa 100644 --- a/test/scenarios/modes/minimal/python/main.py +++ b/test/scenarios/modes/minimal/python/main.py @@ -25,7 +25,7 @@ async def main(): print("Minimal mode test complete") - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/modes/minimal/typescript/src/index.ts b/test/scenarios/modes/minimal/typescript/src/index.ts index 091595bec..f20e476de 100644 --- a/test/scenarios/modes/minimal/typescript/src/index.ts +++ b/test/scenarios/modes/minimal/typescript/src/index.ts @@ -26,7 +26,7 @@ async function main() { console.log("Minimal mode test complete"); - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/prompts/attachments/go/main.go b/test/scenarios/prompts/attachments/go/main.go index 4b248bf95..95eb2b4d0 100644 --- a/test/scenarios/prompts/attachments/go/main.go +++ b/test/scenarios/prompts/attachments/go/main.go @@ -34,7 +34,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() exe, err := os.Executable() if err != nil { diff --git a/test/scenarios/prompts/attachments/python/main.py b/test/scenarios/prompts/attachments/python/main.py index acf9c7af1..c7e21e8b9 100644 --- a/test/scenarios/prompts/attachments/python/main.py +++ b/test/scenarios/prompts/attachments/python/main.py @@ -33,7 +33,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/prompts/attachments/typescript/src/index.ts b/test/scenarios/prompts/attachments/typescript/src/index.ts index 72e601ca2..100f7e17d 100644 --- a/test/scenarios/prompts/attachments/typescript/src/index.ts +++ b/test/scenarios/prompts/attachments/typescript/src/index.ts @@ -31,7 +31,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/prompts/reasoning-effort/go/main.go b/test/scenarios/prompts/reasoning-effort/go/main.go index 43c5eb74a..ccb4e5284 100644 --- a/test/scenarios/prompts/reasoning-effort/go/main.go +++ b/test/scenarios/prompts/reasoning-effort/go/main.go @@ -32,7 +32,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/prompts/reasoning-effort/python/main.py b/test/scenarios/prompts/reasoning-effort/python/main.py index 74444e7bf..b38452a89 100644 --- a/test/scenarios/prompts/reasoning-effort/python/main.py +++ b/test/scenarios/prompts/reasoning-effort/python/main.py @@ -28,7 +28,7 @@ async def main(): print("Reasoning effort: low") print(f"Response: {response.data.content}") - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/prompts/reasoning-effort/typescript/src/index.ts b/test/scenarios/prompts/reasoning-effort/typescript/src/index.ts index fd2091ef0..e569fd705 100644 --- a/test/scenarios/prompts/reasoning-effort/typescript/src/index.ts +++ b/test/scenarios/prompts/reasoning-effort/typescript/src/index.ts @@ -27,7 +27,7 @@ async function main() { console.log(`Response: ${response.data.content}`); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/prompts/system-message/go/main.go b/test/scenarios/prompts/system-message/go/main.go index aeef76137..074c9994b 100644 --- a/test/scenarios/prompts/system-message/go/main.go +++ b/test/scenarios/prompts/system-message/go/main.go @@ -33,7 +33,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/prompts/system-message/python/main.py b/test/scenarios/prompts/system-message/python/main.py index a3bfccdcf..5e396c8cd 100644 --- a/test/scenarios/prompts/system-message/python/main.py +++ b/test/scenarios/prompts/system-message/python/main.py @@ -27,7 +27,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/prompts/system-message/typescript/src/index.ts b/test/scenarios/prompts/system-message/typescript/src/index.ts index dc518069b..e0eb0aab7 100644 --- a/test/scenarios/prompts/system-message/typescript/src/index.ts +++ b/test/scenarios/prompts/system-message/typescript/src/index.ts @@ -23,7 +23,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/sessions/concurrent-sessions/go/main.go b/test/scenarios/sessions/concurrent-sessions/go/main.go index 02b3f03ae..ced915531 100644 --- a/test/scenarios/sessions/concurrent-sessions/go/main.go +++ b/test/scenarios/sessions/concurrent-sessions/go/main.go @@ -35,7 +35,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session1.Destroy() + defer session1.Disconnect() session2, err := client.CreateSession(ctx, &copilot.SessionConfig{ Model: "claude-haiku-4.5", @@ -48,7 +48,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session2.Destroy() + defer session2.Disconnect() type result struct { label string diff --git a/test/scenarios/sessions/concurrent-sessions/python/main.py b/test/scenarios/sessions/concurrent-sessions/python/main.py index 171a202e4..ebca89901 100644 --- a/test/scenarios/sessions/concurrent-sessions/python/main.py +++ b/test/scenarios/sessions/concurrent-sessions/python/main.py @@ -44,7 +44,7 @@ async def main(): if response2: print("Session 2 (robot):", response2.data.content) - await asyncio.gather(session1.destroy(), session2.destroy()) + await asyncio.gather(session1.disconnect(), session2.disconnect()) finally: await client.stop() diff --git a/test/scenarios/sessions/concurrent-sessions/typescript/src/index.ts b/test/scenarios/sessions/concurrent-sessions/typescript/src/index.ts index 80772886a..89543d281 100644 --- a/test/scenarios/sessions/concurrent-sessions/typescript/src/index.ts +++ b/test/scenarios/sessions/concurrent-sessions/typescript/src/index.ts @@ -35,7 +35,7 @@ async function main() { console.log("Session 2 (robot):", response2.data.content); } - await Promise.all([session1.destroy(), session2.destroy()]); + await Promise.all([session1.disconnect(), session2.disconnect()]); } finally { await client.stop(); process.exit(0); diff --git a/test/scenarios/sessions/infinite-sessions/go/main.go b/test/scenarios/sessions/infinite-sessions/go/main.go index 38090660c..540f8f6b4 100644 --- a/test/scenarios/sessions/infinite-sessions/go/main.go +++ b/test/scenarios/sessions/infinite-sessions/go/main.go @@ -39,7 +39,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() prompts := []string{ "What is the capital of France?", diff --git a/test/scenarios/sessions/infinite-sessions/python/main.py b/test/scenarios/sessions/infinite-sessions/python/main.py index fe39a7117..23749d06f 100644 --- a/test/scenarios/sessions/infinite-sessions/python/main.py +++ b/test/scenarios/sessions/infinite-sessions/python/main.py @@ -38,7 +38,7 @@ async def main(): print("Infinite sessions test complete — all messages processed successfully") - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/sessions/infinite-sessions/typescript/src/index.ts b/test/scenarios/sessions/infinite-sessions/typescript/src/index.ts index a3b3de61c..9de7b34f7 100644 --- a/test/scenarios/sessions/infinite-sessions/typescript/src/index.ts +++ b/test/scenarios/sessions/infinite-sessions/typescript/src/index.ts @@ -37,7 +37,7 @@ async function main() { console.log("Infinite sessions test complete — all messages processed successfully"); - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/sessions/multi-user-short-lived/README.md b/test/scenarios/sessions/multi-user-short-lived/README.md index 6596fa7bb..17e7e1278 100644 --- a/test/scenarios/sessions/multi-user-short-lived/README.md +++ b/test/scenarios/sessions/multi-user-short-lived/README.md @@ -17,7 +17,7 @@ Demonstrates a **stateless backend pattern** where multiple users interact with │(new) │ │(new)│ │(new) │ └──────┘ └─────┘ └──────┘ -Each request → new session → destroy after response +Each request → new session → disconnect after response Virtual FS per user (in-memory, not shared across users) ``` diff --git a/test/scenarios/sessions/session-resume/go/main.go b/test/scenarios/sessions/session-resume/go/main.go index 6ec4bb02d..2ba0b24bc 100644 --- a/test/scenarios/sessions/session-resume/go/main.go +++ b/test/scenarios/sessions/session-resume/go/main.go @@ -38,7 +38,7 @@ func main() { log.Fatal(err) } - // 3. Get the session ID (don't destroy — resume needs the session to persist) + // 3. Get the session ID (don't disconnect — resume needs the session to persist) sessionID := session.SessionID // 4. Resume the session with the same ID @@ -49,7 +49,7 @@ func main() { log.Fatal(err) } fmt.Println("Session resumed") - defer resumed.Destroy() + defer resumed.Disconnect() // 5. Ask for the secret word response, err := resumed.SendAndWait(ctx, copilot.MessageOptions{ diff --git a/test/scenarios/sessions/session-resume/python/main.py b/test/scenarios/sessions/session-resume/python/main.py index b65370b97..7eb5e0cae 100644 --- a/test/scenarios/sessions/session-resume/python/main.py +++ b/test/scenarios/sessions/session-resume/python/main.py @@ -23,7 +23,7 @@ async def main(): {"prompt": "Remember this: the secret word is PINEAPPLE."} ) - # 3. Get the session ID (don't destroy — resume needs the session to persist) + # 3. Get the session ID (don't disconnect — resume needs the session to persist) session_id = session.session_id # 4. Resume the session with the same ID @@ -38,7 +38,7 @@ async def main(): if response: print(response.data.content) - await resumed.destroy() + await resumed.disconnect() finally: await client.stop() diff --git a/test/scenarios/sessions/session-resume/typescript/src/index.ts b/test/scenarios/sessions/session-resume/typescript/src/index.ts index 7d08f40ef..9e0a16859 100644 --- a/test/scenarios/sessions/session-resume/typescript/src/index.ts +++ b/test/scenarios/sessions/session-resume/typescript/src/index.ts @@ -18,7 +18,7 @@ async function main() { prompt: "Remember this: the secret word is PINEAPPLE.", }); - // 3. Get the session ID (don't destroy — resume needs the session to persist) + // 3. Get the session ID (don't disconnect — resume needs the session to persist) const sessionId = session.sessionId; // 4. Resume the session with the same ID @@ -34,7 +34,7 @@ async function main() { console.log(response.data.content); } - await resumed.destroy(); + await resumed.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/sessions/streaming/go/main.go b/test/scenarios/sessions/streaming/go/main.go index 0be9ae031..6243a1662 100644 --- a/test/scenarios/sessions/streaming/go/main.go +++ b/test/scenarios/sessions/streaming/go/main.go @@ -27,7 +27,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() chunkCount := 0 session.On(func(event copilot.SessionEvent) { diff --git a/test/scenarios/sessions/streaming/python/main.py b/test/scenarios/sessions/streaming/python/main.py index 2bbc94e78..94569de11 100644 --- a/test/scenarios/sessions/streaming/python/main.py +++ b/test/scenarios/sessions/streaming/python/main.py @@ -34,7 +34,7 @@ def on_event(event): print(response.data.content) print(f"\nStreaming chunks received: {chunk_count}") - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/sessions/streaming/typescript/src/index.ts b/test/scenarios/sessions/streaming/typescript/src/index.ts index fb0a23bed..f70dcccec 100644 --- a/test/scenarios/sessions/streaming/typescript/src/index.ts +++ b/test/scenarios/sessions/streaming/typescript/src/index.ts @@ -26,7 +26,7 @@ async function main() { } console.log(`\nStreaming chunks received: ${chunkCount}`); - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/tools/custom-agents/go/main.go b/test/scenarios/tools/custom-agents/go/main.go index 1ce90d47e..f2add8224 100644 --- a/test/scenarios/tools/custom-agents/go/main.go +++ b/test/scenarios/tools/custom-agents/go/main.go @@ -35,7 +35,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What custom agents are available? Describe the researcher agent and its capabilities.", diff --git a/test/scenarios/tools/custom-agents/python/main.py b/test/scenarios/tools/custom-agents/python/main.py index d4e416716..0b5f073d5 100644 --- a/test/scenarios/tools/custom-agents/python/main.py +++ b/test/scenarios/tools/custom-agents/python/main.py @@ -32,7 +32,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/tools/custom-agents/typescript/src/index.ts b/test/scenarios/tools/custom-agents/typescript/src/index.ts index b098bffa8..f6e163256 100644 --- a/test/scenarios/tools/custom-agents/typescript/src/index.ts +++ b/test/scenarios/tools/custom-agents/typescript/src/index.ts @@ -28,7 +28,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/tools/mcp-servers/go/main.go b/test/scenarios/tools/mcp-servers/go/main.go index 70831cafa..a6e2e9c1f 100644 --- a/test/scenarios/tools/mcp-servers/go/main.go +++ b/test/scenarios/tools/mcp-servers/go/main.go @@ -53,7 +53,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/tools/mcp-servers/python/main.py b/test/scenarios/tools/mcp-servers/python/main.py index 81d2e39ba..f092fb9a8 100644 --- a/test/scenarios/tools/mcp-servers/python/main.py +++ b/test/scenarios/tools/mcp-servers/python/main.py @@ -47,7 +47,7 @@ async def main(): else: print("\nNo MCP servers configured (set MCP_SERVER_CMD to test with a real server)") - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/tools/mcp-servers/typescript/src/index.ts b/test/scenarios/tools/mcp-servers/typescript/src/index.ts index 41afa5837..1e8c11466 100644 --- a/test/scenarios/tools/mcp-servers/typescript/src/index.ts +++ b/test/scenarios/tools/mcp-servers/typescript/src/index.ts @@ -43,7 +43,7 @@ async function main() { console.log("\nNo MCP servers configured (set MCP_SERVER_CMD to test with a real server)"); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/tools/no-tools/go/main.go b/test/scenarios/tools/no-tools/go/main.go index d453f0dfd..62af3bcea 100644 --- a/test/scenarios/tools/no-tools/go/main.go +++ b/test/scenarios/tools/no-tools/go/main.go @@ -36,7 +36,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "Use the bash tool to run 'echo hello'.", diff --git a/test/scenarios/tools/no-tools/python/main.py b/test/scenarios/tools/no-tools/python/main.py index d857183c0..a3824bab7 100644 --- a/test/scenarios/tools/no-tools/python/main.py +++ b/test/scenarios/tools/no-tools/python/main.py @@ -30,7 +30,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/tools/no-tools/typescript/src/index.ts b/test/scenarios/tools/no-tools/typescript/src/index.ts index dea9c4f14..487b47622 100644 --- a/test/scenarios/tools/no-tools/typescript/src/index.ts +++ b/test/scenarios/tools/no-tools/typescript/src/index.ts @@ -26,7 +26,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/tools/skills/go/main.go b/test/scenarios/tools/skills/go/main.go index e322dda6c..5652de329 100644 --- a/test/scenarios/tools/skills/go/main.go +++ b/test/scenarios/tools/skills/go/main.go @@ -40,7 +40,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "Use the greeting skill to greet someone named Alice.", diff --git a/test/scenarios/tools/skills/python/main.py b/test/scenarios/tools/skills/python/main.py index 5adb74b76..3e06650b5 100644 --- a/test/scenarios/tools/skills/python/main.py +++ b/test/scenarios/tools/skills/python/main.py @@ -34,7 +34,7 @@ async def main(): print("\nSkill directories configured successfully") - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/tools/skills/typescript/src/index.ts b/test/scenarios/tools/skills/typescript/src/index.ts index fa4b33727..de7f13568 100644 --- a/test/scenarios/tools/skills/typescript/src/index.ts +++ b/test/scenarios/tools/skills/typescript/src/index.ts @@ -32,7 +32,7 @@ async function main() { console.log("\nSkill directories configured successfully"); - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/tools/tool-filtering/go/main.go b/test/scenarios/tools/tool-filtering/go/main.go index a774fb3e8..851ca3111 100644 --- a/test/scenarios/tools/tool-filtering/go/main.go +++ b/test/scenarios/tools/tool-filtering/go/main.go @@ -33,7 +33,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What tools do you have available? List each one by name.", diff --git a/test/scenarios/tools/tool-filtering/python/main.py b/test/scenarios/tools/tool-filtering/python/main.py index 174be620e..1fdfacc76 100644 --- a/test/scenarios/tools/tool-filtering/python/main.py +++ b/test/scenarios/tools/tool-filtering/python/main.py @@ -27,7 +27,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/tools/tool-filtering/typescript/src/index.ts b/test/scenarios/tools/tool-filtering/typescript/src/index.ts index 40cc91124..9976e38f8 100644 --- a/test/scenarios/tools/tool-filtering/typescript/src/index.ts +++ b/test/scenarios/tools/tool-filtering/typescript/src/index.ts @@ -24,7 +24,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/tools/virtual-filesystem/go/main.go b/test/scenarios/tools/virtual-filesystem/go/main.go index 29b1eef4f..39e3d910e 100644 --- a/test/scenarios/tools/virtual-filesystem/go/main.go +++ b/test/scenarios/tools/virtual-filesystem/go/main.go @@ -100,7 +100,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "Create a file called plan.md with a brief 3-item project plan " + diff --git a/test/scenarios/tools/virtual-filesystem/python/main.py b/test/scenarios/tools/virtual-filesystem/python/main.py index b150c1a2a..9a51e7efa 100644 --- a/test/scenarios/tools/virtual-filesystem/python/main.py +++ b/test/scenarios/tools/virtual-filesystem/python/main.py @@ -80,7 +80,7 @@ async def main(): print(f"\n[{path}]") print(content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/tools/virtual-filesystem/typescript/src/index.ts b/test/scenarios/tools/virtual-filesystem/typescript/src/index.ts index 0a6f0ffd1..4f7dadfd6 100644 --- a/test/scenarios/tools/virtual-filesystem/typescript/src/index.ts +++ b/test/scenarios/tools/virtual-filesystem/typescript/src/index.ts @@ -74,7 +74,7 @@ async function main() { console.log(content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/transport/reconnect/README.md b/test/scenarios/transport/reconnect/README.md index 4ae3c22d2..c2ed0d2fa 100644 --- a/test/scenarios/transport/reconnect/README.md +++ b/test/scenarios/transport/reconnect/README.md @@ -7,8 +7,8 @@ Tests that a **pre-running** `copilot` TCP server correctly handles **multiple s │ Your App │ ─────────────────▶ │ Copilot CLI │ │ (SDK) │ ◀───────────────── │ (TCP server) │ └─────────────┘ └──────────────┘ - Session 1: create → send → destroy - Session 2: create → send → destroy + Session 1: create → send → disconnect + Session 2: create → send → disconnect ``` ## What This Tests diff --git a/test/scenarios/transport/reconnect/csharp/Program.cs b/test/scenarios/transport/reconnect/csharp/Program.cs index a93ed8a71..80dc482da 100644 --- a/test/scenarios/transport/reconnect/csharp/Program.cs +++ b/test/scenarios/transport/reconnect/csharp/Program.cs @@ -28,7 +28,7 @@ Console.Error.WriteLine("No response content received for session 1"); Environment.Exit(1); } - Console.WriteLine("Session 1 destroyed\n"); + Console.WriteLine("Session 1 disconnected\n"); // Second session — tests that the server accepts new sessions Console.WriteLine("--- Session 2 ---"); @@ -51,7 +51,7 @@ Console.Error.WriteLine("No response content received for session 2"); Environment.Exit(1); } - Console.WriteLine("Session 2 destroyed"); + Console.WriteLine("Session 2 disconnected"); Console.WriteLine("\nReconnect test passed — both sessions completed successfully"); } diff --git a/test/scenarios/transport/reconnect/go/main.go b/test/scenarios/transport/reconnect/go/main.go index 27f6c1592..493e9d258 100644 --- a/test/scenarios/transport/reconnect/go/main.go +++ b/test/scenarios/transport/reconnect/go/main.go @@ -43,8 +43,8 @@ func main() { log.Fatal("No response content received for session 1") } - session1.Destroy() - fmt.Println("Session 1 destroyed") + session1.Disconnect() + fmt.Println("Session 1 disconnected") fmt.Println() // Session 2 — tests that the server accepts new sessions @@ -69,8 +69,8 @@ func main() { log.Fatal("No response content received for session 2") } - session2.Destroy() - fmt.Println("Session 2 destroyed") + session2.Disconnect() + fmt.Println("Session 2 disconnected") fmt.Println("\nReconnect test passed — both sessions completed successfully") } diff --git a/test/scenarios/transport/reconnect/python/main.py b/test/scenarios/transport/reconnect/python/main.py index e8aecea50..1b82b1096 100644 --- a/test/scenarios/transport/reconnect/python/main.py +++ b/test/scenarios/transport/reconnect/python/main.py @@ -24,8 +24,8 @@ async def main(): print("No response content received for session 1", file=sys.stderr) sys.exit(1) - await session1.destroy() - print("Session 1 destroyed\n") + await session1.disconnect() + print("Session 1 disconnected\n") # Second session — tests that the server accepts new sessions print("--- Session 2 ---") @@ -41,8 +41,8 @@ async def main(): print("No response content received for session 2", file=sys.stderr) sys.exit(1) - await session2.destroy() - print("Session 2 destroyed") + await session2.disconnect() + print("Session 2 disconnected") print("\nReconnect test passed — both sessions completed successfully") finally: diff --git a/test/scenarios/transport/reconnect/typescript/src/index.ts b/test/scenarios/transport/reconnect/typescript/src/index.ts index 57bac483d..ca28df94b 100644 --- a/test/scenarios/transport/reconnect/typescript/src/index.ts +++ b/test/scenarios/transport/reconnect/typescript/src/index.ts @@ -21,8 +21,8 @@ async function main() { process.exit(1); } - await session1.destroy(); - console.log("Session 1 destroyed\n"); + await session1.disconnect(); + console.log("Session 1 disconnected\n"); // Second session — tests that the server accepts new sessions console.log("--- Session 2 ---"); @@ -39,8 +39,8 @@ async function main() { process.exit(1); } - await session2.destroy(); - console.log("Session 2 destroyed"); + await session2.disconnect(); + console.log("Session 2 disconnected"); console.log("\nReconnect test passed — both sessions completed successfully"); } finally { diff --git a/test/scenarios/transport/stdio/go/main.go b/test/scenarios/transport/stdio/go/main.go index e548a08e7..b8902fd99 100644 --- a/test/scenarios/transport/stdio/go/main.go +++ b/test/scenarios/transport/stdio/go/main.go @@ -27,7 +27,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/transport/stdio/python/main.py b/test/scenarios/transport/stdio/python/main.py index 138bb5646..d1441361f 100644 --- a/test/scenarios/transport/stdio/python/main.py +++ b/test/scenarios/transport/stdio/python/main.py @@ -19,7 +19,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/transport/stdio/typescript/src/index.ts b/test/scenarios/transport/stdio/typescript/src/index.ts index 989a0b9a6..bee246f64 100644 --- a/test/scenarios/transport/stdio/typescript/src/index.ts +++ b/test/scenarios/transport/stdio/typescript/src/index.ts @@ -17,7 +17,7 @@ async function main() { console.log(response.data.content); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } diff --git a/test/scenarios/transport/tcp/go/main.go b/test/scenarios/transport/tcp/go/main.go index 9a0b1be4e..8be7dd605 100644 --- a/test/scenarios/transport/tcp/go/main.go +++ b/test/scenarios/transport/tcp/go/main.go @@ -31,7 +31,7 @@ func main() { if err != nil { log.Fatal(err) } - defer session.Destroy() + defer session.Disconnect() response, err := session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: "What is the capital of France?", diff --git a/test/scenarios/transport/tcp/python/main.py b/test/scenarios/transport/tcp/python/main.py index 05aaa9270..c407d4fea 100644 --- a/test/scenarios/transport/tcp/python/main.py +++ b/test/scenarios/transport/tcp/python/main.py @@ -18,7 +18,7 @@ async def main(): if response: print(response.data.content) - await session.destroy() + await session.disconnect() finally: await client.stop() diff --git a/test/scenarios/transport/tcp/typescript/src/index.ts b/test/scenarios/transport/tcp/typescript/src/index.ts index 139e47a86..29a19dd10 100644 --- a/test/scenarios/transport/tcp/typescript/src/index.ts +++ b/test/scenarios/transport/tcp/typescript/src/index.ts @@ -19,7 +19,7 @@ async function main() { process.exit(1); } - await session.destroy(); + await session.disconnect(); } finally { await client.stop(); } From fbc948b534524ee70306a57e9bfbc21a352d19ba Mon Sep 17 00:00:00 2001 From: Patrick Nikoletich Date: Wed, 4 Mar 2026 20:21:58 -0800 Subject: [PATCH 4/5] fix: rename snapshot to match updated test name The hooks_extended test 'should invoke onSessionEnd hook when session is destroyed' was renamed to '...disconnected', but the snapshot YAML file wasn't renamed to match, causing CI to fail with 'No cached response'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ld_invoke_onsessionend_hook_when_session_is_disconnected.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/snapshots/hooks_extended/{should_invoke_onsessionend_hook_when_session_is_destroyed.yaml => should_invoke_onsessionend_hook_when_session_is_disconnected.yaml} (100%) diff --git a/test/snapshots/hooks_extended/should_invoke_onsessionend_hook_when_session_is_destroyed.yaml b/test/snapshots/hooks_extended/should_invoke_onsessionend_hook_when_session_is_disconnected.yaml similarity index 100% rename from test/snapshots/hooks_extended/should_invoke_onsessionend_hook_when_session_is_destroyed.yaml rename to test/snapshots/hooks_extended/should_invoke_onsessionend_hook_when_session_is_disconnected.yaml From 7c3c0e6950a90423d9c4d403b6038533fee54112 Mon Sep 17 00:00:00 2001 From: Patrick Nikoletich Date: Thu, 5 Mar 2026 13:52:39 -0800 Subject: [PATCH 5/5] dotnet: remove DisconnectAsync, keep only DisposeAsync Address review feedback from SteveSandersonMS: for .NET, the standard IAsyncDisposable pattern (DisposeAsync) is sufficient on its own without a duplicate DisconnectAsync method. Moves the disconnect implementation directly into DisposeAsync and removes the separate DisconnectAsync method. Updates all references in Client.cs and README.md accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/README.md | 12 ++++------- dotnet/src/Client.cs | 6 +++--- dotnet/src/Session.cs | 48 ++++++++++--------------------------------- 3 files changed, 18 insertions(+), 48 deletions(-) diff --git a/dotnet/README.md b/dotnet/README.md index ba942796f..bdb3e8dab 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -217,22 +217,18 @@ Abort the currently processing message in this session. Get all events/messages from this session. -##### `DisconnectAsync(CancellationToken cancellationToken = default): Task` - -Disconnect the session and release in-memory resources. Session data on disk is preserved — the conversation can be resumed later via `ResumeSessionAsync()`. To permanently delete session data, use `client.DeleteSessionAsync()`. - ##### `DisposeAsync(): ValueTask` -Calls `DisconnectAsync()`. Enables the `await using` pattern for automatic cleanup: +Close the session and release in-memory resources. Session data on disk is preserved — the conversation can be resumed later via `ResumeSessionAsync()`. To permanently delete session data, use `client.DeleteSessionAsync()`. ```csharp // Preferred: automatic cleanup via await using await using var session = await client.CreateSessionAsync(config); -// session is automatically disconnected when leaving scope +// session is automatically disposed when leaving scope -// Alternative: explicit disconnect +// Alternative: explicit dispose var session2 = await client.CreateSessionAsync(config); -await session2.DisconnectAsync(); +await session2.DisposeAsync(); ``` --- diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 399c9891c..a340cd63a 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -243,11 +243,11 @@ public async Task StopAsync() { try { - await session.DisconnectAsync(); + await session.DisposeAsync(); } catch (Exception ex) { - errors.Add(new Exception($"Failed to disconnect session {session.SessionId}: {ex.Message}", ex)); + errors.Add(new Exception($"Failed to dispose session {session.SessionId}: {ex.Message}", ex)); } } @@ -669,7 +669,7 @@ public async Task> ListModelsAsync(CancellationToken cancellatio /// A task that represents the asynchronous delete operation. /// Thrown when the session does not exist or deletion fails. /// - /// Unlike , which only releases in-memory + /// Unlike , which only releases in-memory /// resources and preserves session data for later resumption, this method is /// irreversible. The session cannot be resumed after deletion. /// diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index af175066d..054f10972 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -26,7 +26,7 @@ namespace GitHub.Copilot.SDK; /// /// /// implements . Use the -/// await using pattern for automatic cleanup, or call +/// await using pattern for automatic cleanup, or call /// explicitly. Disposing a session releases in-memory resources but preserves session data /// on disk — the conversation can be resumed later via /// . To permanently delete session data, @@ -530,11 +530,10 @@ public async Task SetModelAsync(string model, CancellationToken cancellationToke } /// - /// Disconnects this session and releases all in-memory resources (event handlers, + /// Closes this session and releases all in-memory resources (event handlers, /// tool handlers, permission handlers). /// - /// A that can be used to cancel the operation. - /// A task representing the disconnect operation. + /// A task representing the dispose operation. /// /// /// Session state on disk (conversation history, planning state, artifacts) is @@ -549,14 +548,16 @@ public async Task SetModelAsync(string model, CancellationToken cancellationToke /// /// /// - /// // Disconnect when done — session can still be resumed later - /// await session.DisconnectAsync(); - /// - /// // Or use 'await using' for automatic disconnection + /// // Using 'await using' for automatic disposal — session can still be resumed later /// await using var session = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); + /// + /// // Or manually dispose + /// var session2 = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); + /// // ... use the session ... + /// await session2.DisposeAsync(); /// /// - public async Task DisconnectAsync(CancellationToken cancellationToken = default) + public async ValueTask DisposeAsync() { if (Interlocked.Exchange(ref _isDisposed, 1) == 1) { @@ -566,7 +567,7 @@ public async Task DisconnectAsync(CancellationToken cancellationToken = default) try { await InvokeRpcAsync( - "session.destroy", [new SessionDestroyRequest() { SessionId = SessionId }], cancellationToken); + "session.destroy", [new SessionDestroyRequest() { SessionId = SessionId }], CancellationToken.None); } catch (ObjectDisposedException) { @@ -583,33 +584,6 @@ await InvokeRpcAsync( _permissionHandler = null; } - /// - /// Disposes the by disconnecting and releasing all resources. - /// - /// A task representing the dispose operation. - /// - /// - /// This method calls to perform cleanup. It is the - /// implementation of and enables the - /// await using pattern for automatic resource management. - /// - /// - /// - /// - /// // Using 'await using' for automatic disposal — session can still be resumed later - /// await using var session = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); - /// - /// // Or manually dispose - /// var session2 = await client.CreateSessionAsync(new() { OnPermissionRequest = PermissionHandler.ApproveAll }); - /// // ... use the session ... - /// await session2.DisposeAsync(); - /// - /// - public async ValueTask DisposeAsync() - { - await DisconnectAsync(); - } - internal record SendMessageRequest { public string SessionId { get; init; } = string.Empty;