Files
2025-12-28 05:38:14 -05:00

158 lines
5.2 KiB
C#

using MarketAlly.AIPlugin;
using MarketAlly.AIPlugin.Agentic;
using MarketAlly.AIPlugin.Agentic.Models;
using MarketAlly.AIPlugin.Conversation;
using MarketAlly.GitCommitEditor.Options;
namespace MarketAlly.GitCommitEditor.Services;
/// <summary>
/// Service for managing AI model providers dynamically based on configured API keys.
/// Only shows providers that the user has actually configured.
/// </summary>
public interface IModelProviderService
{
/// <summary>
/// Gets the providers that have API keys configured.
/// </summary>
IReadOnlyList<string> AvailableProviders { get; }
/// <summary>
/// Gets models for a provider that support function calling.
/// </summary>
IReadOnlyList<string> GetModelsForProvider(string provider);
/// <summary>
/// Gets models with display information for a provider.
/// </summary>
IReadOnlyList<ModelDisplayInfo> GetModelsWithInfoForProvider(string provider);
/// <summary>
/// Gets model info by ID.
/// </summary>
ModelDisplayInfo? GetModelInfo(string modelId);
/// <summary>
/// Gets the API key for a provider.
/// </summary>
string? GetApiKey(AIProvider provider);
/// <summary>
/// Checks if a provider has an API key configured.
/// </summary>
bool HasApiKey(AIProvider provider);
/// <summary>
/// Refreshes the available providers based on current API key configuration.
/// Call this after adding or removing an API key.
/// </summary>
void RefreshProviders();
}
/// <summary>
/// Implementation of IModelProviderService that uses IApiKeyProvider to determine available providers.
/// Uses the static ModelLibrary.Default for efficient querying.
/// </summary>
public class ModelProviderService : IModelProviderService
{
private readonly IApiKeyProvider _apiKeyProvider;
public ModelProviderService(IApiKeyProvider apiKeyProvider)
{
_apiKeyProvider = apiKeyProvider;
}
public IReadOnlyList<string> AvailableProviders =>
ModelLibrary.Default.GetModelsWithFunctionCalling()
.Where(m => m.Type != ModelType.Legacy)
.Select(m => m.Provider.ToString())
.Distinct()
.OrderBy(p => p)
.ToList();
public void RefreshProviders()
{
// No-op - static library doesn't need refreshing
// API key changes are handled separately by IApiKeyProvider
}
public IReadOnlyList<string> GetModelsForProvider(string provider)
{
var aiProvider = ParseProvider(provider);
var models = ModelLibrary.Default.GetModelsByProvider(aiProvider)
.Where(m => m.SupportsFunctionCalling && m.Type != ModelType.Legacy)
.OrderByDescending(m => m.Tier)
.ThenBy(m => m.DisplayName)
.Select(m => m.ModelId)
.ToList();
return models.Count > 0 ? models : new[] { ModelConstants.Claude.Sonnet4 };
}
public IReadOnlyList<ModelDisplayInfo> GetModelsWithInfoForProvider(string provider)
{
var aiProvider = ParseProvider(provider);
var models = ModelLibrary.Default.GetModelsByProvider(aiProvider)
.Where(m => m.SupportsFunctionCalling && m.Type != ModelType.Legacy)
.OrderByDescending(m => m.Tier)
.ThenBy(m => m.DisplayName)
.Select(m => new ModelDisplayInfo
{
ModelId = m.ModelId,
DisplayName = m.DisplayName,
Tier = m.Tier.ToString(),
InputCostPer1MTokens = m.InputCostPer1MTokens,
OutputCostPer1MTokens = m.OutputCostPer1MTokens,
Speed = m.Speed.ToString()
})
.ToList();
return models.Count > 0 ? models : GetDefaultModelInfo();
}
public ModelDisplayInfo? GetModelInfo(string modelId)
{
var model = ModelLibrary.Default.GetModel(modelId);
if (model == null) return null;
return new ModelDisplayInfo
{
ModelId = model.ModelId,
DisplayName = model.DisplayName,
Tier = model.Tier.ToString(),
InputCostPer1MTokens = model.InputCostPer1MTokens,
OutputCostPer1MTokens = model.OutputCostPer1MTokens,
Speed = model.Speed.ToString()
};
}
public string? GetApiKey(AIProvider provider) => _apiKeyProvider.GetApiKey(provider);
public bool HasApiKey(AIProvider provider) => _apiKeyProvider.HasApiKey(provider);
private static IReadOnlyList<ModelDisplayInfo> GetDefaultModelInfo() => new[]
{
new ModelDisplayInfo
{
ModelId = ModelConstants.Claude.Sonnet4,
DisplayName = "Claude Sonnet 4",
Tier = "Balanced",
InputCostPer1MTokens = 3.00m,
OutputCostPer1MTokens = 15.00m,
Speed = "Fast"
}
};
private static AIProvider ParseProvider(string? provider)
{
return provider?.ToLowerInvariant() switch
{
"claude" or "anthropic" => AIProvider.Claude,
"openai" or "gpt" => AIProvider.OpenAI,
"gemini" or "google" => AIProvider.Gemini,
"qwen" or "alibaba" => AIProvider.Qwen,
_ => AIProvider.Claude
};
}
}