diff --git a/src/Cellm.Tests/Integration/Helpers/ProviderTestFixture.cs b/src/Cellm.Tests/Integration/Helpers/ProviderTestFixture.cs index 1a933f9..0bac227 100644 --- a/src/Cellm.Tests/Integration/Helpers/ProviderTestFixture.cs +++ b/src/Cellm.Tests/Integration/Helpers/ProviderTestFixture.cs @@ -13,6 +13,7 @@ using Cellm.Models.Providers.OpenAi; using Cellm.Models.Providers.OpenAiCompatible; using Cellm.Models.Providers.OpenRouter; +using Cellm.Models.Providers.Requesty; using Cellm.Tools.ModelContextProtocol; using MediatR; using Microsoft.Extensions.Configuration; @@ -85,6 +86,7 @@ public bool IsProviderAvailable(Provider provider) Provider.OpenAi => HasApiKey(), Provider.OpenAiCompatible => HasApiKey(), Provider.OpenRouter => HasApiKey(), + Provider.Requesty => HasApiKey(), _ => false }; } @@ -101,6 +103,7 @@ public string GetDefaultModel(Provider provider) Provider.OpenAi => GetConfig().DefaultModel, Provider.OpenAiCompatible => GetConfig().DefaultModel, Provider.OpenRouter => GetConfig().DefaultModel, + Provider.Requesty => GetConfig().DefaultModel, _ => throw new ArgumentException($"Unknown provider: {provider}") }; } diff --git a/src/Cellm.Tests/Unit/PipelineTests.cs b/src/Cellm.Tests/Unit/PipelineTests.cs index 2f8f13c..0e619dc 100644 --- a/src/Cellm.Tests/Unit/PipelineTests.cs +++ b/src/Cellm.Tests/Unit/PipelineTests.cs @@ -29,6 +29,7 @@ public PipelineTests(PipelineTestFixture fixture) [Provider.DeepSeek, "deepseek-chat"], [Provider.Ollama, "llama3.2"], [Provider.OpenRouter, "openai/gpt-4.1-mini"], + [Provider.Requesty, "openai/gpt-4o-mini"], ]; [Theory] diff --git a/src/Cellm/AddIn/CellmAddIn.cs b/src/Cellm/AddIn/CellmAddIn.cs index 484eeeb..14c8ad3 100644 --- a/src/Cellm/AddIn/CellmAddIn.cs +++ b/src/Cellm/AddIn/CellmAddIn.cs @@ -16,6 +16,7 @@ using Cellm.Models.Providers.OpenAi; using Cellm.Models.Providers.OpenAiCompatible; using Cellm.Models.Providers.OpenRouter; +using Cellm.Models.Providers.Requesty; using Cellm.Models.Resilience; using Cellm.Tools; using Cellm.Tools.FileReader; @@ -101,6 +102,7 @@ internal static ServiceCollection ConfigureServices(ServiceCollection services, .Configure(configuration.GetRequiredSection(nameof(OpenAiConfiguration))) .Configure(configuration.GetRequiredSection(nameof(OpenAiCompatibleConfiguration))) .Configure(configuration.GetRequiredSection(nameof(OpenRouterConfiguration))) + .Configure(configuration.GetRequiredSection(nameof(RequestyConfiguration))) .Configure(configuration.GetRequiredSection(nameof(ResilienceConfiguration))) .Configure(configuration.GetRequiredSection(nameof(SentryConfiguration))); @@ -185,7 +187,8 @@ internal static ServiceCollection ConfigureServices(ServiceCollection services, .AddResilientHttpClient(resilienceConfiguration, cellmAddInConfiguration, Provider.Mistral) .AddResilientHttpClient(resilienceConfiguration, cellmAddInConfiguration, Provider.Ollama) .AddResilientHttpClient(resilienceConfiguration, cellmAddInConfiguration, Provider.OpenAiCompatible) - .AddResilientHttpClient(resilienceConfiguration, cellmAddInConfiguration, Provider.OpenRouter); + .AddResilientHttpClient(resilienceConfiguration, cellmAddInConfiguration, Provider.OpenRouter) + .AddResilientHttpClient(resilienceConfiguration, cellmAddInConfiguration, Provider.Requesty); #pragma warning disable EXTEXP0018 // Type is for evaluation purposes only and is subject to change or removal in future updates. services @@ -205,7 +208,8 @@ internal static ServiceCollection ConfigureServices(ServiceCollection services, .AddOllamaChatClient() .AddOpenAiChatClient() .AddOpenAiCompatibleChatClient() - .AddOpenRouterChatClient(); + .AddOpenRouterChatClient() + .AddRequestyChatClient(); // Add tools services @@ -253,7 +257,8 @@ internal static IEnumerable GetProviderConfigurations() Services.GetRequiredService>().CurrentValue, Services.GetRequiredService>().CurrentValue, Services.GetRequiredService>().CurrentValue, - Services.GetRequiredService>().CurrentValue + Services.GetRequiredService>().CurrentValue, + Services.GetRequiredService>().CurrentValue ]; } diff --git a/src/Cellm/AddIn/UserInterface/Resources/Requesty.svg b/src/Cellm/AddIn/UserInterface/Resources/Requesty.svg new file mode 100644 index 0000000..df91617 --- /dev/null +++ b/src/Cellm/AddIn/UserInterface/Resources/Requesty.svg @@ -0,0 +1 @@ + diff --git a/src/Cellm/AddIn/UserInterface/Ribbon/RibbonModelGroup.cs b/src/Cellm/AddIn/UserInterface/Ribbon/RibbonModelGroup.cs index 6a9c4d4..5a0ba9d 100644 --- a/src/Cellm/AddIn/UserInterface/Ribbon/RibbonModelGroup.cs +++ b/src/Cellm/AddIn/UserInterface/Ribbon/RibbonModelGroup.cs @@ -13,6 +13,7 @@ using Cellm.Models.Providers.OpenAi; using Cellm.Models.Providers.OpenAiCompatible; using Cellm.Models.Providers.OpenRouter; +using Cellm.Models.Providers.Requesty; using Cellm.Users; using ExcelDna.Integration.CustomUI; using Microsoft.Extensions.AI; @@ -540,6 +541,9 @@ public void ShowProviderSettingsForm(IRibbonControl control) case Provider.OpenRouter: currentBaseAddress = GetProviderConfiguration()?.BaseAddress?.ToString() ?? ""; break; + case Provider.Requesty: + currentBaseAddress = GetProviderConfiguration()?.BaseAddress?.ToString() ?? ""; + break; default: break; } diff --git a/src/Cellm/Cellm.csproj b/src/Cellm/Cellm.csproj index 965439a..18006db 100644 --- a/src/Cellm/Cellm.csproj +++ b/src/Cellm/Cellm.csproj @@ -29,6 +29,7 @@ + @@ -94,5 +95,6 @@ + diff --git a/src/Cellm/Models/Providers/Provider.cs b/src/Cellm/Models/Providers/Provider.cs index f6b5f7b..832e1b2 100644 --- a/src/Cellm/Models/Providers/Provider.cs +++ b/src/Cellm/Models/Providers/Provider.cs @@ -12,5 +12,6 @@ public enum Provider Ollama, OpenAi, OpenAiCompatible, - OpenRouter + OpenRouter, + Requesty } diff --git a/src/Cellm/Models/Providers/Requesty/RequestyConfiguration.cs b/src/Cellm/Models/Providers/Requesty/RequestyConfiguration.cs new file mode 100644 index 0000000..19158e8 --- /dev/null +++ b/src/Cellm/Models/Providers/Requesty/RequestyConfiguration.cs @@ -0,0 +1,35 @@ +using Cellm.Users; +using Microsoft.Extensions.AI; + +namespace Cellm.Models.Providers.Requesty; + +internal class RequestyConfiguration : IProviderConfiguration +{ + public Provider Id { get => Provider.Requesty; } + + public string Name { get => "Requesty"; } + + public Entitlement Entitlement { get => Entitlement.EnableRequestyProvider; } + + public string Icon { get => $"AddIn/UserInterface/Resources/{nameof(Provider.Requesty)}.svg"; } + + public Uri BaseAddress => new("https://router.requesty.ai/v1"); + + public string DefaultModel { get; init; } = "openai/gpt-4o-mini"; + + public string ApiKey { get; init; } = string.Empty; + + public string SmallModel { get; init; } = string.Empty; + + public string MediumModel { get; init; } = string.Empty; + + public string LargeModel { get; init; } = string.Empty; + + public AdditionalPropertiesDictionary? AdditionalProperties { get; init; } = []; + + public bool SupportsJsonSchemaResponses { get; init; } = true; + + public bool SupportsStructuredOutputWithTools { get; init; } = true; + + public bool IsEnabled { get; init; } = false; +} diff --git a/src/Cellm/Models/ServiceCollectionExtensions.cs b/src/Cellm/Models/ServiceCollectionExtensions.cs index 31b26ad..9063ec3 100644 --- a/src/Cellm/Models/ServiceCollectionExtensions.cs +++ b/src/Cellm/Models/ServiceCollectionExtensions.cs @@ -22,6 +22,7 @@ using Cellm.Models.Providers.OpenAi; using Cellm.Models.Providers.OpenAiCompatible; using Cellm.Models.Providers.OpenRouter; +using Cellm.Models.Providers.Requesty; using Cellm.Models.Resilience; using Cellm.Users; using Microsoft.Extensions.AI; @@ -480,6 +481,37 @@ public static IServiceCollection AddOpenRouterChatClient(this IServiceCollection return services; } + public static IServiceCollection AddRequestyChatClient(this IServiceCollection services) + { + services + .AddKeyedChatClient(Provider.Requesty, serviceProvider => + { + var account = serviceProvider.GetRequiredService(); + account.ThrowIfNotEntitled(Entitlement.EnableRequestyProvider); + + var requestyConfiguration = serviceProvider.GetRequiredService>(); + var resilientHttpClient = serviceProvider.GetResilientHttpClient(Provider.Requesty); + + if (string.IsNullOrWhiteSpace(requestyConfiguration.CurrentValue.ApiKey)) + { + throw new CellmException($"Empty {nameof(RequestyConfiguration.ApiKey)} for {Provider.Requesty}. Please set your API key."); + } + + var openAiClient = new OpenAIClient( + new ApiKeyCredential(requestyConfiguration.CurrentValue.ApiKey), + new OpenAIClientOptions + { + Transport = new HttpClientPipelineTransport(resilientHttpClient), + Endpoint = requestyConfiguration.CurrentValue.BaseAddress + }); + + return openAiClient.GetChatClient(requestyConfiguration.CurrentValue.DefaultModel).AsIChatClient(); + }, ServiceLifetime.Transient) + .UseFunctionInvocation(); + + return services; + } + public static IServiceCollection AddTools(this IServiceCollection services, params Delegate[] tools) { diff --git a/src/Cellm/Users/Entitlement.cs b/src/Cellm/Users/Entitlement.cs index 173fa72..6a98c45 100644 --- a/src/Cellm/Users/Entitlement.cs +++ b/src/Cellm/Users/Entitlement.cs @@ -15,6 +15,7 @@ public enum Entitlement EnableOpenAiCompatibleProviderLocalModels, EnableOpenAiCompatibleProviderHostedModels, EnableOpenRouterProvider, + EnableRequestyProvider, EnableModelContextProtocol, DisableTelemetry } diff --git a/src/Cellm/Users/Models/Entitlements.cs b/src/Cellm/Users/Models/Entitlements.cs index f548dd9..96591a7 100644 --- a/src/Cellm/Users/Models/Entitlements.cs +++ b/src/Cellm/Users/Models/Entitlements.cs @@ -24,7 +24,8 @@ internal class Entitlements() Entitlement.EnableOpenAiCompatibleProvider, Entitlement.EnableOpenAiCompatibleProviderHostedModels, Entitlement.EnableOpenAiCompatibleProviderLocalModels, - Entitlement.EnableOpenRouterProvider + Entitlement.EnableOpenRouterProvider, + Entitlement.EnableRequestyProvider ]; public IEnumerable AsEnumerable() diff --git a/src/Cellm/appsettings.json b/src/Cellm/appsettings.json index ec2df1b..b45ca23 100644 --- a/src/Cellm/appsettings.json +++ b/src/Cellm/appsettings.json @@ -113,6 +113,15 @@ "LargeModel": "anthropic/claude-opus-4-6", "IsEnabled": true }, + "RequestyConfiguration": { + "BaseAddress": "https://router.requesty.ai/v1", + "DefaultModel": "openai/gpt-4o-mini", + "ApiKey": "", + "SmallModel": "openai/gpt-4o-mini", + "MediumModel": "openai/gpt-4o", + "LargeModel": "anthropic/claude-sonnet-4-5", + "IsEnabled": true + }, "ModelContextProtocolConfiguration": { "StdioServers": [ {