stop method

Future<List<Exception>> stop()

Stops the client and closes all sessions.

Sessions are destroyed with up to 3 retries and exponential backoff. Returns a list of errors encountered during cleanup.

Implementation

Future<List<Exception>> stop() async {
  _forceStopping = true;
  final errors = <Exception>[];

  // Destroy all sessions with retry logic (direct RPC, bypassing
  // session.destroy()'s idempotency guard to allow real retries).
  final sessionsToDestroy = List<CopilotSession>.from(_sessions.values);
  for (final session in sessionsToDestroy) {
    Exception? lastError;

    for (var attempt = 1; attempt <= 3; attempt++) {
      try {
        await _connection?.sendRequest(
          'session.destroy',
          {'sessionId': session.sessionId},
          const Duration(seconds: 10),
        );
        lastError = null;
        break;
      } catch (e) {
        lastError = e is Exception ? e : Exception(e.toString());
        if (attempt < 3) {
          // Exponential backoff: 100ms, 200ms
          await Future<void>.delayed(
            Duration(milliseconds: 100 * (1 << (attempt - 1))),
          );
        }
      }
    }

    // Always clean up the session locally
    session.handleConnectionClose();

    if (lastError != null) {
      errors.add(Exception(
        'Failed to destroy session ${session.sessionId} after 3 attempts: '
        '$lastError',
      ));
    }
  }
  _sessions.clear();
  _modelsCache = null;

  // Close connection and transport
  try {
    await _connection?.close();
  } catch (e) {
    errors.add(
        e is Exception ? e : Exception('Failed to close connection: $e'));
  }
  _connection = null;

  try {
    await _transport?.close();
  } catch (e) {
    errors
        .add(e is Exception ? e : Exception('Failed to close transport: $e'));
  }

  _setConnectionState(ConnectionState.disconnected);
  _forceStopping = false;
  return errors;
}