Repository: PowerShell/AIShell
Pull request #351: Enhanced Model Management and SystemPrompt Integration in OllamaAgent
Author: login: cnupy
Created: 2025-03-05T19:54:09Z | Merged: 2025-04-25T17:47:34Z

# PR Summary

## Feature 1: Support for Multiple Models 
### Description
OllamaAgent can now handle and switch between multiple models seamlessly, listing available models dynamically via an API
### Implementation Details
*   Modified the agent to include a model selection mechanism that allows it to iterate through predefined models.
*   Introduced a `model` command to manage the switching between models based on available models.

## Feature 2: Integration of SystemPrompt 
### Description
It is possible to configure SystemPrompt with the selected model into OllamaAgent, providing users with a toolkit for task-oriented conversations.
### Implementation Details
*   Modified the agent to accommodate Ollama's SystemPrompt functionality.
*   Introduced a `system-prompt` command to manage the setting.

## Feature 3: Support for Predefined ModelConfigs sets 
### Description
OllamaAgent now supports the use of predefined model configurations, allowing users to easily switch between different models and system prompts based on specific requirements.
### Implementation Details
*   Introduced a `ModelConfig` Record to encapsulate data for each configuration.
*   Introduced a `config` command to manage the switching between configuration based on available predefined sets.

<!-- Anything that looks like this is a comment and can't be seen after the Pull Request is created. -->

<!-- Summarize your PR between here and the checklist. -->

## PR Context

<!-- Provide a little reasoning as to why this Pull Request helps and why you have opened it. -->
This Pull Request enhances the functionality of OllamaAgent by introducing improved model management. By allowing seamless switching between multiple models and supporting predefined configurations, it offers users greater flexibility. The addition of SystemPrompt functionality empowers users with a more task-oriented conversational toolkit, streamlining interactions. These updates not only enhance user experience but also improve the agent's adaptability to diverse requirements. This makes OllamaAgent a more versatile and powerful tool overall.


Diff:
diff --git a/shell/agents/AIShell.Ollama.Agent/Command.cs b/shell/agents/AIShell.Ollama.Agent/Command.cs
new file mode 100644
index 00000000..dbc66422
--- /dev/null
+++ b/shell/agents/AIShell.Ollama.Agent/Command.cs
@@ -0,0 +1,309 @@
+using System.CommandLine;
+using System.CommandLine.Completions;
+using System.Threading.Tasks;
+using AIShell.Abstraction;
+
+namespace AIShell.Ollama.Agent;
+
+internal sealed class PresetCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+    
+    public PresetCommand(OllamaAgent agent)
+        : base("preset", "Command for preset management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var use = new Command("use", "Specify a preset to use.");
+        var usePreset = new Argument<string>(
+            name: "Preset",
+            getDefaultValue: () => null,
+            description: "Name of a preset.").AddCompletions(PresetNameCompleter);
+        use.AddArgument(usePreset);
+        use.SetHandler(UsePresetAction, usePreset);
+
+        var list = new Command("list", "List a specific preset, or all configured presets.");
+        var listPreset = new Argument<string>(
+            name: "Preset",
+            getDefaultValue: () => null,
+            description: "Name of a preset.").AddCompletions(PresetNameCompleter);
+        list.AddArgument(listPreset);
+        list.SetHandler(ListPresetAction, listPreset);
+
+        AddCommand(list);
+        AddCommand(use);
+    }
+
+    private void ListPresetAction(string name)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+
+        try
+        {
+            if (string.IsNullOrEmpty(name))
+            {
+                settings.ListAllPresets(host);
+                return;
+            }
+
+            settings.ShowOnePreset(host, name);
+        }
+        catch (Exception ex)
+        {
+            string availablePresetNames = PresetNamesAsString();
+            host.WriteErrorLine($"{ex.Message} Available preset(s): {availablePresetNames}.");
+        }
+    }
+
+    private async Task UsePresetAction(string name)
+    {
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        var setting = _agnet.Settings;
+        var host = Shell.Host;
+
+        if (setting is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+
+        if (setting.Presets.Count is 0)
+        {
+            host.WriteErrorLine("There are no presets configured.");
+            return;
+        }
+
+        try
+        {
+            ModelConfig chosenPreset = (string.IsNullOrEmpty(name)
+                ? await host.PromptForSelectionAsync(
+                    title: "[orange1]Please select a [Blue]Preset[/] to use[/]:",
+                    choices: setting.Presets,
+                    converter: PresetName,
+                    CancellationToken.None)
+                : setting.Presets.FirstOrDefault(c => c.Name == name)) ?? throw new InvalidOperationException($"The preset '{name}' doesn't exist.");
+            await setting.UsePreset(host, chosenPreset);
+            host.MarkupLine($"Using the preset [green]{chosenPreset.Name}[/]:");
+        }
+        catch (Exception ex)
+        {
+            string availablePresetNames = PresetNamesAsString();
+            host.WriteErrorLine($"{ex.Message} Available presets: {availablePresetNames}.");
+        }
+    }
+
+    private static string PresetName(ModelConfig preset) => preset.Name.Any(Char.IsWhiteSpace) ? $"\"{preset.Name}\"" : preset.Name;
+    private IEnumerable<string> PresetNameCompleter(CompletionContext context) => _agnet.Settings?.Presets?.Select(PresetName) ?? [];
+    private string PresetNamesAsString() => string.Join(", ", PresetNameCompleter(null));
+}
+
+internal sealed class SystemPromptCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+
+    public SystemPromptCommand(OllamaAgent agent)
+        : base("system-prompt", "Command for system prompt management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var show = new Command("show", "Show the current system prompt.");
+        show.SetHandler(ShowSystemPromptAction);
+
+        var set = new Command("set", "Sets the system prompt.");
+        var systemPromptModel = new Argument<string>(
+            name: "System-Prompt",
+            getDefaultValue: () => null,
+            description: "The system prompt");
+        set.AddArgument(systemPromptModel);
+        set.SetHandler(SetSystemPromptAction, systemPromptModel);
+
+        AddCommand(show);
+        AddCommand(set);
+    }
+
+    private void ShowSystemPromptAction()
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+
+        try
+        {
+            settings.ShowSystemPrompt(host);
+        }
+        catch (Exception ex)
+        {
+            host.WriteErrorLine(ex.Message);
+        }
+    }
+
+    private void SetSystemPromptAction(string prompt)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+        _agnet.ResetContext();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+
+        try
+        {
+            settings.SetSystemPrompt(host, prompt);
+        }
+        catch (Exception ex)
+        {
+            host.WriteErrorLine(ex.Message);
+        }
+    }
+}
+
+internal sealed class ModelCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+
+    public ModelCommand(OllamaAgent agent)
+        : base("model", "Command for model management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var use = new Command("use", "Specify a model to use, or choose one from the available models.");
+        var useModel = new Argument<string>(
+            name: "Model",
+            getDefaultValue: () => null,
+            description: "Name of a model.").AddCompletions(ModelNameCompleter);
+        use.AddArgument(useModel);
+        use.SetHandler(UseModelAction, useModel);
+
+        var list = new Command("list", "List a specific model, or all available models.");
+        var listModel = new Argument<string>(
+            name: "Model",
+            getDefaultValue: () => null,
+            description: "Name of a model.").AddCompletions(ModelNameCompleter);
+        list.AddArgument(listModel);
+        list.SetHandler(ListModelAction, listModel);
+
+        AddCommand(list);
+        AddCommand(use);
+    }
+
+    private async Task ListModelAction(string name)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+        try
+        {
+            if (string.IsNullOrEmpty(name))
+            {
+                await settings.ListAllModels(host);
+                return;
+            }
+
+            await settings.ShowOneModel(host, name);
+        }
+        catch (Exception ex)
+        {
+            host.WriteErrorLine(ex.Message);
+        }
+    }
+
+    private async Task UseModelAction(string name)
+    {
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        var settings = _agnet.Settings;
+        var host = Shell.Host;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+
+        try
+        {
+            bool success = await settings.PerformSelfcheck(host, checkEndpointOnly: true);
+            if (!success)
+            {
+                return;
+            }
+
+            var allModels = await settings.GetAllModels();
+            if (allModels.Count is 0)
+            {
+                host.WriteErrorLine($"No models found from '{settings.Endpoint}'.");
+                return;
+            }
+
+            if (string.IsNullOrEmpty(name))
+            {
+                name = await host.PromptForSelectionAsync(
+                    title: "[orange1]Please select a [Blue]Model[/] to use[/]:",
+                    choices: allModels,
+                    CancellationToken.None);
+            }
+
+            await settings.UseModel(host, name);
+            host.MarkupLine($"Using the model [green]{name}[/]");
+        }
+        catch (Exception ex)
+        {
+            host.WriteErrorLine(ex.Message);
+        }
+    }
+
+    private IEnumerable<string> ModelNameCompleter(CompletionContext context)
+    {
+        try
+        {
+            // Model retrieval may throw.
+            var results = _agnet.Settings?.GetAllModels().Result;
+            if (results is not null)
+            {
+                return results;
+            }
+        }
+        catch (Exception) { }
+
+        return [];
+    }
+}
diff --git a/shell/agents/AIShell.Ollama.Agent/OllamaAgent.cs b/shell/agents/AIShell.Ollama.Agent/OllamaAgent.cs
index 480cf383..60da8520 100644
--- a/shell/agents/AIShell.Ollama.Agent/OllamaAgent.cs
+++ b/shell/agents/AIShell.Ollama.Agent/OllamaAgent.cs
@@ -1,14 +1,12 @@
-using System.Diagnostics;
 using System.Text;
 using System.Text.Json;
-using System.Text.RegularExpressions;
 using AIShell.Abstraction;
 using OllamaSharp;
 using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
-public sealed partial class OllamaAgent : ILLMAgent
+public sealed class OllamaAgent : ILLMAgent
 {
     private bool _reloadSettings;
     private bool _isDisposed;
@@ -110,7 +108,7 @@ public void Initialize(AgentConfig config)
     /// <summary>
     /// Get commands that an agent can register to the shell when being loaded.
     /// </summary>
-    public IEnumerable<CommandBase> GetCommands() => null;
+    public IEnumerable<CommandBase> GetCommands() => [new PresetCommand(this), new ModelCommand(this), new SystemPromptCommand(this)];
 
     /// <summary>
     /// Gets the path to the setting file of the agent.
@@ -148,6 +146,11 @@ public Task RefreshChatAsync(IShell shell, bool force)
         return Task.CompletedTask;
     }
 
+    public void ResetContext()
+    {
+        _request.Context = null;
+    }
+
     /// <summary>
     /// Main chat function that takes the users input and passes it to the LLM and renders it.
     /// </summary>
@@ -165,17 +168,24 @@ public async Task<bool> ChatAsync(string input, IShell shell)
         // Reload the setting file if needed.
         ReloadSettings();
 
-        if (IsLocalHost().IsMatch(_client.Uri.Host) && Process.GetProcessesByName("ollama").Length is 0)
+        bool success = await _settings.PerformSelfcheck(host);
+        if (!success)
         {
-            host.WriteErrorLine("Please be sure the Ollama is installed and server is running. Check all the prerequisites in the README of this agent are met.");
             return false;
         }
 
+        ModelConfig config = _settings.RunningConfig;
+
         // Prepare request
         _request.Prompt = input;
-        _request.Model = _settings.Model;
+        _request.Model = config.ModelName;
         _request.Stream = _settings.Stream;
 
+        if (!string.IsNullOrWhiteSpace(config.SystemPrompt))
+        {
+            _request.System = config.SystemPrompt;
+        }
+
         try
         {
             if (_request.Stream)
@@ -238,15 +248,15 @@ public async Task<bool> ChatAsync(string input, IShell shell)
         catch (HttpRequestException e)
         {
             host.WriteErrorLine($"{e.Message}");
-            host.WriteErrorLine($"Ollama model:    \"{_settings.Model}\"");
-            host.WriteErrorLine($"Ollama endpoint: \"{_settings.Endpoint}\"");
-            host.WriteErrorLine($"Ollama settings: \"{SettingFile}\"");
+            host.WriteErrorLine($"Ollama active model: \"{config.ModelName}\"");
+            host.WriteErrorLine($"Ollama endpoint:     \"{_settings.Endpoint}\"");
+            host.WriteErrorLine($"Ollama settings:     \"{SettingFile}\"");
         }
 
         return true;
     }
 
-    private void ReloadSettings()
+    internal void ReloadSettings()
     {
         if (_reloadSettings)
         {
@@ -308,22 +318,24 @@ private void NewExampleSettingFile()
             // 1. Install Ollama: `winget install Ollama.Ollama`
             // 2. Start Ollama API server: `ollama serve`
             // 3. Install Ollama model: `ollama pull phi3`
-
-            // Declare Ollama model
-            "Model": "phi3",
+            
+            // Declare predefined model configurations
+            "Presets": [
+                {
+                    "Name": "PowerShell Expert",
+                    "Description": "A ollama agent with expertise in PowerShell scripting and command line utilities.",
+                    "ModelName": "phi3",
+                    "SystemPrompt": "1. You are a helpful and friendly assistant with expertise in PowerShell scripting and command line.\n2. Assume user is using the operating system `Windows 11` unless otherwise specified.\n3. Use the `code block` syntax in markdown to encapsulate any part in responses that is code, YAML, JSON or XML, but not table.\n4. When encapsulating command line code, use '```powershell' if it's PowerShell command; use '```sh' if it's non-PowerShell CLI command.\n5. When generating CLI commands, never ever break a command into multiple lines. Instead, always list all parameters and arguments of the command on the same line.\n6. Please keep the response concise but to the point. Do not overexplain."
+                }
+            ],
             // Declare Ollama endpoint
             "Endpoint": "http://localhost:11434",
             // Enable Ollama streaming
-            "Stream": false
+            "Stream": false,
+            // Specify the default preset to use
+            "DefaultPreset": "PowerShell Expert"
         }
         """;
         File.WriteAllText(SettingFile, SampleContent, Encoding.UTF8);
     }
-
-    /// <summary>
-    /// Defines a generated regular expression to match localhost addresses
-    /// "localhost", "127.0.0.1" and "[::1]" with case-insensitivity.
-    /// </summary>
-    [GeneratedRegex("^(localhost|127\\.0\\.0\\.1|\\[::1\\])$", RegexOptions.IgnoreCase)]
-    internal partial Regex IsLocalHost();
 }
diff --git a/shell/agents/AIShell.Ollama.Agent/README.md b/shell/agents/AIShell.Ollama.Agent/README.md
index bd8e91e1..6e06f120 100644
--- a/shell/agents/AIShell.Ollama.Agent/README.md
+++ b/shell/agents/AIShell.Ollama.Agent/README.md
@@ -20,11 +20,21 @@ To configure the agent, run `/agent config ollama` to open up the setting file i
     // 2. Start Ollama API server: `ollama serve`
     // 3. Install Ollama model: `ollama pull phi3`
 
-    // Declare Ollama model
-    "Model": "phi3",
+    // Declare predefined model configurations
+    "Presets": [
+        {
+            "Name": "PowerShell Expert",
+            "Description": "A ollama agent with expertise in PowerShell scripting and command line utilities.",
+            "ModelName": "phi3",
+            "SystemPrompt": "You are a helpful and friendly assistant with expertise in PowerShell scripting and command line."
+        }
+    ],
+
     // Declare Ollama endpoint
     "Endpoint": "http://localhost:11434",
     // Enable Ollama streaming
-    "Stream": false
+    "Stream": false,
+    // Specify the default preset to use
+    "DefaultPreset": "PowerShell Expert"
 }
 ```
diff --git a/shell/agents/AIShell.Ollama.Agent/Settings.cs b/shell/agents/AIShell.Ollama.Agent/Settings.cs
index 11ebd8de..8b1db2bd 100644
--- a/shell/agents/AIShell.Ollama.Agent/Settings.cs
+++ b/shell/agents/AIShell.Ollama.Agent/Settings.cs
@@ -1,40 +1,278 @@
-﻿using System.Text.Json;
+﻿using System.Diagnostics;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
-internal class Settings
+internal partial class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private bool _runningConfigChecked = false;
+    private bool? _isRunningLocalHost = null;
+    private List<string> _availableModels = [];
+    public List<ModelConfig> Presets { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Presets = configData.Presets ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        if (string.IsNullOrEmpty(configData.DefaultPreset))
+        {
+            RunningConfig = Presets.Count > 0
+                ? Presets[0] with { }  /* No default preset - use the first one defined in Presets */
+                : new ModelConfig(name: nameof(RunningConfig), modelName: ""); /* No presets are defined - use empty */
+        }
+        else
+        {
+            // Ensure the default configuration is available in the list of configurations.
+            var first = Presets.FirstOrDefault(c => c.Name == configData.DefaultPreset)
+                ?? throw new InvalidOperationException($"The selected default preset '{configData.DefaultPreset}' doesn't exist.");
+            // Use the default config
+            RunningConfig = first with { };
+        }
+    }
+
+    /// <summary>
+    /// Retrieve available models from the Ollama endpoint.
+    /// </summary>
+    /// <param name="host">Used for writing error to host when it's a local endpoint but the Ollama server is not started. When the value is null, the endpoint check will be skipped.</param>
+    /// <param name="cancellationToken">Used for cancel the operation.</param>
+    /// <returns></returns>
+    private async Task<bool> EnsureModelsInitialized(IHost host, CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        {
+            return true;
+        }
+
+        // The endpoint check is supposed to be interactive and can be skipped in some cases, such as when
+        // the `PerformSelfcheck` method was already called right before entering this method.
+        // So, we will simply skip the endpoint check when the passed-in host is null. If there's anything
+        // wrong with the endpoint, the subsequent calls to retrieve models will fail and throw anyway.
+        if (host is not null)
+        {
+            bool success = await PerformSelfcheck(host, checkEndpointOnly: true);
+            if (!success)
+            {
+                return false;
+            }
+        }
+
+        using OllamaApiClient client = new(Endpoint);
+        var models = await client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+        _availableModels = [.. models.Select(m => m.Name)];
+        _initialized = true;
+        return true;
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(IHost host = null, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
+        {
+            return _availableModels;
+        }
+
+        return [];
+    }
+
+    internal void EnsureModelNameIsValid(string name)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+
+        if (!_availableModels.Contains(name.AddLatestTagIfNecessery()))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist. The available models are: [{string.Join(", ", _availableModels)}].");
+        }
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [new CustomElement<string>(label: "System prompt", s => s)];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+
+    private static List<IRenderElement<string>> GetRenderModelElements(Func<string, bool> isActive) => [
+        new CustomElement<string>(label: "Name", m => m),
+        new CustomElement<string>(label: "Active", m => isActive(m) ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
+        {
+            EnsureModelNameIsValid(name);
+            RunningConfig = RunningConfig with { ModelName = name };
+            _runningConfigChecked = true;
+        }
+    }
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
+        {
+            host.RenderTable(_availableModels, GetRenderModelElements(m => m == RunningConfig.ModelName.AddLatestTagIfNecessery()));
+        }
     }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
+        {
+            EnsureModelNameIsValid(name);
+            host.RenderList(name, GetRenderModelElements(m => m == RunningConfig.ModelName.AddLatestTagIfNecessery()));
+        }
+    }
+
+    internal async Task UsePreset(IHost host, ModelConfig preset, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
+        {
+            EnsureModelNameIsValid(preset.ModelName);
+            RunningConfig = preset with { };
+            _runningConfigChecked = true;
+        }
+    }
+
+    internal void ListAllPresets(IHost host)
+    {
+        host.RenderTable(
+            Presets,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOnePreset(IHost host, string name)
+    {
+        var preset = Presets.FirstOrDefault(c => c.Name == name);
+        if (preset is null)
+        {
+            host.WriteErrorLine($"The preset '{name}' doesn't exist.");
+            return;
+        }
+
+        host.RenderList(
+            preset,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task<bool> PerformSelfcheck(IHost host, bool checkEndpointOnly = false)
+    {
+        _isRunningLocalHost ??= IsLocalHost().IsMatch(new Uri(Endpoint).Host);
+
+        if (_isRunningLocalHost is true && Process.GetProcessesByName("ollama").Length is 0)
+        {
+            host.WriteErrorLine("Please be sure the Ollama is installed and server is running. Check all the prerequisites in the README of this agent are met.");
+            return false;
+        }
+
+        if (!checkEndpointOnly && !_runningConfigChecked)
+        {
+            // Skip the endpoint check in 'EnsureModelsInitialized' as we already did it.
+            await EnsureModelsInitialized(host: null).ConfigureAwait(false);
+            if (string.IsNullOrEmpty(RunningConfig.ModelName))
+            {
+                // There is no model set, so use the first one available.
+                if (_availableModels.Count is 0)
+                {
+                    host.WriteErrorLine($"No models are available to use from '{Endpoint}'.");
+                    return false;
+                }
+
+                RunningConfig = RunningConfig with { ModelName = _availableModels.First() };
+                host.MarkupLine($"No Ollama model is configured. Using the first available model [green]'{RunningConfig.ModelName}'[/].");
+            }
+            else
+            {
+                try
+                {
+                    EnsureModelNameIsValid(RunningConfig.ModelName);
+                }
+                catch (InvalidOperationException e)
+                {
+                    host.WriteErrorLine(e.Message);
+                    return false;
+                }
+            }
+
+            _runningConfigChecked = true;
+        }
+
+        return true;
+    }
+
+    /// <summary>
+    /// Defines a generated regular expression to match localhost addresses
+    /// "localhost", "127.0.0.1" and "[::1]" with case-insensitivity.
+    /// </summary>
+    [GeneratedRegex("^(localhost|127\\.0\\.0\\.1|\\[::1\\])$", RegexOptions.IgnoreCase)]
+    internal partial Regex IsLocalHost();
 }
 
-internal class ConfigData
+/// <summary>
+/// Represents a configuration for an Ollama model.
+/// </summary>
+internal record ModelConfig
 {
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    [JsonRequired]
+    public string Name { get; init; }
+
+    [JsonRequired]
+    public string ModelName { get; init; }
+
+    public string SystemPrompt { get; init; } = string.Empty;
+
+    public string Description { get; init; } = string.Empty;
+    
+    /// <summary>
+    /// Initializes a new instance of the <see cref="ModelConfig"/> class with the specified parameters.
+    /// </summary>
+    /// <param name="name">The name of the model configuration.</param>
+    /// <param name="modelName">The name of the model to be used.</param>
+    /// <param name="systemPrompt">An optional system prompt to guide the model's behavior. Defaults to an empty string.</param>
+    /// <param name="description">An optional description of the model configuration. Defaults to an empty string.</param>
+    public ModelConfig(string name, string modelName, string systemPrompt = "", string description = "")
+    {
+        Name = name;
+        ModelName = modelName;
+        SystemPrompt = systemPrompt;
+        Description = description;
+    }
 }
 
+/// <summary>
+/// Represents the configuration data for the AI Shell Ollama Agent.
+/// </summary>
+/// <param name="Presets">Optional. A list of predefined model configurations.</param>
+/// <param name="Endpoint">Optional. The endpoint URL for the agent. Defaults to "http://localhost:11434"
+/// <param name="Stream">Optional. Indicates whether streaming is enabled. Defaults to <c>false</c>.</param>
+/// <param name="DefaultPreset">Optional. Specifies the default preset name. If not provided, the first available preset will be used.</param>
+internal record ConfigData(List<ModelConfig> Presets, string Endpoint = "http://localhost:11434", bool Stream = false, string DefaultPreset = "");
+
 /// <summary>
 /// Use source generation to serialize and deserialize the setting file.
 /// Both metadata-based and serialization-optimization modes are used to gain the best performance.
@@ -47,3 +285,9 @@ internal class ConfigData
     UseStringEnumConverter = true)]
 [JsonSerializable(typeof(ConfigData))]
 internal partial class SourceGenerationContext : JsonSerializerContext { }
+
+static class TagExtensions
+{
+    public static string AddLatestTagIfNecessery(this string model) =>
+        model.Contains(':') ? model : string.Concat(model, ":latest");
+}


Reviews:
user: login: kborowinski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T16:50:27Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T10:46:58Z
user: login: daxian-dbw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-11T00:12:12Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:19:27Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:21:41Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:22:15Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:24:53Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:26:13Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:27:25Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:28:05Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:28:17Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:30:41Z
user: login: daxian-dbw
body: Sorry for the delay!
The ollama agent from the main branch works for me now (I have no idea why it didn't weeks before. I guess something specific to my system). I left some comments, but haven't finished reviewing the `Command.cs` file. Will finish it tomorrow.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T04:50:24Z
user: login: daxian-dbw
body: Done with my 2nd pass of review :)
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T18:19:56Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T05:55:30Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T05:56:26Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T06:07:49Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T06:56:15Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T06:58:24Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T07:02:05Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T07:24:29Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T08:34:51Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T08:52:45Z
user: login: daxian-dbw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T22:58:39Z
user: login: daxian-dbw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T00:26:46Z
user: login: daxian-dbw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T00:28:38Z
user: login: daxian-dbw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T00:34:06Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T16:50:29Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T16:50:52Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T16:53:27Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T16:55:04Z
user: login: cnupy
body: Thanks for the effort. Everything looks good to me! I just have a small suggestion to make the code logic easier to follow.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T07:31:54Z
user: login: daxian-dbw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T16:59:04Z
user: login: cnupy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T11:59:38Z
user: login: daxian-dbw
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T16:51:36Z
user: login: StevenBucher98
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-25T16:55:31Z

Review comments:
diff_hunk: @@ -238,15 +250,23 @@ public async Task<bool> ChatAsync(string input, IShell shell)
         catch (HttpRequestException e)
         {
             host.WriteErrorLine($"{e.Message}");
-            host.WriteErrorLine($"Ollama model:    \"{_settings.Model}\"");
+            host.WriteErrorLine($"Ollama active model:    \"{activeModel.Name}\"");
path: shell/agents/AIShell.Ollama.Agent/OllamaAgent.cs
user: login: kborowinski
body: @cnupy 
This is just cosmetics, but I would change the indentation as follows:
```powershell
host.WriteErrorLine($"Ollama active model: \"{activeModel.Name}\"");
host.WriteErrorLine($"Ollama endpoint:     \"{_settings.Endpoint}\"");
host.WriteErrorLine($"Ollama settings:     \"{SettingFile}\"");
```

created_at: 2025-03-07T16:50:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -238,15 +250,23 @@ public async Task<bool> ChatAsync(string input, IShell shell)
         catch (HttpRequestException e)
         {
             host.WriteErrorLine($"{e.Message}");
-            host.WriteErrorLine($"Ollama model:    \"{_settings.Model}\"");
+            host.WriteErrorLine($"Ollama active model:    \"{activeModel.Name}\"");
path: shell/agents/AIShell.Ollama.Agent/OllamaAgent.cs
user: login: cnupy
body: Thanks! Fixed!
created_at: 2025-03-09T10:46:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: This comment doesn't apply here, please update.
created_at: 2025-03-10T22:13:27Z
author_association: MEMBER
diff_hunk: @@ -308,13 +328,20 @@ private void NewExampleSettingFile()
             // 1. Install Ollama: `winget install Ollama.Ollama`
             // 2. Start Ollama API server: `ollama serve`
             // 3. Install Ollama model: `ollama pull phi3`
-
-            // Declare Ollama model
-            "Model": "phi3",
+            "Configs": [
+                {
+                    "Name": "PowerShell Expert",
+                    "Description": "A ollama agent with expertise in PowerShell scripting and command line utilities.",
+                    "ModelName": "phi3",
+                    "SystemPrompt": "1. You are a helpful and friendly assistant with expertise in PowerShell scripting and command line.\n2. Assume user is using the operating system `Windows 11` unless otherwise specified.\n3. Use the `code block` syntax in markdown to encapsulate any part in responses that is code, YAML, JSON or XML, but not table.\n4. When encapsulating command line code, use '```powershell' if it's PowerShell command; use '```sh' if it's non-PowerShell CLI command.\n5. When generating CLI commands, never ever break a command into multiple lines. Instead, always list all parameters and arguments of the command on the same line.\n6. Please keep the response concise but to the point. Do not overexplain."
+                }
+            ],
             // Declare Ollama endpoint
             "Endpoint": "http://localhost:11434",
             // Enable Ollama streaming
-            "Stream": false
+            "Stream": false,
+            // Specify the default model to use
+            "DefaultConfig": "PowerShell Expert",
path: shell/agents/AIShell.Ollama.Agent/OllamaAgent.cs
user: login: daxian-dbw
body: ```suggestion
            "DefaultConfig": "PowerShell Expert"
```
created_at: 2025-03-10T22:13:58Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: I think we should handle invalid configuration in setting file. For example,
1. `DefaultConfig` specified, but `Configs` is `null`, empty array, or doesn't contain the specified `DefaultConfig`. 
2. The item defined in `Configs` doesn't contain `ModelName`, or `Name`, or `Description`

And please have XML comments to the `ConfigData` record below to make it clear what fields are must-to-have and what are not.
created_at: 2025-03-10T22:27:00Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: It looks if `Configs` is empty or not specified, you will use the first model from the list. In that case, you should write out a message about it to user, so that the user knows what happens and which model it being used.
created_at: 2025-03-10T22:28:48Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
+            ]);
+    }
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        this.RunningConfig = this.RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(
+            this.RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "New system prompt is", str => str)
+            ]);
+    }
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(
+            [.. _models],
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(
+            model,
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var config = this.Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m.ToRunnigConfig() == this.RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task UseConfg(IHost host, ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        this.RunningConfig = config.ToRunnigConfig();
+        await UseModel(this.RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            [.. this.Configs],
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m.ToRunnigConfig() == this.RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal async Task<OllamaModel> GetActiveModel(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return _activeModel;
+    }    
 }
 
-internal class ConfigData
+internal record OllamaModel(string Name);
+
+internal record ModelConfig(string Name, string SystemPrompt, string ModelName, string Description, bool ResetContext);
+
+internal record ConfigData(List<ModelConfig> Configs, string Endpoint, bool Stream, string DefaultConfig);
+
+internal record RunningConfig(string Name = "", string ModelName = "", string SystemPrompt = "", bool ResetContext = false);
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: Why do you need this record? Why not just use `ModelConfig`?
created_at: 2025-03-10T22:30:02Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: Nitpick: how about change to the following to reduce nesting?
```
if (_initialized)
{
    return;
}

OllamaApiClient _client = null;
try
{
    ...
```
created_at: 2025-03-10T22:31:40Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: This call is not needed as `GetModelByName` will call it.
created_at: 2025-03-10T22:34:46Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: ```suggestion
                new CustomElement<string>(label: "System prompt:", str => str)
```
created_at: 2025-03-10T22:38:44Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
+            ]);
+    }
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        this.RunningConfig = this.RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(
+            this.RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "New system prompt is", str => str)
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: ```suggestion
                new CustomElement<string>(label: "New system prompt:", str => str)
```
created_at: 2025-03-10T23:03:49Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
+            ]);
+    }
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        this.RunningConfig = this.RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(
+            this.RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "New system prompt is", str => str)
+            ]);
+    }
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(
+            [.. _models],
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: Why not declare `_models` as `IList<>`? Any reason it should be using `ICollection<>`?
created_at: 2025-03-10T23:05:15Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
+            ]);
+    }
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        this.RunningConfig = this.RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(
+            this.RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "New system prompt is", str => str)
+            ]);
+    }
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(
+            [.. _models],
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(
+            model,
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var config = this.Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m.ToRunnigConfig() == this.RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task UseConfg(IHost host, ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        this.RunningConfig = config.ToRunnigConfig();
+        await UseModel(this.RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            [.. this.Configs],
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m.ToRunnigConfig() == this.RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal async Task<OllamaModel> GetActiveModel(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return _activeModel;
+    }    
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: ```suggestion
    }
```
created_at: 2025-03-10T23:08:20Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
+            ]);
+    }
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        this.RunningConfig = this.RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(
+            this.RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "New system prompt is", str => str)
+            ]);
+    }
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(
+            [.. _models],
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(
+            model,
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name, CancellationToken cancellationToken = default)
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: The parameter `cancellationToken` is not used, can be removed.
created_at: 2025-03-10T23:16:45Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
+            ]);
+    }
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        this.RunningConfig = this.RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(
+            this.RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "New system prompt is", str => str)
+            ]);
+    }
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(
+            [.. _models],
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(
+            model,
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var config = this.Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m.ToRunnigConfig() == this.RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task UseConfg(IHost host, ModelConfig config, CancellationToken cancellationToken = default)
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: The parameter `host` can be removed.
created_at: 2025-03-10T23:18:42Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
+            ]);
+    }
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        this.RunningConfig = this.RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(
+            this.RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "New system prompt is", str => str)
+            ]);
+    }
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(
+            [.. _models],
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(
+            model,
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var config = this.Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m.ToRunnigConfig() == this.RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task UseConfg(IHost host, ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        this.RunningConfig = config.ToRunnigConfig();
+        await UseModel(this.RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            [.. this.Configs],
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: Same here, any reason why we cannot use `IList<>` for `Configs`?
created_at: 2025-03-10T23:19:22Z
author_association: MEMBER
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Fixed.
created_at: 2025-03-12T16:19:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Thanks for the suggestion! I've refactored the code, and it's much cleaner now.
created_at: 2025-03-12T16:21:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Thanks!
created_at: 2025-03-12T16:22:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Fixed. The semicolon is already included when using CustomElement render element.
created_at: 2025-03-12T16:24:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
+            ]);
+    }
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        this.RunningConfig = this.RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(
+            this.RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "New system prompt is", str => str)
+            ]);
+    }
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(
+            [.. _models],
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: No particular reason, but nice suggestion. Fixed.
created_at: 2025-03-12T16:26:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Nice catch!
created_at: 2025-03-12T16:27:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
+            ]);
+    }
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        this.RunningConfig = this.RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(
+            this.RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "New system prompt is", str => str)
+            ]);
+    }
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(
+            [.. _models],
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(
+            model,
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var config = this.Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m.ToRunnigConfig() == this.RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task UseConfg(IHost host, ModelConfig config, CancellationToken cancellationToken = default)
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Fixed!
created_at: 2025-03-12T16:28:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
+                }
+                else
+                {
+                    _activeModel = _models.FirstOrDefault(m => m.Name == this.RunningConfig.ModelName);
+                    if (_activeModel == null)
+                    {
+                        string message = $"The Model '{this.RunningConfig.ModelName}' specified as \"Default\" in the configuration doesn't exist.";
+                        throw new InvalidOperationException(message);
+                    }
+                }
+                _initialized = true;
+            }
+            finally
+            {
+                if (_client is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+        }
+    }
+
+    internal async Task<ICollection<OllamaModel>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return this._models;
+    }
+
+    internal async Task<OllamaModel> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        var model = _models.FirstOrDefault(m => m.Name == name);
+
+        if (model is not null)
+        {
+            return model;
+        }
+
+        throw new InvalidOperationException($"A model with the name '{name}' doesn't exist.");
+    }
+
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        _activeModel = await GetModelByName(name, cancellationToken);
+    }
+
+    internal void UseModel(OllamaModel model)
+    {
+        _activeModel = model;
+    }
+
+    internal void ShowSystemPrompt(IHost host)
+    {
+        host.RenderList(
+            RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "System prompt is", str => str)
+            ]);
+    }
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        this.RunningConfig = this.RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(
+            this.RunningConfig.SystemPrompt,
+            [
+                new CustomElement<string>(label: "New system prompt is", str => str)
+            ]);
+    }
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(
+            [.. _models],
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(
+            model,
+            [
+                new PropertyElement<OllamaModel>(nameof(OllamaModel.Name)),
+                new CustomElement<OllamaModel>(label: "Active", m => m.Name == _activeModel?.Name ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var config = this.Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m.ToRunnigConfig() == this.RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task UseConfg(IHost host, ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        this.RunningConfig = config.ToRunnigConfig();
+        await UseModel(this.RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            [.. this.Configs],
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Fixed.
created_at: 2025-03-12T16:28:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,38 +1,219 @@
-﻿using System.Text.Json;
+﻿using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Linq;
+using AIShell.Abstraction;
+using OllamaSharp;
+using OllamaSharp.Models;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private OllamaModel _activeModel;
+    private ICollection<OllamaModel> _models = [];
+    private bool _initialized = false;
+
+    public ICollection<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public RunningConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
             throw new ArgumentException("\"Endpoint\" key is missing.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs;
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        RunningConfig = new RunningConfig();
+
+        if (Configs is not null)
+        {
+            var modelConfig = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig);
+            if (modelConfig is not null)
+            {
+                RunningConfig = modelConfig.ToRunnigConfig();
+            }
+        }
     }
+
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (!_initialized)
+        {
+            OllamaApiClient _client = null;
+            try
+            {
+                _client = new OllamaApiClient(this.Endpoint);
+                var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+                this._models = models.Select(m => new OllamaModel(Name: m.Name)).ToList().AsReadOnly();
+
+                if (this._models.Count == 0)
+                {
+                    throw new InvalidOperationException("No model is available.");
+                }
+
+                if (string.IsNullOrEmpty(this.RunningConfig.ModelName))
+                {
+                    // Active GPT not specified, but there is only one GPT defined, then use it by default.
+                    _activeModel = _models.First();
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: This case is now handled a bit differently after I refactored the Settings class. Thanks again for the suggestion!
created_at: 2025-03-12T16:30:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: When `defaultConfig` is specified and `Configs` is not empty, we will be running this enumerator twice (here and `Configs.First(...)` below).
created_at: 2025-03-28T23:34:59Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: Given the comment about the duplicate enumeration, how about changing this section the following?
```suggestion
        if (string.IsNullOrEmpty(configData.DefaultConfig))
        {
            RunningConfig = Configs.Count > 0
                ? Configs[0] with { }  /* No default config - use the first one defined in Configs */
                : new ModelConfig(nameof(RunningConfig), ModelName: ""); /* No config available - use empty */
        }
        else
        {
            // Ensure the default configuration is available in the list of configurations.
            var first = Configs.FirstOrDefault(c => c.Name == configData.DefaultConfig)
                ?? throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
            // Use the default config
            RunningConfig = first with { };
        }
```
created_at: 2025-03-28T23:38:28Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: How about just use a private variable instead? A property doesn't seem to be needed.
created_at: 2025-03-28T23:53:57Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: Local variable should not have the `_` prefix.
```suggestion
        OllamaApiClient client = null;
```
created_at: 2025-03-28T23:54:49Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: What does the `m.Name` look like? I mean for modules shown in the `ollama list` below, do a user have to specify `phi3:latest` in the configuration instead of just `phi3`?

```
PS:9> ollama.exe list
NAME                ID              SIZE      MODIFIED
deepseek-r1:1.5b    a42b25d8c10a    1.1 GB    2 weeks ago
codellama:latest    8fdf8f752f6e    3.8 GB    3 months ago
phi3:latest         a2c89ceaed85    2.3 GB    10 months ago
```
created_at: 2025-03-29T00:00:20Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: `OllamaApiClient` implements `IDisposable` interface, so maybe you can avoid this try/finally and simply use `using OllamaApiClient = new(Endpoint)` above?
created_at: 2025-03-29T17:47:45Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: It's better to list the available models in the error message, like:
```
$"A model with the name '{name}' doesn't exist. The available models are: xxx, xxx, xxx, xxx."
```
created_at: 2025-03-29T17:55:14Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: ```suggestion
    {
```
created_at: 2025-03-29T18:21:08Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+    
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: ```suggestion
    }

```
created_at: 2025-03-29T18:21:56Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+    
+    private static List<IRenderElement<string>> GetRenderModelElements(string currentModelName) => [
+        new CustomElement<string>(label: "Model Name", m => m),
+        new CustomElement<string>(label: "Active", m => m == currentModelName ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default) =>
+        RunningConfig = RunningConfig with { ModelName = await GetModelByName(name, cancellationToken).ConfigureAwait(false) };
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(AvailableModels, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(model, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task UseConfg(ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        RunningConfig = config with { };
+        await UseModel(RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            Configs,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name)
+    {
+        var config = Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task<string> GetActiveModel(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (string.IsNullOrEmpty(RunningConfig.ModelName))
+        {
+            // There is no model set, so use the first one available.
+            RunningConfig = RunningConfig with { ModelName = AvailableModels.First() };
+            host.MarkupLine($"No Ollama model is configured. Using the first available model [green]'{RunningConfig.ModelName}'.");
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: ```suggestion
            host.MarkupLine($"No Ollama model is configured. Using the first available model [green]'{RunningConfig.ModelName}'[/].");
```
created_at: 2025-03-29T18:40:27Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+    
+    private static List<IRenderElement<string>> GetRenderModelElements(string currentModelName) => [
+        new CustomElement<string>(label: "Model Name", m => m),
+        new CustomElement<string>(label: "Active", m => m == currentModelName ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default) =>
+        RunningConfig = RunningConfig with { ModelName = await GetModelByName(name, cancellationToken).ConfigureAwait(false) };
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(AvailableModels, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(model, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task UseConfg(ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        RunningConfig = config with { };
+        await UseModel(RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            Configs,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name)
+    {
+        var config = Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task<string> GetActiveModel(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (string.IsNullOrEmpty(RunningConfig.ModelName))
+        {
+            // There is no model set, so use the first one available.
+            RunningConfig = RunningConfig with { ModelName = AvailableModels.First() };
+            host.MarkupLine($"No Ollama model is configured. Using the first available model [green]'{RunningConfig.ModelName}'.");
+        }
+        else
+        {
+            if (!AvailableModels.Contains(RunningConfig.ModelName))
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: Based on `UseConfg` method and the constructor of `Settings`, `RunningConfig.ModuleName` should've been verified when setting `RunningConfig`, right? If so, then we don't need to check again here.
created_at: 2025-03-29T18:44:11Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+    
+    private static List<IRenderElement<string>> GetRenderModelElements(string currentModelName) => [
+        new CustomElement<string>(label: "Model Name", m => m),
+        new CustomElement<string>(label: "Active", m => m == currentModelName ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default) =>
+        RunningConfig = RunningConfig with { ModelName = await GetModelByName(name, cancellationToken).ConfigureAwait(false) };
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(AvailableModels, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(model, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task UseConfg(ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        RunningConfig = config with { };
+        await UseModel(RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: Hmm, `UseModel` will clone another `ModelConfig` and assign to `RunningConfig`, which seems unnecessary for this operation. Can we simply verify if `AvailableModels` contains `RunningConfig.ModelName` and throw exception if not?
created_at: 2025-03-29T18:46:20Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+    
+    private static List<IRenderElement<string>> GetRenderModelElements(string currentModelName) => [
+        new CustomElement<string>(label: "Model Name", m => m),
+        new CustomElement<string>(label: "Active", m => m == currentModelName ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default) =>
+        RunningConfig = RunningConfig with { ModelName = await GetModelByName(name, cancellationToken).ConfigureAwait(false) };
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(AvailableModels, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(model, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task UseConfg(ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        RunningConfig = config with { };
+        await UseModel(RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            Configs,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name)
+    {
+        var config = Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task<string> GetActiveModel(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (string.IsNullOrEmpty(RunningConfig.ModelName))
+        {
+            // There is no model set, so use the first one available.
+            RunningConfig = RunningConfig with { ModelName = AvailableModels.First() };
+            host.MarkupLine($"No Ollama model is configured. Using the first available model [green]'{RunningConfig.ModelName}'.");
+        }
+        else
+        {
+            if (!AvailableModels.Contains(RunningConfig.ModelName))
+            {
+                throw new InvalidOperationException($"The configured Ollama model '{RunningConfig.ModelName}' doesn't exist in the list of available models.");
+            }
+        }
+
+        return RunningConfig.ModelName;
+    }
 }
 
+/// <summary>
+/// Represents a configuration for an Ollama model.
+/// </summary>
+/// <param name="Name">Required. The unique identifier name for this configuration.</param>
+/// <param name="ModelName">Required. The name of the Ollama model to be used.</param>
+/// <param name="SystemPrompt">Optional. The system prompt to be used with this model configuration.</param>
+/// <param name="Description">Optional. A human-readable description of this configuration.</param>
+/// <param name="ResetContext">Optional. Indicates whether the context should be reset when switching to this configuration. Defaults to <c>false</c>.</param>
+
+internal record ModelConfig(string Name, string ModelName, string SystemPrompt = "", string Description = "", bool ResetContext = false);
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: ```suggestion
internal record ModelConfig(string Name, string ModelName, string SystemPrompt = "", string Description = "", bool ResetContext = false);
```
created_at: 2025-03-29T18:50:00Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+    
+    private static List<IRenderElement<string>> GetRenderModelElements(string currentModelName) => [
+        new CustomElement<string>(label: "Model Name", m => m),
+        new CustomElement<string>(label: "Active", m => m == currentModelName ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default) =>
+        RunningConfig = RunningConfig with { ModelName = await GetModelByName(name, cancellationToken).ConfigureAwait(false) };
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(AvailableModels, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(model, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task UseConfg(ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        RunningConfig = config with { };
+        await UseModel(RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            Configs,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name)
+    {
+        var config = Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task<string> GetActiveModel(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (string.IsNullOrEmpty(RunningConfig.ModelName))
+        {
+            // There is no model set, so use the first one available.
+            RunningConfig = RunningConfig with { ModelName = AvailableModels.First() };
+            host.MarkupLine($"No Ollama model is configured. Using the first available model [green]'{RunningConfig.ModelName}'.");
+        }
+        else
+        {
+            if (!AvailableModels.Contains(RunningConfig.ModelName))
+            {
+                throw new InvalidOperationException($"The configured Ollama model '{RunningConfig.ModelName}' doesn't exist in the list of available models.");
+            }
+        }
+
+        return RunningConfig.ModelName;
+    }
 }
 
+/// <summary>
+/// Represents a configuration for an Ollama model.
+/// </summary>
+/// <param name="Name">Required. The unique identifier name for this configuration.</param>
+/// <param name="ModelName">Required. The name of the Ollama model to be used.</param>
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: Are the required keys enforced? I don't see we check if the key/value is specified.
created_at: 2025-03-30T14:36:19Z
author_association: MEMBER
diff_hunk: @@ -238,15 +250,23 @@ public async Task<bool> ChatAsync(string input, IShell shell)
         catch (HttpRequestException e)
         {
             host.WriteErrorLine($"{e.Message}");
-            host.WriteErrorLine($"Ollama model:    \"{_settings.Model}\"");
-            host.WriteErrorLine($"Ollama endpoint: \"{_settings.Endpoint}\"");
-            host.WriteErrorLine($"Ollama settings: \"{SettingFile}\"");
+            host.WriteErrorLine($"Ollama active model: \"{activeModel}\"");
+            host.WriteErrorLine($"Ollama endpoint:     \"{_settings.Endpoint}\"");
+            host.WriteErrorLine($"Ollama settings:     \"{SettingFile}\"");
+        }
+        finally
+        {
+            if (_settings.RunningConfig.ResetContext)
+            {
+                // Reset the request context
+            	ResetContext();
+            }
path: shell/agents/AIShell.Ollama.Agent/OllamaAgent.cs
user: login: daxian-dbw
body: The XML comment for `ResetContext` says:
> Indicates whether the context should be reset when switching to this configuration.

It sounds to me we should reset context only when switching to a new config. But here you are resetting the context for every chat.
created_at: 2025-03-31T04:36:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,278 @@
+using System.CommandLine;
+using System.CommandLine.Completions;
+using System.Threading.Tasks;
+using AIShell.Abstraction;
+
+namespace AIShell.Ollama.Agent;
+
+internal sealed class ConfigCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+     public ConfigCommand(OllamaAgent agent)
path: shell/agents/AIShell.Ollama.Agent/Command.cs
user: login: daxian-dbw
body: ```suggestion

    public ConfigCommand(OllamaAgent agent)
```
created_at: 2025-03-31T04:37:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,278 @@
+using System.CommandLine;
+using System.CommandLine.Completions;
+using System.Threading.Tasks;
+using AIShell.Abstraction;
+
+namespace AIShell.Ollama.Agent;
+
+internal sealed class ConfigCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+     public ConfigCommand(OllamaAgent agent)
+        : base("config", "Command for config management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var use = new Command("use", "Specify a config to use.");
+        var useConfig = new Argument<string>(
+            name: "Config",
+            getDefaultValue: () => null,
+            description: "Name of a configuration.").AddCompletions(ConfigNameCompleter);
+        use.AddArgument(useConfig);
+        use.SetHandler(UseConfigAction, useConfig);
+
+        var list = new Command("list", "List a specific config, or all available configs.");
+        var listConfig = new Argument<string>(
+            name: "Config",
+            getDefaultValue: () => null,
+            description: "Name of a configuration.").AddCompletions(ConfigNameCompleter);
+        list.AddArgument(listConfig);
+        list.SetHandler(ListConfigAction, listConfig);
+
+        AddCommand(list);
+        AddCommand(use);
+    }
+
+    private void ListConfigAction(string name)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Invalid configuration.");
+            return;
+        }
+
+        if (string.IsNullOrEmpty(name))
+        {
+            settings.ListAllConfigs(host);
+            return;
+        }
+
+        try
+        {
+            settings.ShowOneConfig(host, name);
+        }
+        catch (InvalidOperationException ex)
+        {
+            string availableConfigNames = ConfigNamesAsString();
+            host.WriteErrorLine($"{ex.Message} Available cofiguration(s): {availableConfigNames}.");
+        }
+    }
+
+    private async Task UseConfigAction(string name)
+    {
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        var setting = _agnet.Settings;
+        var host = Shell.Host;
+
+        if (setting is null || setting.Configs.Count is 0)
+        {
+            host.WriteErrorLine("No configs configured.");
+            return;
+        }
+
+        try
+        {
+            ModelConfig chosenConfig = (string.IsNullOrEmpty(name)
+                ? host.PromptForSelectionAsync(
+                    title: "[orange1]Please select a [Blue]Configuration[/] to use[/]:",
+                    choices: setting.Configs,
+                    converter: ConfigName,
+                    CancellationToken.None).GetAwaiter().GetResult()
+                : setting.Configs.FirstOrDefault(c => c.Name == name)) ?? throw new InvalidOperationException($"The configuration '{name}' doesn't exist.");
+            await setting.UseConfg(chosenConfig);
+            host.MarkupLine($"Using the config [green]{chosenConfig.Name}[/]:");
+        }
+        catch (InvalidOperationException ex)
+        {
+            string availableConfigNames = ConfigNamesAsString();
+            host.WriteErrorLine($"{ex.Message} Available configurations: {availableConfigNames}.");
+        }
+    }
+
+    private static string ConfigName(ModelConfig config) => config.Name.Any(Char.IsWhiteSpace) ? $"\"{config.Name}\"" : config.Name;
+    private IEnumerable<string> ConfigNameCompleter(CompletionContext context) => _agnet.Settings?.Configs?.Select(ConfigName) ?? [];
+    private string ConfigNamesAsString() => string.Join(", ", ConfigNameCompleter(null));
+}
+
+internal sealed class SystemPromptCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+
+    public SystemPromptCommand(OllamaAgent agent)
+        : base("system-prompt", "Command for system prompt management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var show = new Command("show", "Show the current system prompt.");
+        show.SetHandler(ShowSystemPromptAction);
+
+        var set = new Command("set", "Sets the system prompt.");
+        var systemPromptModel = new Argument<string>(
+            name: "System-Prompt",
+            getDefaultValue: () => null,
+            description: "The system prompt");
+        set.AddArgument(systemPromptModel);
+        set.SetHandler(SetSystemPromptAction, systemPromptModel);
+
+        AddCommand(show);
+        AddCommand(set);
+    }
+
+    private void ShowSystemPromptAction()
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Invalid configuration.");
+            return;
+        }
+
+        try
+        {
+            settings.ShowSystemPrompt(host);
+        }
+        catch (InvalidOperationException ex)
+        {
+            host.WriteErrorLine($"{ex.Message}");
+        }
+    }
+
+    private void SetSystemPromptAction(string prompt)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+        _agnet.ResetContext();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Invalid configuration.");
+            return;
+        }
+
+        try
+        {
+            settings.SetSystemPrompt(host, prompt);
+        }
+        catch (InvalidOperationException ex)
+        {
+            host.WriteErrorLine($"{ex.Message}.");
+        }
+    }
+}
+
+internal sealed class ModelCommand : CommandBase
path: shell/agents/AIShell.Ollama.Agent/Command.cs
user: login: daxian-dbw
body: nitpick: we retrieve the available models from Ollama only once (even if there is changes to the setting file), so in case a new model is pulled on the Ollama side, a user has to restart the Ollama agent to be able to see it. How about make the `model` command always refresh `_settings._availableModels`? So that the user would see the new model when using the `model` command.
created_at: 2025-03-31T18:19:37Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Good catch. Accepted.
created_at: 2025-04-01T05:55:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Accepted.
created_at: 2025-04-01T05:56:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: They are listed with the tags and the tag must be specified with the `/mode use` command:
```powershell
@ollama> /model list

  Model Name                          Active
──────────────────────────────────────────────
  qwen2.5-powershell-expert:3b
  qwen2.5:3b
  llama3.2-powershell-expert:3b
  llama3.2:3b
  llama3.2:latest

@ollama> /model use llama3.2
ERROR: A model with the name 'llama3.2' doesn't exist in the list of available models. Available Modless: qwen2.5-powershell-expert:3b, qwen2.5:3b,        
llama3.2-powershell-expert:3b, llama3.2:3b, llama3.2:latest.
  ```
created_at: 2025-04-01T06:07:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Thanks for the idea!
created_at: 2025-04-01T06:56:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Thanks.
created_at: 2025-04-01T06:58:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+    
+    private static List<IRenderElement<string>> GetRenderModelElements(string currentModelName) => [
+        new CustomElement<string>(label: "Model Name", m => m),
+        new CustomElement<string>(label: "Active", m => m == currentModelName ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default) =>
+        RunningConfig = RunningConfig with { ModelName = await GetModelByName(name, cancellationToken).ConfigureAwait(false) };
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(AvailableModels, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(model, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task UseConfg(ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        RunningConfig = config with { };
+        await UseModel(RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: I did some refactoring. It should be better now.
created_at: 2025-04-01T07:02:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -238,15 +250,23 @@ public async Task<bool> ChatAsync(string input, IShell shell)
         catch (HttpRequestException e)
         {
             host.WriteErrorLine($"{e.Message}");
-            host.WriteErrorLine($"Ollama model:    \"{_settings.Model}\"");
-            host.WriteErrorLine($"Ollama endpoint: \"{_settings.Endpoint}\"");
-            host.WriteErrorLine($"Ollama settings: \"{SettingFile}\"");
+            host.WriteErrorLine($"Ollama active model: \"{activeModel}\"");
+            host.WriteErrorLine($"Ollama endpoint:     \"{_settings.Endpoint}\"");
+            host.WriteErrorLine($"Ollama settings:     \"{SettingFile}\"");
+        }
+        finally
+        {
+            if (_settings.RunningConfig.ResetContext)
+            {
+                // Reset the request context
+            	ResetContext();
+            }
path: shell/agents/AIShell.Ollama.Agent/OllamaAgent.cs
user: login: cnupy
body: The comment is incorrect. The intended behavior is to reset the context with each interaction. However, I am still unsure about this option. My intention was for it to reset the context automatically, but it appears that Ollama does not honor this setting as expected, retaining the context even when it's set to null. I may need to consider removing this option.

There is a better way to handle this. I am currently experimenting with using the OllamaApiClient `ChatAsync` method instead of `GenerateAsync`. However, this will be addressed in a future pull request.
created_at: 2025-04-01T07:24:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+    
+    private static List<IRenderElement<string>> GetRenderModelElements(string currentModelName) => [
+        new CustomElement<string>(label: "Model Name", m => m),
+        new CustomElement<string>(label: "Active", m => m == currentModelName ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default) =>
+        RunningConfig = RunningConfig with { ModelName = await GetModelByName(name, cancellationToken).ConfigureAwait(false) };
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(AvailableModels, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(model, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task UseConfg(ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        RunningConfig = config with { };
+        await UseModel(RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            Configs,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name)
+    {
+        var config = Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task<string> GetActiveModel(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (string.IsNullOrEmpty(RunningConfig.ModelName))
+        {
+            // There is no model set, so use the first one available.
+            RunningConfig = RunningConfig with { ModelName = AvailableModels.First() };
+            host.MarkupLine($"No Ollama model is configured. Using the first available model [green]'{RunningConfig.ModelName}'.");
+        }
+        else
+        {
+            if (!AvailableModels.Contains(RunningConfig.ModelName))
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: `UseConfig` is invoked only when the `/config use` command is executed. The `Settings` constructor, on the other hand, simply validates the configuration without retrieving the available models from the Ollama server. Users can interact with AIShell without needing to call `/config use`. This approach ensures that the model configured in `Settings` is available on the server, with the validation deferred until just before the `GenerateAsync` call.
created_at: 2025-04-01T08:34:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,278 @@
+using System.CommandLine;
+using System.CommandLine.Completions;
+using System.Threading.Tasks;
+using AIShell.Abstraction;
+
+namespace AIShell.Ollama.Agent;
+
+internal sealed class ConfigCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+     public ConfigCommand(OllamaAgent agent)
+        : base("config", "Command for config management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var use = new Command("use", "Specify a config to use.");
+        var useConfig = new Argument<string>(
+            name: "Config",
+            getDefaultValue: () => null,
+            description: "Name of a configuration.").AddCompletions(ConfigNameCompleter);
+        use.AddArgument(useConfig);
+        use.SetHandler(UseConfigAction, useConfig);
+
+        var list = new Command("list", "List a specific config, or all available configs.");
+        var listConfig = new Argument<string>(
+            name: "Config",
+            getDefaultValue: () => null,
+            description: "Name of a configuration.").AddCompletions(ConfigNameCompleter);
+        list.AddArgument(listConfig);
+        list.SetHandler(ListConfigAction, listConfig);
+
+        AddCommand(list);
+        AddCommand(use);
+    }
+
+    private void ListConfigAction(string name)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Invalid configuration.");
+            return;
+        }
+
+        if (string.IsNullOrEmpty(name))
+        {
+            settings.ListAllConfigs(host);
+            return;
+        }
+
+        try
+        {
+            settings.ShowOneConfig(host, name);
+        }
+        catch (InvalidOperationException ex)
+        {
+            string availableConfigNames = ConfigNamesAsString();
+            host.WriteErrorLine($"{ex.Message} Available cofiguration(s): {availableConfigNames}.");
+        }
+    }
+
+    private async Task UseConfigAction(string name)
+    {
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        var setting = _agnet.Settings;
+        var host = Shell.Host;
+
+        if (setting is null || setting.Configs.Count is 0)
+        {
+            host.WriteErrorLine("No configs configured.");
+            return;
+        }
+
+        try
+        {
+            ModelConfig chosenConfig = (string.IsNullOrEmpty(name)
+                ? host.PromptForSelectionAsync(
+                    title: "[orange1]Please select a [Blue]Configuration[/] to use[/]:",
+                    choices: setting.Configs,
+                    converter: ConfigName,
+                    CancellationToken.None).GetAwaiter().GetResult()
+                : setting.Configs.FirstOrDefault(c => c.Name == name)) ?? throw new InvalidOperationException($"The configuration '{name}' doesn't exist.");
+            await setting.UseConfg(chosenConfig);
+            host.MarkupLine($"Using the config [green]{chosenConfig.Name}[/]:");
+        }
+        catch (InvalidOperationException ex)
+        {
+            string availableConfigNames = ConfigNamesAsString();
+            host.WriteErrorLine($"{ex.Message} Available configurations: {availableConfigNames}.");
+        }
+    }
+
+    private static string ConfigName(ModelConfig config) => config.Name.Any(Char.IsWhiteSpace) ? $"\"{config.Name}\"" : config.Name;
+    private IEnumerable<string> ConfigNameCompleter(CompletionContext context) => _agnet.Settings?.Configs?.Select(ConfigName) ?? [];
+    private string ConfigNamesAsString() => string.Join(", ", ConfigNameCompleter(null));
+}
+
+internal sealed class SystemPromptCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+
+    public SystemPromptCommand(OllamaAgent agent)
+        : base("system-prompt", "Command for system prompt management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var show = new Command("show", "Show the current system prompt.");
+        show.SetHandler(ShowSystemPromptAction);
+
+        var set = new Command("set", "Sets the system prompt.");
+        var systemPromptModel = new Argument<string>(
+            name: "System-Prompt",
+            getDefaultValue: () => null,
+            description: "The system prompt");
+        set.AddArgument(systemPromptModel);
+        set.SetHandler(SetSystemPromptAction, systemPromptModel);
+
+        AddCommand(show);
+        AddCommand(set);
+    }
+
+    private void ShowSystemPromptAction()
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Invalid configuration.");
+            return;
+        }
+
+        try
+        {
+            settings.ShowSystemPrompt(host);
+        }
+        catch (InvalidOperationException ex)
+        {
+            host.WriteErrorLine($"{ex.Message}");
+        }
+    }
+
+    private void SetSystemPromptAction(string prompt)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+        _agnet.ResetContext();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Invalid configuration.");
+            return;
+        }
+
+        try
+        {
+            settings.SetSystemPrompt(host, prompt);
+        }
+        catch (InvalidOperationException ex)
+        {
+            host.WriteErrorLine($"{ex.Message}.");
+        }
+    }
+}
+
+internal sealed class ModelCommand : CommandBase
path: shell/agents/AIShell.Ollama.Agent/Command.cs
user: login: cnupy
body: I am currently working on the second iteration of the implementation and wanted this version merged before pushing additional changes. In this iteration, I am adding commands to pull, create, and delete models directly from AIShell. These commands will utilize a live model list and leverage the `ChatAsync` method instead of `GenerateAsync`.
created_at: 2025-04-01T08:52:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: Today, a user can specify `"Model": "phi3"` in the `ollama.config.json` file, and it works fine when assigning `phi3` directly to `_request.Model`. I guess `OllamaSharp` tries expanding `phi3` to `phi3:latest`, like when you use a docker container image. With the change in this PR, `"Model": "phi3"` stops working -- default setting file created by `NewExampleSettingFile()` throws the following exception for me:

```
ERROR: Agent failed to generate a response: The configured Ollama model 'phi3' doesn't exist in the list of available models.
   at AIShell.Ollama.Agent.Settings.GetActiveModel(IHost host, CancellationToken cancellationToken) in
E:\arena\repos\PSCopilot\shell\agents\AIShell.Ollama.Agent\Settings.cs:line 154
```
It'd be great to keep the current behavior -- if the module name contains `:`, then simply compare it; if not, then append `:latest` and compare.
created_at: 2025-04-01T22:58:39Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+    
+    private static List<IRenderElement<string>> GetRenderModelElements(string currentModelName) => [
+        new CustomElement<string>(label: "Model Name", m => m),
+        new CustomElement<string>(label: "Active", m => m == currentModelName ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default) =>
+        RunningConfig = RunningConfig with { ModelName = await GetModelByName(name, cancellationToken).ConfigureAwait(false) };
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(AvailableModels, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(model, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task UseConfg(ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        RunningConfig = config with { };
+        await UseModel(RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            Configs,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name)
+    {
+        var config = Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task<string> GetActiveModel(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (string.IsNullOrEmpty(RunningConfig.ModelName))
+        {
+            // There is no model set, so use the first one available.
+            RunningConfig = RunningConfig with { ModelName = AvailableModels.First() };
+            host.MarkupLine($"No Ollama model is configured. Using the first available model [green]'{RunningConfig.ModelName}'.");
+        }
+        else
+        {
+            if (!AvailableModels.Contains(RunningConfig.ModelName))
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: That makes sense. However, in `ChatAsync`, we will be calling `_settings.GetActiveModel(host)` per every query, and we will be checking `!_availableModels.Contains(RunningConfig.ModelName)` every time. That seems to be wasted efforts that can be avoided.
created_at: 2025-04-02T00:26:46Z
author_association: MEMBER
diff_hunk: @@ -238,15 +250,23 @@ public async Task<bool> ChatAsync(string input, IShell shell)
         catch (HttpRequestException e)
         {
             host.WriteErrorLine($"{e.Message}");
-            host.WriteErrorLine($"Ollama model:    \"{_settings.Model}\"");
-            host.WriteErrorLine($"Ollama endpoint: \"{_settings.Endpoint}\"");
-            host.WriteErrorLine($"Ollama settings: \"{SettingFile}\"");
+            host.WriteErrorLine($"Ollama active model: \"{activeModel}\"");
+            host.WriteErrorLine($"Ollama endpoint:     \"{_settings.Endpoint}\"");
+            host.WriteErrorLine($"Ollama settings:     \"{SettingFile}\"");
+        }
+        finally
+        {
+            if (_settings.RunningConfig.ResetContext)
+            {
+                // Reset the request context
+            	ResetContext();
+            }
path: shell/agents/AIShell.Ollama.Agent/OllamaAgent.cs
user: login: daxian-dbw
body: In that case, maybe remove the `ResetContext` setting in this PR?
created_at: 2025-04-02T00:28:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,278 @@
+using System.CommandLine;
+using System.CommandLine.Completions;
+using System.Threading.Tasks;
+using AIShell.Abstraction;
+
+namespace AIShell.Ollama.Agent;
+
+internal sealed class ConfigCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+     public ConfigCommand(OllamaAgent agent)
+        : base("config", "Command for config management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var use = new Command("use", "Specify a config to use.");
+        var useConfig = new Argument<string>(
+            name: "Config",
+            getDefaultValue: () => null,
+            description: "Name of a configuration.").AddCompletions(ConfigNameCompleter);
+        use.AddArgument(useConfig);
+        use.SetHandler(UseConfigAction, useConfig);
+
+        var list = new Command("list", "List a specific config, or all available configs.");
+        var listConfig = new Argument<string>(
+            name: "Config",
+            getDefaultValue: () => null,
+            description: "Name of a configuration.").AddCompletions(ConfigNameCompleter);
+        list.AddArgument(listConfig);
+        list.SetHandler(ListConfigAction, listConfig);
+
+        AddCommand(list);
+        AddCommand(use);
+    }
+
+    private void ListConfigAction(string name)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Invalid configuration.");
+            return;
+        }
+
+        if (string.IsNullOrEmpty(name))
+        {
+            settings.ListAllConfigs(host);
+            return;
+        }
+
+        try
+        {
+            settings.ShowOneConfig(host, name);
+        }
+        catch (InvalidOperationException ex)
+        {
+            string availableConfigNames = ConfigNamesAsString();
+            host.WriteErrorLine($"{ex.Message} Available cofiguration(s): {availableConfigNames}.");
+        }
+    }
+
+    private async Task UseConfigAction(string name)
+    {
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        var setting = _agnet.Settings;
+        var host = Shell.Host;
+
+        if (setting is null || setting.Configs.Count is 0)
+        {
+            host.WriteErrorLine("No configs configured.");
+            return;
+        }
+
+        try
+        {
+            ModelConfig chosenConfig = (string.IsNullOrEmpty(name)
+                ? host.PromptForSelectionAsync(
+                    title: "[orange1]Please select a [Blue]Configuration[/] to use[/]:",
+                    choices: setting.Configs,
+                    converter: ConfigName,
+                    CancellationToken.None).GetAwaiter().GetResult()
+                : setting.Configs.FirstOrDefault(c => c.Name == name)) ?? throw new InvalidOperationException($"The configuration '{name}' doesn't exist.");
+            await setting.UseConfg(chosenConfig);
+            host.MarkupLine($"Using the config [green]{chosenConfig.Name}[/]:");
+        }
+        catch (InvalidOperationException ex)
+        {
+            string availableConfigNames = ConfigNamesAsString();
+            host.WriteErrorLine($"{ex.Message} Available configurations: {availableConfigNames}.");
+        }
+    }
+
+    private static string ConfigName(ModelConfig config) => config.Name.Any(Char.IsWhiteSpace) ? $"\"{config.Name}\"" : config.Name;
+    private IEnumerable<string> ConfigNameCompleter(CompletionContext context) => _agnet.Settings?.Configs?.Select(ConfigName) ?? [];
+    private string ConfigNamesAsString() => string.Join(", ", ConfigNameCompleter(null));
+}
+
+internal sealed class SystemPromptCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+
+    public SystemPromptCommand(OllamaAgent agent)
+        : base("system-prompt", "Command for system prompt management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var show = new Command("show", "Show the current system prompt.");
+        show.SetHandler(ShowSystemPromptAction);
+
+        var set = new Command("set", "Sets the system prompt.");
+        var systemPromptModel = new Argument<string>(
+            name: "System-Prompt",
+            getDefaultValue: () => null,
+            description: "The system prompt");
+        set.AddArgument(systemPromptModel);
+        set.SetHandler(SetSystemPromptAction, systemPromptModel);
+
+        AddCommand(show);
+        AddCommand(set);
+    }
+
+    private void ShowSystemPromptAction()
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Invalid configuration.");
+            return;
+        }
+
+        try
+        {
+            settings.ShowSystemPrompt(host);
+        }
+        catch (InvalidOperationException ex)
+        {
+            host.WriteErrorLine($"{ex.Message}");
+        }
+    }
+
+    private void SetSystemPromptAction(string prompt)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+        _agnet.ResetContext();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Invalid configuration.");
+            return;
+        }
+
+        try
+        {
+            settings.SetSystemPrompt(host, prompt);
+        }
+        catch (InvalidOperationException ex)
+        {
+            host.WriteErrorLine($"{ex.Message}.");
+        }
+    }
+}
+
+internal sealed class ModelCommand : CommandBase
path: shell/agents/AIShell.Ollama.Agent/Command.cs
user: login: daxian-dbw
body: Sounds good. Then let's ignore this comment for now :)
created_at: 2025-04-02T00:34:06Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Ah, of course. I didn’t understand your question right away.
created_at: 2025-04-05T16:50:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -238,15 +250,23 @@ public async Task<bool> ChatAsync(string input, IShell shell)
         catch (HttpRequestException e)
         {
             host.WriteErrorLine($"{e.Message}");
-            host.WriteErrorLine($"Ollama model:    \"{_settings.Model}\"");
-            host.WriteErrorLine($"Ollama endpoint: \"{_settings.Endpoint}\"");
-            host.WriteErrorLine($"Ollama settings: \"{SettingFile}\"");
+            host.WriteErrorLine($"Ollama active model: \"{activeModel}\"");
+            host.WriteErrorLine($"Ollama endpoint:     \"{_settings.Endpoint}\"");
+            host.WriteErrorLine($"Ollama settings:     \"{SettingFile}\"");
+        }
+        finally
+        {
+            if (_settings.RunningConfig.ResetContext)
+            {
+                // Reset the request context
+            	ResetContext();
+            }
path: shell/agents/AIShell.Ollama.Agent/OllamaAgent.cs
user: login: cnupy
body: Yes, removed.
created_at: 2025-04-05T16:50:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+    
+    private static List<IRenderElement<string>> GetRenderModelElements(string currentModelName) => [
+        new CustomElement<string>(label: "Model Name", m => m),
+        new CustomElement<string>(label: "Active", m => m == currentModelName ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default) =>
+        RunningConfig = RunningConfig with { ModelName = await GetModelByName(name, cancellationToken).ConfigureAwait(false) };
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(AvailableModels, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(model, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task UseConfg(ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        RunningConfig = config with { };
+        await UseModel(RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            Configs,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name)
+    {
+        var config = Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task<string> GetActiveModel(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (string.IsNullOrEmpty(RunningConfig.ModelName))
+        {
+            // There is no model set, so use the first one available.
+            RunningConfig = RunningConfig with { ModelName = AvailableModels.First() };
+            host.MarkupLine($"No Ollama model is configured. Using the first available model [green]'{RunningConfig.ModelName}'.");
+        }
+        else
+        {
+            if (!AvailableModels.Contains(RunningConfig.ModelName))
+            {
+                throw new InvalidOperationException($"The configured Ollama model '{RunningConfig.ModelName}' doesn't exist in the list of available models.");
+            }
+        }
+
+        return RunningConfig.ModelName;
+    }
 }
 
+/// <summary>
+/// Represents a configuration for an Ollama model.
+/// </summary>
+/// <param name="Name">Required. The unique identifier name for this configuration.</param>
+/// <param name="ModelName">Required. The name of the Ollama model to be used.</param>
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: I've added the `[JsonRequired]` attribute to ensure the required keys are properly marked.
created_at: 2025-04-05T16:53:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,193 @@
 ﻿using System.Text.Json;
 using System.Text.Json.Serialization;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
 internal class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private List<string> AvailableModels { get; set; } = [];
+    public List<ModelConfig> Configs { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Configs = configData.Configs ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        // Ensure the default configuration is available in the list of configurations.
+        if (!string.IsNullOrEmpty(configData.DefaultConfig) &&
+                !Configs.Any(c => c.Name == configData.DefaultConfig))
+        {
+            throw new InvalidOperationException($"The selected default configuration '{configData.DefaultConfig}' doesn't exist.");
+        }
+
+        RunningConfig = (Configs, configData.DefaultConfig) switch
+        {
+            (not [], "") => Configs.First() with { }, /* No default config - use the first one defined in Configs */
+            (not [], _) => Configs.First(c => c.Name == configData.DefaultConfig) with { }, /* Use the default config */
+            _ => new ModelConfig(nameof(RunningConfig), "") /* No config available - use empty */
+        };
     }
-}
 
-internal class ConfigData
-{
-    public string Model { get; set; }
-    public string Endpoint { get; set; }
-    public bool Stream { get; set; }
+    private async Task EnsureModelsInitialized(CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        { 
+            return;
+        }
+
+        OllamaApiClient _client = null;
+        try
+        {
+            _client = new OllamaApiClient(Endpoint);
+            var models = await _client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+            AvailableModels = [.. models.Select(m => m.Name)];
+
+            if (AvailableModels.Count == 0)
+            {
+                throw new InvalidOperationException($"No models are available to use from '{Endpoint}'.");
+            }
+            _initialized = true;
+        }
+        finally
+        {
+            if (_client is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        }
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        return AvailableModels;
+    }
+
+    internal async Task<string> GetModelByName(string name, CancellationToken cancellationToken = default)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (!AvailableModels.Contains(name))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist in the list of available models.");
+        }
+
+        return name;
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [ new CustomElement<string>(label: "System prompt", s => s) ];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {   
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+    
+    private static List<IRenderElement<string>> GetRenderModelElements(string currentModelName) => [
+        new CustomElement<string>(label: "Model Name", m => m),
+        new CustomElement<string>(label: "Active", m => m == currentModelName ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(string name, CancellationToken cancellationToken = default) =>
+        RunningConfig = RunningConfig with { ModelName = await GetModelByName(name, cancellationToken).ConfigureAwait(false) };
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        host.RenderTable(AvailableModels, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        var model = await GetModelByName(name, cancellationToken).ConfigureAwait(false);
+        host.RenderList(model, GetRenderModelElements(RunningConfig.ModelName));
+    }
+
+    internal async Task UseConfg(ModelConfig config, CancellationToken cancellationToken = default)
+    {
+        RunningConfig = config with { };
+        await UseModel(RunningConfig.ModelName, cancellationToken).ConfigureAwait(false);
+    }
+
+    internal void ListAllConfigs(IHost host)
+    {
+        host.RenderTable(
+            Configs,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOneConfig(IHost host, string name)
+    {
+        var config = Configs.FirstOrDefault(c => c.Name == name);
+        host.RenderList(
+            config,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task<string> GetActiveModel(IHost host, CancellationToken cancellationToken = default)
+    {
+        await EnsureModelsInitialized(cancellationToken).ConfigureAwait(false);
+        if (string.IsNullOrEmpty(RunningConfig.ModelName))
+        {
+            // There is no model set, so use the first one available.
+            RunningConfig = RunningConfig with { ModelName = AvailableModels.First() };
+            host.MarkupLine($"No Ollama model is configured. Using the first available model [green]'{RunningConfig.ModelName}'.");
+        }
+        else
+        {
+            if (!AvailableModels.Contains(RunningConfig.ModelName))
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Got it now! I've added a flag to ensure the check is performed only once.
created_at: 2025-04-05T16:55:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,268 @@
-﻿using System.Text.Json;
+﻿using System.Diagnostics;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
-internal class Settings
+internal partial class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private bool _runningConfigChecked = false;
+    private bool? _isRunningLocalHost = null;
+    private List<string> _availableModels = [];
+    public List<ModelConfig> Presets { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Presets = configData.Presets ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        if (string.IsNullOrEmpty(configData.DefaultPreset))
+        {
+            RunningConfig = Presets.Count > 0
+                ? Presets[0] with { }  /* No default preset - use the first one defined in Presets */
+                : new ModelConfig(name: nameof(RunningConfig), modelName: ""); /* No presets are defined - use empty */
+        }
+        else
+        {
+            // Ensure the default configuration is available in the list of configurations.
+            var first = Presets.FirstOrDefault(c => c.Name == configData.DefaultPreset)
+                ?? throw new InvalidOperationException($"The selected default preset '{configData.DefaultPreset}' doesn't exist.");
+            // Use the default config
+            RunningConfig = first with { };
+        }
+    }
+
+    private async Task<bool> EnsureModelsInitialized(IHost host, CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        {
+            return true;
+        }
+
+        // Skip the self check when host is null.
+        if (host is not null)
+        {
+            bool success = await PerformSelfcheck(host, checkEndpointOnly: true);
+            if (!success)
+            {
+                return false;
+            }
+        }
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: Would it make sense to introduce a flag for performing a self-check instead of using `null` for the host? This might make the logic clearer and easier to follow.
```suggestion
    private async Task<bool> EnsureModelsInitialized(IHost host, CancellationToken cancellationToken = default) =>
        EnsureModelsInitialized(host: host, performSelfcheck: true, cancellationToken: cancellationToken);
    
    private async Task<bool> EnsureModelsInitialized(IHost host, bool performSelfcheck, CancellationToken cancellationToken = default)
    {
        if (_initialized)
        {
            return true;
        }

        // Skip the self check when host is null.
        if (performSelfcheck)
        {
            bool success = await PerformSelfcheck(host, checkEndpointOnly: true);
            if (!success)
            {
                return false;
            }
        }
```
created_at: 2025-04-24T06:55:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,268 @@
-﻿using System.Text.Json;
+﻿using System.Diagnostics;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
-internal class Settings
+internal partial class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private bool _runningConfigChecked = false;
+    private bool? _isRunningLocalHost = null;
+    private List<string> _availableModels = [];
+    public List<ModelConfig> Presets { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Presets = configData.Presets ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        if (string.IsNullOrEmpty(configData.DefaultPreset))
+        {
+            RunningConfig = Presets.Count > 0
+                ? Presets[0] with { }  /* No default preset - use the first one defined in Presets */
+                : new ModelConfig(name: nameof(RunningConfig), modelName: ""); /* No presets are defined - use empty */
+        }
+        else
+        {
+            // Ensure the default configuration is available in the list of configurations.
+            var first = Presets.FirstOrDefault(c => c.Name == configData.DefaultPreset)
+                ?? throw new InvalidOperationException($"The selected default preset '{configData.DefaultPreset}' doesn't exist.");
+            // Use the default config
+            RunningConfig = first with { };
+        }
+    }
+
+    private async Task<bool> EnsureModelsInitialized(IHost host, CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        {
+            return true;
+        }
+
+        // Skip the self check when host is null.
+        if (host is not null)
+        {
+            bool success = await PerformSelfcheck(host, checkEndpointOnly: true);
+            if (!success)
+            {
+                return false;
+            }
+        }
+
+        using OllamaApiClient client = new(Endpoint);
+        var models = await client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+        _availableModels = [.. models.Select(m => m.Name)];
+        _initialized = true;
+        return true;
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(IHost host = null, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
+        {
+            return _availableModels;
+        }
+
+        return [];
+    }
+
+    internal void EnsureModelNameIsValid(string name)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+
+        if (!_availableModels.Contains(name.AddLatestTagIfNecessery()))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist. The available models are: [{string.Join(", ", _availableModels)}].");
+        }
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [new CustomElement<string>(label: "System prompt", s => s)];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+
+    private static List<IRenderElement<string>> GetRenderModelElements(Func<string, bool> isActive) => [
+        new CustomElement<string>(label: "Name", m => m),
+        new CustomElement<string>(label: "Active", m => isActive(m) ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
+        {
+            EnsureModelNameIsValid(name);
+            RunningConfig = RunningConfig with { ModelName = name };
+            _runningConfigChecked = true;
+        }
+    }
+
+    internal async Task ListAllModels(IHost host, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
+        {
+            host.RenderTable(_availableModels, GetRenderModelElements(m => m == RunningConfig.ModelName.AddLatestTagIfNecessery()));
+        }
     }
+
+    internal async Task ShowOneModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
+        {
+            EnsureModelNameIsValid(name);
+            host.RenderList(name, GetRenderModelElements(m => m == RunningConfig.ModelName.AddLatestTagIfNecessery()));
+        }
+    }
+
+    internal async Task UsePreset(IHost host, ModelConfig preset, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
+        {
+            EnsureModelNameIsValid(preset.ModelName);
+            RunningConfig = preset with { };
+            _runningConfigChecked = true;
+        }
+    }
+
+    internal void ListAllPresets(IHost host)
+    {
+        host.RenderTable(
+            Presets,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig  ? "true" : string.Empty)
+            ]);
+    }
+
+    internal void ShowOnePreset(IHost host, string name)
+    {
+        var preset = Presets.FirstOrDefault(c => c.Name == name);
+        if (preset is null)
+        {
+            host.WriteErrorLine($"The preset '{name}' doesn't exist.");
+            return;
+        }
+
+        host.RenderList(
+            preset,
+            [
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Name)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.Description)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.ModelName)),
+                new PropertyElement<ModelConfig>(nameof(ModelConfig.SystemPrompt)),
+                new CustomElement<ModelConfig>(label: "Active", m => m == RunningConfig ? "true" : string.Empty),
+            ]);
+    }
+
+    internal async Task<bool> PerformSelfcheck(IHost host, bool checkEndpointOnly = false)
+    {
+        _isRunningLocalHost ??= IsLocalHost().IsMatch(new Uri(Endpoint).Host);
+
+        if (_isRunningLocalHost is true && Process.GetProcessesByName("ollama").Length is 0)
+        {
+            host.WriteErrorLine("Please be sure the Ollama is installed and server is running. Check all the prerequisites in the README of this agent are met.");
+            return false;
+        }
+
+        if (!checkEndpointOnly && !_runningConfigChecked)
+        {
+            await EnsureModelsInitialized(host: null).ConfigureAwait(false);
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: 
```suggestion
            await EnsureModelsInitialized(host: host, performSelfcheck: false).ConfigureAwait(false);
```
created_at: 2025-04-24T07:05:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,295 @@
+using System.CommandLine;
+using System.CommandLine.Completions;
+using System.Threading.Tasks;
+using AIShell.Abstraction;
+
+namespace AIShell.Ollama.Agent;
+
+internal sealed class PresetCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+    
+    public PresetCommand(OllamaAgent agent)
+        : base("preset", "Command for preset management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var use = new Command("use", "Specify a preset to use.");
+        var usePreset = new Argument<string>(
+            name: "Preset",
+            getDefaultValue: () => null,
+            description: "Name of a preset.").AddCompletions(PresetNameCompleter);
+        use.AddArgument(usePreset);
+        use.SetHandler(UsePresetAction, usePreset);
+
+        var list = new Command("list", "List a specific preset, or all configured presets.");
+        var listPreset = new Argument<string>(
+            name: "Preset",
+            getDefaultValue: () => null,
+            description: "Name of a preset.").AddCompletions(PresetNameCompleter);
+        list.AddArgument(listPreset);
+        list.SetHandler(ListPresetAction, listPreset);
+
+        AddCommand(list);
+        AddCommand(use);
+    }
+
+    private void ListPresetAction(string name)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+
+        try
+        {
+            if (string.IsNullOrEmpty(name))
+            {
+                settings.ListAllPresets(host);
+                return;
+            }
+
+            settings.ShowOnePreset(host, name);
+        }
+        catch (InvalidOperationException ex)
+        {
+            string availablePresetNames = PresetNamesAsString();
+            host.WriteErrorLine($"{ex.Message} Available preset(s): {availablePresetNames}.");
+        }
+    }
+
+    private async Task UsePresetAction(string name)
+    {
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        var setting = _agnet.Settings;
+        var host = Shell.Host;
+
+        if (setting is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+
+        if (setting.Presets.Count is 0)
+        {
+            host.WriteErrorLine("There are no presets configured.");
+            return;
+        }
+
+        try
+        {
+            ModelConfig chosenPreset = (string.IsNullOrEmpty(name)
+                ? await host.PromptForSelectionAsync(
+                    title: "[orange1]Please select a [Blue]Preset[/] to use[/]:",
+                    choices: setting.Presets,
+                    converter: PresetName,
+                    CancellationToken.None)
+                : setting.Presets.FirstOrDefault(c => c.Name == name)) ?? throw new InvalidOperationException($"The preset '{name}' doesn't exist.");
+            await setting.UsePreset(host, chosenPreset);
+            host.MarkupLine($"Using the preset [green]{chosenPreset.Name}[/]:");
+        }
+        catch (InvalidOperationException ex)
+        {
+            string availablePresetNames = PresetNamesAsString();
+            host.WriteErrorLine($"{ex.Message} Available presets: {availablePresetNames}.");
+        }
+    }
+
+    private static string PresetName(ModelConfig preset) => preset.Name.Any(Char.IsWhiteSpace) ? $"\"{preset.Name}\"" : preset.Name;
+    private IEnumerable<string> PresetNameCompleter(CompletionContext context) => _agnet.Settings?.Presets?.Select(PresetName) ?? [];
+    private string PresetNamesAsString() => string.Join(", ", PresetNameCompleter(null));
+}
+
+internal sealed class SystemPromptCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+
+    public SystemPromptCommand(OllamaAgent agent)
+        : base("system-prompt", "Command for system prompt management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var show = new Command("show", "Show the current system prompt.");
+        show.SetHandler(ShowSystemPromptAction);
+
+        var set = new Command("set", "Sets the system prompt.");
+        var systemPromptModel = new Argument<string>(
+            name: "System-Prompt",
+            getDefaultValue: () => null,
+            description: "The system prompt");
+        set.AddArgument(systemPromptModel);
+        set.SetHandler(SetSystemPromptAction, systemPromptModel);
+
+        AddCommand(show);
+        AddCommand(set);
+    }
+
+    private void ShowSystemPromptAction()
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+
+        try
+        {
+            settings.ShowSystemPrompt(host);
+        }
+        catch (InvalidOperationException ex)
+        {
+            host.WriteErrorLine(ex.Message);
+        }
+    }
+
+    private void SetSystemPromptAction(string prompt)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+        _agnet.ResetContext();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+
+        try
+        {
+            settings.SetSystemPrompt(host, prompt);
+        }
+        catch (InvalidOperationException ex)
+        {
+            host.WriteErrorLine(ex.Message);
+        }
+    }
+}
+
+internal sealed class ModelCommand : CommandBase
+{
+    private readonly OllamaAgent _agnet;
+
+    public ModelCommand(OllamaAgent agent)
+        : base("model", "Command for model management within the 'ollama' agent.")
+    {
+        _agnet = agent;
+
+        var use = new Command("use", "Specify a model to use, or choose one from the available models.");
+        var useModel = new Argument<string>(
+            name: "Model",
+            getDefaultValue: () => null,
+            description: "Name of a model.").AddCompletions(ModelNameCompleter);
+        use.AddArgument(useModel);
+        use.SetHandler(UseModelAction, useModel);
+
+        var list = new Command("list", "List a specific model, or all available models.");
+        var listModel = new Argument<string>(
+            name: "Model",
+            getDefaultValue: () => null,
+            description: "Name of a model.").AddCompletions(ModelNameCompleter);
+        list.AddArgument(listModel);
+        list.SetHandler(ListModelAction, listModel);
+
+        AddCommand(list);
+        AddCommand(use);
+    }
+
+    private async Task ListModelAction(string name)
+    {
+        IHost host = Shell.Host;
+
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        Settings settings = _agnet.Settings;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+        try
+        {
+            if (string.IsNullOrEmpty(name))
+            {
+                await settings.ListAllModels(host);
+                return;
+            }
+
+            await settings.ShowOneModel(host, name);
+        }
+        catch (InvalidOperationException ex)
+        {
+            host.WriteErrorLine(ex.Message);
+        }
+    }
+
+    private async Task UseModelAction(string name)
+    {
+        // Reload the setting file if needed.
+        _agnet.ReloadSettings();
+
+        var settings = _agnet.Settings;
+        var host = Shell.Host;
+
+        if (settings is null)
+        {
+            host.WriteErrorLine("Error loading the configuration.");
+            return;
+        }
+
+        try
+        {
+            bool success = await settings.PerformSelfcheck(host, checkEndpointOnly: true);
+            if (!success)
+            {
+                return;
+            }
+
+            var allModels = await settings.GetAllModels();
+            if (allModels.Count is 0)
+            {
+                host.WriteErrorLine($"No models found from '{settings.Endpoint}'.");
+                return;
+            }
+
+            if (string.IsNullOrEmpty(name))
+            {
+                name = await host.PromptForSelectionAsync(
+                    title: "[orange1]Please select a [Blue]Model[/] to use[/]:",
+                    choices: allModels,
+                    CancellationToken.None);
+            }
+
+            await settings.UseModel(host: null, name);
path: shell/agents/AIShell.Ollama.Agent/Command.cs
user: login: cnupy
body: 
```suggestion
            await settings.UseModel(host: host, name: name);
```
created_at: 2025-04-24T07:23:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,268 @@
-﻿using System.Text.Json;
+﻿using System.Diagnostics;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
-internal class Settings
+internal partial class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private bool _runningConfigChecked = false;
+    private bool? _isRunningLocalHost = null;
+    private List<string> _availableModels = [];
+    public List<ModelConfig> Presets { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Presets = configData.Presets ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        if (string.IsNullOrEmpty(configData.DefaultPreset))
+        {
+            RunningConfig = Presets.Count > 0
+                ? Presets[0] with { }  /* No default preset - use the first one defined in Presets */
+                : new ModelConfig(name: nameof(RunningConfig), modelName: ""); /* No presets are defined - use empty */
+        }
+        else
+        {
+            // Ensure the default configuration is available in the list of configurations.
+            var first = Presets.FirstOrDefault(c => c.Name == configData.DefaultPreset)
+                ?? throw new InvalidOperationException($"The selected default preset '{configData.DefaultPreset}' doesn't exist.");
+            // Use the default config
+            RunningConfig = first with { };
+        }
+    }
+
+    private async Task<bool> EnsureModelsInitialized(IHost host, CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        {
+            return true;
+        }
+
+        // Skip the self check when host is null.
+        if (host is not null)
+        {
+            bool success = await PerformSelfcheck(host, checkEndpointOnly: true);
+            if (!success)
+            {
+                return false;
+            }
+        }
+
+        using OllamaApiClient client = new(Endpoint);
+        var models = await client.ListLocalModelsAsync(cancellationToken).ConfigureAwait(false);
+        _availableModels = [.. models.Select(m => m.Name)];
+        _initialized = true;
+        return true;
+    }
+
+    internal async Task<ICollection<string>> GetAllModels(IHost host = null, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
+        {
+            return _availableModels;
+        }
+
+        return [];
+    }
+
+    internal void EnsureModelNameIsValid(string name)
+    {
+        ArgumentException.ThrowIfNullOrEmpty(name);
+
+        if (!_availableModels.Contains(name.AddLatestTagIfNecessery()))
+        {
+            throw new InvalidOperationException($"A model with the name '{name}' doesn't exist. The available models are: [{string.Join(", ", _availableModels)}].");
+        }
+    }
+
+    private static List<IRenderElement<string>> GetSystemPromptRenderElements() => [new CustomElement<string>(label: "System prompt", s => s)];
+
+    internal void ShowSystemPrompt(IHost host) => host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+
+    internal void SetSystemPrompt(IHost host, string prompt)
+    {
+        RunningConfig = RunningConfig with { SystemPrompt = prompt ?? string.Empty };
+        host.RenderList(RunningConfig.SystemPrompt, GetSystemPromptRenderElements());
+    }
+
+    private static List<IRenderElement<string>> GetRenderModelElements(Func<string, bool> isActive) => [
+        new CustomElement<string>(label: "Name", m => m),
+        new CustomElement<string>(label: "Active", m => isActive(m) ? "true" : string.Empty)
+    ];
+
+    internal async Task UseModel(IHost host, string name, CancellationToken cancellationToken = default)
+    {
+        if (await EnsureModelsInitialized(host, cancellationToken).ConfigureAwait(false))
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: 
```suggestion
        if (await EnsureModelsInitialized(host: host, performSelfcheck: false, cancellationToken: cancellationToken).ConfigureAwait(false))
```
created_at: 2025-04-24T07:25:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,268 @@
-﻿using System.Text.Json;
+﻿using System.Diagnostics;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
-internal class Settings
+internal partial class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private bool _runningConfigChecked = false;
+    private bool? _isRunningLocalHost = null;
+    private List<string> _availableModels = [];
+    public List<ModelConfig> Presets { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Presets = configData.Presets ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        if (string.IsNullOrEmpty(configData.DefaultPreset))
+        {
+            RunningConfig = Presets.Count > 0
+                ? Presets[0] with { }  /* No default preset - use the first one defined in Presets */
+                : new ModelConfig(name: nameof(RunningConfig), modelName: ""); /* No presets are defined - use empty */
+        }
+        else
+        {
+            // Ensure the default configuration is available in the list of configurations.
+            var first = Presets.FirstOrDefault(c => c.Name == configData.DefaultPreset)
+                ?? throw new InvalidOperationException($"The selected default preset '{configData.DefaultPreset}' doesn't exist.");
+            // Use the default config
+            RunningConfig = first with { };
+        }
+    }
+
+    private async Task<bool> EnsureModelsInitialized(IHost host, CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        {
+            return true;
+        }
+
+        // Skip the self check when host is null.
+        if (host is not null)
+        {
+            bool success = await PerformSelfcheck(host, checkEndpointOnly: true);
+            if (!success)
+            {
+                return false;
+            }
+        }
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: `GetAllModels(IHost host = null, ...)` method has `null` as the default value for, and `host` is actually only used for endpoint check, which is not necessary in some cases, such as in `UseModel` action, `PerformSelfcheck` is called before calling into `EnsureModelsInitialized`.

~How about having an overload that doesn't accept `host`? It would be clearer on the intention.~ Look at it more, I still think it's cleaner to skip endpoint check when `host is null`. This is because `EnsureModelsInitialized` is wrapped in utility methods such as `GetAllModels`, `UseModel`, `UsePreset` and etc, and we only know whether we need the endpoint check or not when calling those utility methods.

I added more comments to make the intent clear.
created_at: 2025-04-24T16:59:04Z
author_association: MEMBER
diff_hunk: @@ -1,40 +1,268 @@
-﻿using System.Text.Json;
+﻿using System.Diagnostics;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
-internal class Settings
+internal partial class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private bool _runningConfigChecked = false;
+    private bool? _isRunningLocalHost = null;
+    private List<string> _availableModels = [];
+    public List<ModelConfig> Presets { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Presets = configData.Presets ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        if (string.IsNullOrEmpty(configData.DefaultPreset))
+        {
+            RunningConfig = Presets.Count > 0
+                ? Presets[0] with { }  /* No default preset - use the first one defined in Presets */
+                : new ModelConfig(name: nameof(RunningConfig), modelName: ""); /* No presets are defined - use empty */
+        }
+        else
+        {
+            // Ensure the default configuration is available in the list of configurations.
+            var first = Presets.FirstOrDefault(c => c.Name == configData.DefaultPreset)
+                ?? throw new InvalidOperationException($"The selected default preset '{configData.DefaultPreset}' doesn't exist.");
+            // Use the default config
+            RunningConfig = first with { };
+        }
+    }
+
+    private async Task<bool> EnsureModelsInitialized(IHost host, CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        {
+            return true;
+        }
+
+        // Skip the self check when host is null.
+        if (host is not null)
+        {
+            bool success = await PerformSelfcheck(host, checkEndpointOnly: true);
+            if (!success)
+            {
+                return false;
+            }
+        }
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: cnupy
body: > `GetAllModels(IHost host = null, ...)` method has `null` as the default value for, and `host` is actually only used for endpoint check, which is not necessary in some cases, such as in `UseModel` action, `PerformSelfcheck` is called before calling into `EnsureModelsInitialized`.
> 
> ~How about having an overload that doesn't accept `host`? It would be clearer on the intention.~ Look at it more, I still think it's cleaner to skip endpoint check when `host is null`. This is because `EnsureModelsInitialized` is wrapped in utility methods such as `GetAllModels`, `UseModel`, `UsePreset` and etc, and we only know whether we need the endpoint check or not when calling those utility methods.
> 
> I added more comments to make the intent clear.

Sure, I'm OK with this.
created_at: 2025-04-25T11:59:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,268 @@
-﻿using System.Text.Json;
+﻿using System.Diagnostics;
+using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using AIShell.Abstraction;
+using OllamaSharp;
 
 namespace AIShell.Ollama.Agent;
 
-internal class Settings
+internal partial class Settings
 {
-    public string Model { get; }
+    private bool _initialized = false;
+    private bool _runningConfigChecked = false;
+    private bool? _isRunningLocalHost = null;
+    private List<string> _availableModels = [];
+    public List<ModelConfig> Presets { get; }
     public string Endpoint { get; }
     public bool Stream { get; }
+    public ModelConfig RunningConfig { get; private set; }
 
     public Settings(ConfigData configData)
     {
-        // Validate Model and Endpoint for null or empty values
-        if (string.IsNullOrWhiteSpace(configData.Model))
-        {
-            throw new ArgumentException("\"Model\" key is missing.");
-        }
-
         if (string.IsNullOrWhiteSpace(configData.Endpoint))
         {
-            throw new ArgumentException("\"Endpoint\" key is missing.");
+            throw new InvalidOperationException("'Endpoint' key is missing in configuration.");
         }
 
-        Model = configData.Model;
+        Presets = configData.Presets ?? [];
         Endpoint = configData.Endpoint;
         Stream = configData.Stream;
+
+        if (string.IsNullOrEmpty(configData.DefaultPreset))
+        {
+            RunningConfig = Presets.Count > 0
+                ? Presets[0] with { }  /* No default preset - use the first one defined in Presets */
+                : new ModelConfig(name: nameof(RunningConfig), modelName: ""); /* No presets are defined - use empty */
+        }
+        else
+        {
+            // Ensure the default configuration is available in the list of configurations.
+            var first = Presets.FirstOrDefault(c => c.Name == configData.DefaultPreset)
+                ?? throw new InvalidOperationException($"The selected default preset '{configData.DefaultPreset}' doesn't exist.");
+            // Use the default config
+            RunningConfig = first with { };
+        }
+    }
+
+    private async Task<bool> EnsureModelsInitialized(IHost host, CancellationToken cancellationToken = default)
+    {
+        if (_initialized)
+        {
+            return true;
+        }
+
+        // Skip the self check when host is null.
+        if (host is not null)
+        {
+            bool success = await PerformSelfcheck(host, checkEndpointOnly: true);
+            if (!success)
+            {
+                return false;
+            }
+        }
path: shell/agents/AIShell.Ollama.Agent/Settings.cs
user: login: daxian-dbw
body: Great. I will get the PR merged.
created_at: 2025-04-25T16:51:36Z
author_association: MEMBER

PR comments:
user: login: cnupy
created_at: 2025-03-05T19:56:06Z
body: > @cnupy please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
> 
> ```
> @microsoft-github-policy-service agree [company="{your company}"]
> ```
> 
> > Options:
> > 
> > * (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
> > 
> > ```
> > @microsoft-github-policy-service agree
> > ```
> > 
> > 
> >     
> >       
> >     
> > 
> >       
> >     
> > 
> >     
> >   
> > 
> > * (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
> > 
> > ```
> > @microsoft-github-policy-service agree company="Microsoft"
> > ```
> 
> Contributor License Agreement
> ### Contribution License Agreement
> This Contribution License Agreement (**“Agreement”**) is agreed to by the party signing below (**“You”**), and conveys certain license rights to Microsoft Corporation and its affiliates (“Microsoft”) for Your contributions to Microsoft open source projects. This Agreement is effective as of the latest signature date below.
> 
> 1. **Definitions**.
>    **“Code”** means the computer software code, whether in human-readable or machine-executable form,
>    that is delivered by You to Microsoft under this Agreement.
>    **“Project”** means any of the projects owned or managed by Microsoft and offered under a license
>    approved by the Open Source Initiative ([www.opensource.org](http://www.opensource.org)).
>    **“Submit”** is the act of uploading, submitting, transmitting, or distributing code or other content to any
>    Project, including but not limited to communication on electronic mailing lists, source code control
>    systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of
>    discussing and improving that Project, but excluding communication that is conspicuously marked or
>    otherwise designated in writing by You as “Not a Submission.”
>    **“Submission”** means the Code and any other copyrightable material Submitted by You, including any
>    associated comments and documentation.
> 2. **Your Submission**. You must agree to the terms of this Agreement before making a Submission to any
>    Project. This Agreement covers any and all Submissions that You, now or in the future (except as
>    described in Section 4 below), Submit to any Project.
> 3. **Originality of Work**. You represent that each of Your Submissions is entirely Your original work.
>    Should You wish to Submit materials that are not Your original work, You may Submit them separately
>    to the Project if You (a) retain all copyright and license information that was in the materials as You
>    received them, (b) in the description accompanying Your Submission, include the phrase “Submission
>    containing materials of a third party:” followed by the names of the third party and any licenses or other
>    restrictions of which You are aware, and (c) follow any other instructions in the Project’s written
>    guidelines concerning Submissions.
> 4. **Your Employer**. References to “employer” in this Agreement include Your employer or anyone else
>    for whom You are acting in making Your Submission, e.g. as a contractor, vendor, or agent. If Your
>    Submission is made in the course of Your work for an employer or Your employer has intellectual
>    property rights in Your Submission by contract or applicable law, You must secure permission from Your
>    employer to make the Submission before signing this Agreement. In that case, the term “You” in this
>    Agreement will refer to You and the employer collectively. If You change employers in the future and
>    desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement
>    and secure permission from the new employer before Submitting those Submissions.
> 5. **Licenses**.
> 
> * **Copyright License**. You grant Microsoft, and those who receive the Submission directly or
>   indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license in the
>   Submission to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute
>   the Submission and such derivative works, and to sublicense any or all of the foregoing rights to third
>   parties.
> * **Patent License**. You grant Microsoft, and those who receive the Submission directly or
>   indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license under
>   Your patent claims that are necessarily infringed by the Submission or the combination of the
>   Submission with the Project to which it was Submitted to make, have made, use, offer to sell, sell and
>   import or otherwise dispose of the Submission alone or with the Project.
> * **Other Rights Reserved**. Each party reserves all rights not expressly granted in this Agreement.
>   No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are
>   granted by implication, exhaustion, estoppel or otherwise.
> 
> 6. **Representations and Warranties**. You represent that You are legally entitled to grant the above
>    licenses. You represent that each of Your Submissions is entirely Your original work (except as You may
>    have disclosed under Section 3). You represent that You have secured permission from Your employer to
>    make the Submission in cases where Your Submission is made in the course of Your work for Your
>    employer or Your employer has intellectual property rights in Your Submission by contract or applicable
>    law. If You are signing this Agreement on behalf of Your employer, You represent and warrant that You
>    have the necessary authority to bind the listed employer to the obligations contained in this Agreement.
>    You are not expected to provide support for Your Submission, unless You choose to do so. UNLESS
>    REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES
>    EXPRESSLY STATED IN SECTIONS 3, 4, AND 6, THE SUBMISSION PROVIDED UNDER THIS AGREEMENT IS
>    PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
>    NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
> 7. **Notice to Microsoft**. You agree to notify Microsoft in writing of any facts or circumstances of which
>    You later become aware that would make Your representations in this Agreement inaccurate in any
>    respect.
> 8. **Information about Submissions**. You agree that contributions to Projects and information about
>    contributions may be maintained indefinitely and disclosed publicly, including Your name and other
>    information that You submit with Your Submission.
> 9. **Governing Law/Jurisdiction**. This Agreement is governed by the laws of the State of Washington, and
>    the parties consent to exclusive jurisdiction and venue in the federal courts sitting in King County,
>    Washington, unless no federal subject matter jurisdiction exists, in which case the parties consent to
>    exclusive jurisdiction and venue in the Superior Court of King County, Washington. The parties waive all
>    defenses of lack of personal jurisdiction and forum non-conveniens.
> 10. **Entire Agreement/Assignment**. This Agreement is the entire agreement between the parties, and
>     supersedes any and all prior agreements, understandings or communications, written or oral, between
>     the parties relating to the subject matter hereof. This Agreement may be assigned by Microsoft.

@microsoft-github-policy-service agree
author_association: CONTRIBUTOR
user: login: daxian-dbw
created_at: 2025-03-11T00:20:50Z
body: @kborowinski @cnupy After upgrading to the v0.5.13 of Ollama, the `OllamaAgent` stops working even from the main branch ... After sending a query, it's just spinning forever, and after cancel the request, the server side displays the following record:

![image](https://github.com/user-attachments/assets/86f84433-7298-4f97-aaa7-971e5b430531)

I moved to `OllamaSharp` [v5.1.4](https://www.nuget.org/packages/OllamaSharp/5.1.4), but it's the same result. Any idea what could be the problem?
author_association: MEMBER
user: login: kborowinski
created_at: 2025-03-11T06:44:30Z
body: @daxian-dbw I'm on latest `AIShell` build from *main* and `Ollama v0.5.13` with `llama3.1:8b-instruct-q5_K_M` and it works. What model are you running? Has anything changed on your computer where you run `AIShell` like drivers? How your config looks like?

![Animation](https://github.com/user-attachments/assets/ce03cab8-7a8a-494d-91fc-f158f5b55184)

And this is different computer, graphic card and model `phi4-mini:3.8b-q4_K_M`, and it works as well:

![Animation](https://github.com/user-attachments/assets/f95befff-8808-475d-8ba2-722b11d81b69)

Try to stop all `Ollama` processes and then start it again:
```powershell
Get-Process ollama* | Stop-Process -Force
```

author_association: CONTRIBUTOR
user: login: cnupy
created_at: 2025-03-11T10:11:52Z
body: Everything's working fine for me as well. I'm using Ollama 0.5.4
```shell
/llm/ollama# ./ollama -v
ggml_sycl_init: found 1 SYCL devices:
ollama version is 0.5.4-ipexllm-20250310


author_association: CONTRIBUTOR
user: login: daxian-dbw
created_at: 2025-04-02T00:35:47Z
body: @cnupy Thanks for the quick updates. Now only those 4 remaining comments are relevant (I left follow-up comments). All rest looks good.
author_association: MEMBER
user: login: cnupy
created_at: 2025-04-05T17:21:35Z
body: > @cnupy Thanks for the quick updates. Now only those 4 remaining comments are relevant (I left follow-up comments). All rest looks good.

I've resolved the four remaining comments. Additionally, I renamed the `Configs` property to `Presets` for better clarity. I've also carried out some additional refactoring to properly implement the running model checking. Furthermore, I moved and optimized `PerformSelfcheck` from `OllamaAgent` to `Settings` so that it can be utilized in both locations. Let me know if there's anything else you'd like adjusted.
author_association: CONTRIBUTOR
user: login: daxian-dbw
created_at: 2025-04-23T21:30:17Z
body: @cnupy To be more efficient, I went ahead and made some refactoring directly. The main changs are:
1. Update `PerformSelfcheck` to account for `RunningConfig` check as well, while making it possible to do endpoint check only too. With this change, we will only need a call to `PerformSelfcheck`, and then we can directly use `_settings.RunningConfig.ModelName`.
2. Update `EnsureModelsInitialized` to not do the endpoint check when the passed-in host is null.
3. Use `async` actions for commands where they are applicable.

Please review my changes and see if you have any concerns. Thanks!
author_association: MEMBER
user: login: daxian-dbw
created_at: 2025-04-23T21:38:32Z
body: BTW, the Ollama server writes out a warning about using the `context` field:
> [GIN] 2025/04/23 - 13:05:47 | 200 |    9.6578815s |       127.0.0.1 | POST     "/api/generate"
time=2025-04-23T13:06:59.988-07:00 level=WARN source=routes.go:274 msg="the context field is deprecated and will be removed in a future version of Ollama"

So, the ollama agent code will need to be updated accordingly to avoid breaking (tracked by #375).
author_association: MEMBER
user: login: cnupy
created_at: 2025-04-24T07:49:05Z
body: > the context field is deprecated and will be removed in a future version of Ollama

Yes, the correct way is to use the `chat` endpoint instead of the `generate` endpoint (ChatAsync method in the OllamaSharp client), as mentioned [previously](https://github.com/PowerShell/AIShell/pull/351#discussion_r2022295491).
author_association: CONTRIBUTOR
user: login: daxian-dbw
created_at: 2025-04-25T17:48:04Z
body: @cnupy Thank you for the contribution!
author_association: MEMBER