Udostępnij za pośrednictwem


Jak utworzyć dostawcę opinii

Program PowerShell 7.4 wprowadził koncepcję dostawców opinii. Dostawca opinii to moduł programu PowerShell, który implementuje interfejs IFeedbackProvider w celu udostępnienia sugestii poleceń opartych na próbach wykonywania poleceń użytkownika. Dostawca jest wyzwalany, gdy wystąpi sukces lub niepowodzenie wykonania. Dostawcy opinii używają informacji z powodzenia lub niepowodzenia przekazywania opinii.

Warunki wstępne

Aby utworzyć dostawcę opinii, musisz spełnić następujące wymagania wstępne:

  • Instalowanie programu PowerShell w wersji 7.4 lub nowszej
    • Należy włączyć funkcję eksperymentalną PSFeedbackProvider, aby umożliwić obsługę dostawców opinii i predyktorów. Aby uzyskać więcej informacji, zobacz Korzystanie z funkcji eksperymentalnych.
  • Instalowanie zestawu .NET 8 SDK — 8.0.0 lub nowszego
    • Aby uzyskać najnowszą wersję zestawu SDK, odwiedź stronę pobierania .NET 8.0.

Omówienie źródła opinii zwrotnej

Dostawca opinii to moduł binarny programu PowerShell, który implementuje interfejs System.Management.Automation.Subsystem.Feedback.IFeedbackProvider. Ten interfejs deklaruje metody uzyskiwania opinii na podstawie danych wejściowych wiersza polecenia. Interfejs opinii może udostępniać sugestie na podstawie powodzenia lub niepowodzenia polecenia wywoływanego przez użytkownika. Sugestie mogą być dowolne. Możesz na przykład zasugerować sposoby rozwiązania błędu lub lepszych rozwiązań, takich jak unikanie używania aliasów. Aby uzyskać więcej informacji, zobacz wpis na blogu Co to są dostawcy opinii?.

Na poniższym diagramie przedstawiono architekturę dostawcy opinii:

Diagram architektury dostawcy opinii.

W poniższych przykładach przedstawiono proces tworzenia prostego dostawcy opinii. Ponadto możesz zarejestrować dostawcę za pomocą interfejsu predykcji poleceń, aby dodać sugestie opinii użytkownika do doświadczenia z predyktorem poleceń. Aby uzyskać więcej informacji na temat predyktorów, zobacz Korzystanie z predyktorów w PSReadLine i Jak stworzyć predyktor wiersza poleceń.

Krok 1. Tworzenie nowego projektu biblioteki klas

Użyj następującego polecenia, aby utworzyć nowy projekt w katalogu projektu:

dotnet new classlib --name MyFeedbackProvider

Dodaj odwołanie do pakietu System.Management.Automation do pliku .csproj. W poniższym przykładzie pokazano zaktualizowany plik .csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Management.Automation" Version="7.4.0-preview.3">
        <ExcludeAssets>contentFiles</ExcludeAssets>
        <PrivateAssets>All</PrivateAssets>
    </PackageReference>
  </ItemGroup>
</Project>

Notatka

Należy zmienić wersję zestawu System.Management.Automation, aby odpowiadała wersji zapoznawczej programu PowerShell, na którą się ukierunkowujesz. Minimalna wersja to 7.4.0-preview.3.

Krok 2. Dodawanie definicji klasy dla dostawcy

Zmień nazwę pliku Class1.cs, aby był zgodny z nazwą dostawcy. W tym przykładzie użyto myFeedbackProvider.cs. Ten plik zawiera dwie główne klasy definiujące dostawcę opinii. W poniższym przykładzie przedstawiono podstawowy szablon definicji klas.

using System.Management.Automation;
using System.Management.Automation.Subsystem;
using System.Management.Automation.Subsystem.Feedback;
using System.Management.Automation.Subsystem.Prediction;
using System.Management.Automation.Language;

namespace myFeedbackProvider;

public sealed class myFeedbackProvider : IFeedbackProvider, ICommandPredictor
{

}

public class Init : IModuleAssemblyInitializer, IModuleAssemblyCleanup
{

}

Krok 3. Implementowanie klasy Init

Klasa Init rejestruje i wyrejestrowuje dostawcę opinii za pomocą menedżera podsystemu. Metoda OnImport() jest uruchamiana po załadowaniu modułu binarnego. Metoda OnRemove() jest uruchamiana po usunięciu modułu binarnego. W tym przykładzie zarejestrowano zarówno dostawcę opinii, jak i podsystem predyktora poleceń.

public class Init : IModuleAssemblyInitializer, IModuleAssemblyCleanup
{
    private const string Id = "<ADD YOUR GUID HERE>";

    public void OnImport()
    {
        var feedback = new myFeedbackProvider(Id);
        SubsystemManager.RegisterSubsystem(SubsystemKind.FeedbackProvider, feedback);
        SubsystemManager.RegisterSubsystem(SubsystemKind.CommandPredictor, feedback);
    }

    public void OnRemove(PSModuleInfo psModuleInfo)
    {
        SubsystemManager.UnregisterSubsystem<ICommandPredictor>(new Guid(Id));
        SubsystemManager.UnregisterSubsystem<IFeedbackProvider>(new Guid(Id));
    }
}

Zastąp wartość symbolu zastępczego <ADD YOUR GUID HERE> unikatowym identyfikatorem GUID. Możesz wygenerować identyfikator GUID za pomocą cmdletu New-Guid.

New-Guid

Identyfikator GUID jest unikatowym identyfikatorem twojego dostawcy. Dostawca musi mieć unikatowy identyfikator do zarejestrowania w podsystemie.

Krok 4. Dodawanie składowych klasy i definiowanie konstruktora

Poniższy kod implementuje właściwości zdefiniowane w interfejsach, dodaje potrzebne składowe klasy i tworzy konstruktor dla klasy myFeedbackProvider.

/// <summary>
/// Gets the global unique identifier for the subsystem implementation.
/// </summary>
private readonly Guid _guid;
public Guid Id => _guid;

/// <summary>
/// Gets the name of a subsystem implementation, this will be the name displayed when triggered
/// </summary>
public string Name => "myFeedbackProvider";

/// <summary>
/// Gets the description of a subsystem implementation.
/// </summary>
public string Description => "This is very simple feedback provider";

/// <summary>
/// Default implementation. No function is required for a feedback provider.
/// </summary>
Dictionary<string, string>? ISubsystem.FunctionsToDefine => null;

/// <summary>
/// Gets the types of trigger for this feedback provider.
/// </summary>
/// <remarks>
/// The default implementation triggers a feedback provider by <see cref="FeedbackTrigger.CommandNotFound"/> only.
/// </remarks>
public FeedbackTrigger Trigger => FeedbackTrigger.All;

/// <summary>
/// List of candidates from the feedback provider to be passed as predictor results
/// </summary>
private List<string>? _candidates;

/// <summary>
/// PowerShell session used to run PowerShell commands that help create suggestions.
/// </summary>
private PowerShell _powershell;

internal myFeedbackProvider(string guid)
{
    _guid = new Guid(guid); // Save guid
    _powershell = PowerShell.Create(); // Create PowerShell instance
}

Krok 5. Tworzenie metody GetFeedback()

Metoda GetFeedback przyjmuje dwa parametry, context i token. Parametr context odbiera informacje o wyzwalaczu, dzięki czemu możesz zdecydować, jak odpowiedzieć sugestiami. Parametr token jest używany do anulowania. Ta funkcja zwraca FeedbackItem zawierającą sugestię.

/// <summary>
/// Gets feedback based on the given commandline and error record.
/// </summary>
/// <param name="context">The context for the feedback call.</param>
/// <param name="token">The cancellation token to cancel the operation.</param>
/// <returns>The feedback item.</returns>
public FeedbackItem? GetFeedback(FeedbackContext context, CancellationToken token)
{
    // Target describes the different kinds of triggers to activate on,
    var target = context.Trigger;
    var commandLine = context.CommandLine;
    var ast = context.CommandLineAst;

    // defining the header and footer variables
    string header;
    string footer;

    // List of the actions
    List<string>? actions = new List<string>();

    // Trigger on success code goes here

    // Trigger on error code goes here

    return null;
}

Na poniższej ilustracji przedstawiono sposób użycia tych pól w sugestiach wyświetlanych użytkownikowi.

Zrzut ekranu przedstawiający przykładowych dostawców opinii

Tworzenie sugestii dotyczących wyzwalacza powodzenia

W przypadku pomyślnego wywołania chcemy rozwinąć wszystkie aliasy używane w ostatnim wykonaniu. Korzystając z CommandLineAst, identyfikujemy wszystkie aliasy poleceń i zamiast tego tworzymy sugestię użycia w pełni kwalifikowanej nazwy polecenia.

// Trigger on success
if (target == FeedbackTrigger.Success)
{
    // Getting the commands from the AST and only finding those that are Commands
    var astCmds = ast.FindAll((cAst) => cAst is CommandAst, true);

    // Inspect each of the commands
    foreach(var command in astCmds)
    {

        // Get the command name
        var aliasedCmd = ((CommandAst) command).GetCommandName();

        // Check if its an alias or not, if so then add it to the list of actions
        if(TryGetAlias(aliasedCmd, out string commandString))
        {
            actions.Add($"{aliasedCmd} --> {commandString}");
        }
    }

    // If no alias was found return null
    if(actions.Count == 0)
    {
        return null;
    }

    // If aliases are found, set the header to a description and return a new FeedbackItem.
    header = "You have used an aliased command:";
    // Copy actions to _candidates for the predictor
    _candidates = actions;

    return new FeedbackItem(header, actions);
}

Implementowanie metody TryGetAlias()

Metoda TryGetAlias() jest prywatną funkcją pomocnika, która zwraca wartość logiczną, aby wskazać, czy polecenie jest aliasem. W konstruktorze klasy utworzyliśmy wystąpienie programu PowerShell, którego możemy użyć do uruchamiania poleceń programu PowerShell. Metoda TryGetAlias() używa tego wystąpienia programu PowerShell do wywołania metody GetCommand w celu określenia, czy polecenie jest aliasem. Obiekt AliasInfo zwrócony przez GetCommand zawiera pełną nazwę aliasowanego polecenia.

/// <summary>
/// Checks if a command is an alias.
/// </summary>
/// <param name="command">The command to check if alias</param>
/// <param name="targetCommand">The referenced command by the aliased command</param>
/// <returns>True if an alias and false if not</returns>
private bool TryGetAlias(string command, out string targetCommand)
{
    // Create PowerShell runspace as a session state proxy to run GetCommand and check
    // if its an alias
    AliasInfo? pwshAliasInfo =
        _powershell.Runspace.SessionStateProxy.InvokeCommand.GetCommand(command, CommandTypes.Alias) as AliasInfo;

    // if its null then it is not an aliased command so just return false
    if(pwshAliasInfo is null)
    {
        targetCommand = String.Empty;
        return false;
    }

    // Set targetCommand to referenced command name
    targetCommand = pwshAliasInfo.ReferencedCommand.Name;
    return true;
}

Przygotuj sugestie dla wyzwalacza błędu

Gdy wykonanie polecenia zakończy się niepowodzeniem, chcemy zasugerować, aby użytkownik Get-Help uzyskać więcej informacji o sposobie używania polecenia.

// Trigger on error
if (target == FeedbackTrigger.Error)
{
    // Gets the command that caused the error.
    var erroredCommand = context.LastError?.InvocationInfo.MyCommand;
    if (erroredCommand is null)
    {
        return null;
    }

    header = $"You have triggered an error with the command {erroredCommand}. Try using the following command to get help:";

    actions.Add($"Get-Help {erroredCommand}");
    footer = $"You can also check online documentation at https://learn.microsoft.com/en-us/powershell/module/?term={erroredCommand}";

    // Copy actions to _candidates for the predictor
    _candidates = actions;
    return new FeedbackItem(header, actions, footer, FeedbackDisplayLayout.Portrait);
}

Krok 6. Wyślij sugestie do prognozatora wiersza poleceń

Inny sposób, w jaki dostawca opinii może poprawić doświadczenie użytkownika, to dostarczanie sugestii poleceń do interfejsu ICommandPredictor. Aby uzyskać więcej informacji na temat tworzenia predyktora wiersza polecenia, zobacz Jak utworzyć predyktor wiersza polecenia.

Poniższy kod implementuje niezbędne metody z interfejsu ICommandPredictor, aby dodać funkcjonalność predykcyjną do dostawcy opinii.

  • CanAcceptFeedback() — ta metoda zwraca wartość logiczną wskazującą, czy predyktor akceptuje określony typ opinii.
  • GetSuggestion() — ta metoda zwraca obiekt SuggestionPackage zawierający sugestie, które mają być wyświetlane przez predyktor.
  • OnCommandLineAccepted() — ta metoda jest wywoływana, gdy wiersz polecenia jest akceptowany do wykonania.
/// <summary>
/// Gets a value indicating whether the predictor accepts a specific kind of feedback.
/// </summary>
/// <param name="client">Represents the client that initiates the call.</param>
/// <param name="feedback">A specific type of feedback.</param>
/// <returns>True or false, to indicate whether the specific feedback is accepted.</returns>
public bool CanAcceptFeedback(PredictionClient client, PredictorFeedbackKind feedback)
{
    return feedback switch
    {
        PredictorFeedbackKind.CommandLineAccepted => true,
        _ => false,
    };
}

/// <summary>
/// Get the predictive suggestions. It indicates the start of a suggestion rendering session.
/// </summary>
/// <param name="client">Represents the client that initiates the call.</param>
/// <param name="context">The <see cref="PredictionContext"/> object to be used for prediction.</param>
/// <param name="cancellationToken">The cancellation token to cancel the prediction.</param>
/// <returns>An instance of <see cref="SuggestionPackage"/>.</returns>
public SuggestionPackage GetSuggestion(
    PredictionClient client,
    PredictionContext context,
    CancellationToken cancellationToken)
{
    if (_candidates is not null)
    {
        string input = context.InputAst.Extent.Text;
        List<PredictiveSuggestion>? result = null;

        foreach (string c in _candidates)
        {
            if (c.StartsWith(input, StringComparison.OrdinalIgnoreCase))
            {
                result ??= new List<PredictiveSuggestion>(_candidates.Count);
                result.Add(new PredictiveSuggestion(c));
            }
        }

        if (result is not null)
        {
            return new SuggestionPackage(result);
        }
    }

    return default;
}

/// <summary>
/// A command line was accepted to execute.
/// The predictor can start processing early as needed with the latest history.
/// </summary>
/// <param name="client">Represents the client that initiates the call.</param>
/// <param name="history">History command lines provided as references for prediction.</param>
public void OnCommandLineAccepted(PredictionClient client, IReadOnlyList<string> history)
{
    // Reset the candidate state once the command is accepted.
    _candidates = null;
}

Krok 7. Tworzenie dostawcy opinii

Teraz możesz przystąpić do tworzenia i rozpoczynania korzystania z dostawcy opinii. Aby skompilować projekt, uruchom następujące polecenie:

dotnet build

To polecenie umożliwia utworzenie modułu programu PowerShell jako pliku DLL w następującej ścieżce folderu projektu: bin/Debug/net8.0/myFeedbackProvider

Podczas kompilowania na maszynach z systemem Windows może wystąpić błąd error NU1101: Unable to find package System.Management.Automation.. Aby rozwiązać ten problem, dodaj plik nuget.config do katalogu projektu i dodaj następujące elementy:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
  <disabledPackageSources>
    <clear />
  </disabledPackageSources>
</configuration>

Korzystanie z dostawcy opinii

Aby przetestować nowego dostawcę opinii, zaimportuj skompilowany moduł do sesji programu PowerShell. Można to zrobić, importując folder wspomniany po zakończeniu budowania.

Import-Module ./bin/Debug/net8.0/myFeedbackProvider

Gdy jesteś zadowolony z modułu, należy utworzyć manifest modułu, opublikować go w Galerii PowerShell i zainstalować go w $Env:PSModulePath. Aby uzyskać więcej informacji, zobacz Jak utworzyć manifest modułu. Możesz dodać polecenie Import-Module do skryptu $PROFILE, aby moduł był dostępny w sesji programu PowerShell.

Listę zainstalowanych dostawców opinii można uzyskać przy użyciu następującego polecenia:

Get-PSSubsystem -Kind FeedbackProvider
Kind              SubsystemType      IsRegistered Implementations
----              -------------      ------------ ---------------
FeedbackProvider  IFeedbackProvider          True {general}

Notatka

Get-PSSubsystem to eksperymentalne polecenie cmdlet wprowadzone w programie PowerShell 7.1 Aby użyć tego polecenia cmdlet, musisz włączyć funkcję eksperymentalną PSSubsystemPluginModel. Aby uzyskać więcej informacji, zobacz Korzystanie z funkcji eksperymentalnych.

Poniższy zrzut ekranu przedstawia kilka przykładowych sugestii od nowego dostawcy.

Zrzut ekranu przedstawiający wyzwalacze komunikatów zwrotnych dotyczących powodzenia i błędów

Poniżej przedstawiono plik GIF pokazujący, jak działa integracja predyktora z nowego dostawcy.

GIF systemu predykcyjnego pracującego z dostawcą opinii

Inni dostawcy opinii

Utworzyliśmy alternatywne źródło opinii, które można użyć jako dobry punkt odniesienia do głębszych przykładów.

polecenie nie znaleziono

Udostępniający informacje zwrotne command-not-found korzysta z narzędzia command-not-found w systemach Linux, aby dostarczać sugestii przy próbie uruchomienia poleceń natywnych, które są niedostępne. Kod można znaleźć w repozytorium GitHub lub pobrać samodzielnie z PowerShell Gallery .

PowerShell Adapter

Microsoft.PowerShell.PowerShellAdapter to dostawca opinii, który ułatwia konwertowanie danych wyjściowych tekstu z natywnych poleceń na obiekty programu PowerShell. Wykrywa ona "adaptery" w systemie i sugeruje ich użycie, gdy korzystasz z natywnego polecenia. Więcej informacji na temat adapterów programu PowerShell można znaleźć we wpisie na blogu dotyczącym opinii o adapterze programu PowerShell. Kod można również znaleźć w repozytorium GitHub lub pobrać go samodzielnie w galerii PowerShell .

Dodatek — pełny kod implementacji

Poniższy kod łączy poprzednie przykłady w celu znalezienia pełnej implementacji klasy dostawcy.

using System.Management.Automation;
using System.Management.Automation.Subsystem;
using System.Management.Automation.Subsystem.Feedback;
using System.Management.Automation.Subsystem.Prediction;
using System.Management.Automation.Language;

namespace myFeedbackProvider;

public sealed class myFeedbackProvider : IFeedbackProvider, ICommandPredictor
{
    /// <summary>
    /// Gets the global unique identifier for the subsystem implementation.
    /// </summary>
    private readonly Guid _guid;
    public Guid Id => _guid;

    /// <summary>
    /// Gets the name of a subsystem implementation, this will be the name displayed when triggered
    /// </summary>
    public string Name => "myFeedbackProvider";

    /// <summary>
    /// Gets the description of a subsystem implementation.
    /// </summary>
    public string Description => "This is very simple feedback provider";

    /// <summary>
    /// Default implementation. No function is required for a feedback provider.
    /// </summary>
    Dictionary<string, string>? ISubsystem.FunctionsToDefine => null;

    /// <summary>
    /// Gets the types of trigger for this feedback provider.
    /// </summary>
    /// <remarks>
    /// The default implementation triggers a feedback provider by <see cref="FeedbackTrigger.CommandNotFound"/> only.
    /// </remarks>
    public FeedbackTrigger Trigger => FeedbackTrigger.All;

    /// <summary>
    /// List of candidates from the feedback provider to be passed as predictor results
    /// </summary>
    private List<string>? _candidates;

    /// <summary>
    /// PowerShell session used to run PowerShell commands that help create suggestions.
    /// </summary>
    private PowerShell _powershell;

    // Constructor
    internal myFeedbackProvider(string guid)
    {
        _guid = new Guid(guid); // Save guid
        _powershell = PowerShell.Create(); // Create PowerShell instance
    }

    #region IFeedbackProvider
    /// <summary>
    /// Gets feedback based on the given commandline and error record.
    /// </summary>
    /// <param name="context">The context for the feedback call.</param>
    /// <param name="token">The cancellation token to cancel the operation.</param>
    /// <returns>The feedback item.</returns>
    public FeedbackItem? GetFeedback(FeedbackContext context, CancellationToken token)
    {
        // Target describes the different kinds of triggers to activate on,
        var target = context.Trigger;
        var commandLine = context.CommandLine;
        var ast = context.CommandLineAst;

        // defining the header and footer variables
        string header;
        string footer;

        // List of the actions
        List<string>? actions = new List<string>();

        // Trigger on success
        if (target == FeedbackTrigger.Success)
        {
            // Getting the commands from the AST and only finding those that are Commands
            var astCmds = ast.FindAll((cAst) => cAst is CommandAst, true);

            // Inspect each of the commands
            foreach(var command in astCmds)
            {

                // Get the command name
                var aliasedCmd = ((CommandAst) command).GetCommandName();

                // Check if its an alias or not, if so then add it to the list of actions
                if(TryGetAlias(aliasedCmd, out string commandString))
                {
                    actions.Add($"{aliasedCmd} --> {commandString}");
                }
            }

            // If no alias was found return null
            if(actions.Count == 0)
            {
                return null;
            }

            // If aliases are found, set the header to a description and return a new FeedbackItem.
            header = "You have used an aliased command:";
            // Copy actions to _candidates for the predictor
            _candidates = actions;

            return new FeedbackItem(header, actions);
        }

        // Trigger on error
        if (target == FeedbackTrigger.Error)
        {
            // Gets the command that caused the error.
            var erroredCommand = context.LastError?.InvocationInfo.MyCommand;
            if (erroredCommand is null)
            {
                return null;
            }

            header = $"You have triggered an error with the command {erroredCommand}. Try using the following command to get help:";

            actions.Add($"Get-Help {erroredCommand}");
            footer = $"You can also check online documentation at https://learn.microsoft.com/en-us/powershell/module/?term={erroredCommand}";

            // Copy actions to _candidates for the predictor
            _candidates = actions;
            return new FeedbackItem(header, actions, footer, FeedbackDisplayLayout.Portrait);
        }
        return null;
    }

    /// <summary>
    /// Checks if a command is an alias.
    /// </summary>
    /// <param name="command">The command to check if alias</param>
    /// <param name="targetCommand">The referenced command by the aliased command</param>
    /// <returns>True if an alias and false if not</returns>
    private bool TryGetAlias(string command, out string targetCommand)
    {
        // Create PowerShell runspace as a session state proxy to run GetCommand and check
        // if its an alias
        AliasInfo? pwshAliasInfo =
            _powershell.Runspace.SessionStateProxy.InvokeCommand.GetCommand(command, CommandTypes.Alias) as AliasInfo;

        // if its null then it is not an aliased command so just return false
        if(pwshAliasInfo is null)
        {
            targetCommand = String.Empty;
            return false;
        }

        // Set targetCommand to referenced command name
        targetCommand = pwshAliasInfo.ReferencedCommand.Name;
        return true;
    }
    #endregion IFeedbackProvider

    #region ICommandPredictor

    /// <summary>
    /// Gets a value indicating whether the predictor accepts a specific kind of feedback.
    /// </summary>
    /// <param name="client">Represents the client that initiates the call.</param>
    /// <param name="feedback">A specific type of feedback.</param>
    /// <returns>True or false, to indicate whether the specific feedback is accepted.</returns>
    public bool CanAcceptFeedback(PredictionClient client, PredictorFeedbackKind feedback)
    {
        return feedback switch
        {
            PredictorFeedbackKind.CommandLineAccepted => true,
            _ => false,
        };
    }

    /// <summary>
    /// Get the predictive suggestions. It indicates the start of a suggestion rendering session.
    /// </summary>
    /// <param name="client">Represents the client that initiates the call.</param>
    /// <param name="context">The <see cref="PredictionContext"/> object to be used for prediction.</param>
    /// <param name="cancellationToken">The cancellation token to cancel the prediction.</param>
    /// <returns>An instance of <see cref="SuggestionPackage"/>.</returns>
    public SuggestionPackage GetSuggestion(
        PredictionClient client,
        PredictionContext context,
        CancellationToken cancellationToken)
    {
        if (_candidates is not null)
        {
            string input = context.InputAst.Extent.Text;
            List<PredictiveSuggestion>? result = null;

            foreach (string c in _candidates)
            {
                if (c.StartsWith(input, StringComparison.OrdinalIgnoreCase))
                {
                    result ??= new List<PredictiveSuggestion>(_candidates.Count);
                    result.Add(new PredictiveSuggestion(c));
                }
            }

            if (result is not null)
            {
                return new SuggestionPackage(result);
            }
        }

        return default;
    }

    /// <summary>
    /// A command line was accepted to execute.
    /// The predictor can start processing early as needed with the latest history.
    /// </summary>
    /// <param name="client">Represents the client that initiates the call.</param>
    /// <param name="history">History command lines provided as references for prediction.</param>
    public void OnCommandLineAccepted(PredictionClient client, IReadOnlyList<string> history)
    {
        // Reset the candidate state once the command is accepted.
        _candidates = null;
    }

    #endregion;
}

public class Init : IModuleAssemblyInitializer, IModuleAssemblyCleanup
{
    private const string Id = "<ADD YOUR GUID HERE>";

    public void OnImport()
    {
        var feedback = new myFeedbackProvider(Id);
        SubsystemManager.RegisterSubsystem(SubsystemKind.FeedbackProvider, feedback);
        SubsystemManager.RegisterSubsystem(SubsystemKind.CommandPredictor, feedback);
    }

    public void OnRemove(PSModuleInfo psModuleInfo)
    {
        SubsystemManager.UnregisterSubsystem<ICommandPredictor>(new Guid(Id));
        SubsystemManager.UnregisterSubsystem<IFeedbackProvider>(new Guid(Id));
    }
}