diff --git a/src/OpenClaw.Companion/App.axaml.cs b/src/OpenClaw.Companion/App.axaml.cs index 19636a6..c80a144 100644 --- a/src/OpenClaw.Companion/App.axaml.cs +++ b/src/OpenClaw.Companion/App.axaml.cs @@ -1,59 +1,39 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Data.Core; -using Avalonia.Data.Core.Plugins; -using System.Linq; -using Avalonia.Markup.Xaml; -using OpenClaw.Companion.ViewModels; -using OpenClaw.Companion.Views; -using OpenClaw.Companion.Services; - -namespace OpenClaw.Companion; - -public partial class App : Application -{ - private GatewayWebSocketClient? _client; - - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - } - - public override void OnFrameworkInitializationCompleted() - { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - // Avoid duplicate validations from both Avalonia and the CommunityToolkit. - // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins - DisableAvaloniaDataAnnotationValidation(); - - _client = new GatewayWebSocketClient(); - var settings = new SettingsStore(); - desktop.MainWindow = new MainWindow - { - DataContext = new MainWindowViewModel(settings, _client), - }; - - desktop.Exit += async (_, _) => - { - if (_client is not null) - await _client.DisposeAsync(); - }; - } - - base.OnFrameworkInitializationCompleted(); - } +using Avalonia.Markup.Xaml; +using OpenClaw.Companion.ViewModels; +using OpenClaw.Companion.Views; +using OpenClaw.Companion.Services; - private void DisableAvaloniaDataAnnotationValidation() +namespace OpenClaw.Companion; + +public partial class App : Application +{ + private GatewayWebSocketClient? _client; + + public override void Initialize() { - // Get an array of plugins to remove - var dataValidationPluginsToRemove = - BindingPlugins.DataValidators.OfType().ToArray(); + AvaloniaXamlLoader.Load(this); + } - // remove each entry found - foreach (var plugin in dataValidationPluginsToRemove) + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - BindingPlugins.DataValidators.Remove(plugin); + _client = new GatewayWebSocketClient(); + var settings = new SettingsStore(); + desktop.MainWindow = new MainWindow + { + DataContext = new MainWindowViewModel(settings, _client), + }; + + desktop.Exit += async (_, _) => + { + if (_client is not null) + await _client.DisposeAsync(); + }; } + + base.OnFrameworkInitializationCompleted(); } -} +} \ No newline at end of file diff --git a/src/OpenClaw.Companion/OpenClaw.Companion.csproj b/src/OpenClaw.Companion/OpenClaw.Companion.csproj index bc62c82..d33e59e 100644 --- a/src/OpenClaw.Companion/OpenClaw.Companion.csproj +++ b/src/OpenClaw.Companion/OpenClaw.Companion.csproj @@ -18,12 +18,12 @@ - - - - + + + + - + None All @@ -31,5 +31,6 @@ + diff --git a/src/OpenClaw.Gateway/Endpoints/AdminEndpoints.cs b/src/OpenClaw.Gateway/Endpoints/AdminEndpoints.cs index 15ce76b..a24d910 100644 --- a/src/OpenClaw.Gateway/Endpoints/AdminEndpoints.cs +++ b/src/OpenClaw.Gateway/Endpoints/AdminEndpoints.cs @@ -1380,29 +1380,7 @@ operations.ModelProfiles as ConfiguredModelProfileRegistry using var subscription = authEventStore.Subscribe(); var ct = ctx.RequestAborted; - // Send current state as first event - var currentItems = accountId is not null - ? authEventStore.GetLatest(channelId, accountId) is { } currentEvt ? [currentEvt] : [] - : authEventStore.GetAll(channelId); - foreach (var current in currentItems) - { - var json = JsonSerializer.Serialize(MapChannelAuthStatusItem(current), CoreJsonContext.Default.ChannelAuthStatusItem); - await ctx.Response.WriteAsync($"data: {json}\n\n", ct); - await ctx.Response.Body.FlushAsync(ct); - } - - // Stream subsequent events - await foreach (var evt in subscription.Reader.ReadAllAsync(ct)) - { - if (!string.Equals(evt.ChannelId, channelId, StringComparison.Ordinal)) - continue; - if (accountId is not null && !string.Equals(evt.AccountId, accountId, StringComparison.Ordinal)) - continue; - - var evtJson = JsonSerializer.Serialize(MapChannelAuthStatusItem(evt), CoreJsonContext.Default.ChannelAuthStatusItem); - await ctx.Response.WriteAsync($"data: {evtJson}\n\n", ct); - await ctx.Response.Body.FlushAsync(ct); - } + await StreamChannelAuthEventsAsync(ctx, subscription, authEventStore, channelId, accountId, ct); }); app.MapGet("/admin/channels/whatsapp/auth", (HttpContext ctx, string? accountId) => @@ -1439,27 +1417,7 @@ operations.ModelProfiles as ConfiguredModelProfileRegistry using var subscription = authEventStore.Subscribe(); var ct = ctx.RequestAborted; - var currentItems = accountId is not null - ? authEventStore.GetLatest("whatsapp", accountId) is { } currentEvt ? [currentEvt] : [] - : authEventStore.GetAll("whatsapp"); - foreach (var current in currentItems) - { - var json = JsonSerializer.Serialize(MapChannelAuthStatusItem(current), CoreJsonContext.Default.ChannelAuthStatusItem); - await ctx.Response.WriteAsync($"data: {json}\n\n", ct); - await ctx.Response.Body.FlushAsync(ct); - } - - await foreach (var evt in subscription.Reader.ReadAllAsync(ct)) - { - if (!string.Equals(evt.ChannelId, "whatsapp", StringComparison.Ordinal)) - continue; - if (accountId is not null && !string.Equals(evt.AccountId, accountId, StringComparison.Ordinal)) - continue; - - var evtJson = JsonSerializer.Serialize(MapChannelAuthStatusItem(evt), CoreJsonContext.Default.ChannelAuthStatusItem); - await ctx.Response.WriteAsync($"data: {evtJson}\n\n", ct); - await ctx.Response.Body.FlushAsync(ct); - } + await StreamChannelAuthEventsAsync(ctx, subscription, authEventStore, "whatsapp", accountId, ct); }); app.MapGet("/admin/channels/whatsapp/auth/qr.svg", (HttpContext ctx, string? accountId) => @@ -1739,6 +1697,55 @@ private static ChannelAuthStatusItem MapChannelAuthStatusItem(BridgeChannelAuthE UpdatedAtUtc = evt.UpdatedAtUtc }; + private static async Task StreamChannelAuthEventsAsync( + HttpContext ctx, + ChannelAuthEventStore.AuthEventSubscription subscription, + ChannelAuthEventStore authEventStore, + string channelId, + string? accountId, + CancellationToken ct) + { + try + { + await ctx.Response.WriteAsync(": connected\n\n", ct); + await ctx.Response.Body.FlushAsync(ct); + + var currentItems = accountId is not null + ? authEventStore.GetLatest(channelId, accountId) is { } currentEvt ? [currentEvt] : [] + : authEventStore.GetAll(channelId); + foreach (var current in currentItems) + { + await WriteChannelAuthSseEventAsync(ctx, current, ct); + } + + await foreach (var evt in subscription.Reader.ReadAllAsync(ct)) + { + if (!string.Equals(evt.ChannelId, channelId, StringComparison.Ordinal)) + continue; + if (accountId is not null && !string.Equals(evt.AccountId, accountId, StringComparison.Ordinal)) + continue; + + await WriteChannelAuthSseEventAsync(ctx, evt, ct); + } + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + return; + } + } + + private static Task WriteChannelAuthSseEventAsync(HttpContext ctx, BridgeChannelAuthEvent evt, CancellationToken ct) + { + var json = JsonSerializer.Serialize(MapChannelAuthStatusItem(evt), CoreJsonContext.Default.ChannelAuthStatusItem); + return WriteSsePayloadAsync(ctx, $"data: {json}\n\n", ct); + } + + private static async Task WriteSsePayloadAsync(HttpContext ctx, string payload, CancellationToken ct) + { + await ctx.Response.WriteAsync(payload, ct); + await ctx.Response.Body.FlushAsync(ct); + } + private static WhatsAppSetupResponse BuildWhatsAppSetupResponse( GatewayStartupContext startup, GatewayAppRuntime runtime, diff --git a/src/OpenClaw.Gateway/GatewayLlmExecutionService.cs b/src/OpenClaw.Gateway/GatewayLlmExecutionService.cs index 60768ae..c3c46a8 100644 --- a/src/OpenClaw.Gateway/GatewayLlmExecutionService.cs +++ b/src/OpenClaw.Gateway/GatewayLlmExecutionService.cs @@ -44,8 +44,7 @@ private sealed class RouteState public GatewayLlmExecutionService( GatewayConfig config, - ConfiguredModelProfileRegistry modelProfiles, - IModelSelectionPolicy selectionPolicy, + LlmProviderRegistry providerRegistry, ProviderPolicyService policyService, RuntimeEventStore eventStore, RuntimeMetrics runtimeMetrics, @@ -53,8 +52,7 @@ public GatewayLlmExecutionService( ILogger logger) : this( config, - modelProfiles, - selectionPolicy, + CreateCompatibilityServices(config, providerRegistry), policyService, eventStore, runtimeMetrics, @@ -67,8 +65,7 @@ public GatewayLlmExecutionService( public GatewayLlmExecutionService( GatewayConfig config, - ConfiguredModelProfileRegistry modelProfiles, - IModelSelectionPolicy selectionPolicy, + LlmProviderRegistry providerRegistry, ProviderPolicyService policyService, RuntimeEventStore eventStore, RuntimeMetrics runtimeMetrics, @@ -76,22 +73,23 @@ public GatewayLlmExecutionService( PromptCacheCoordinator promptCacheCoordinator, PromptCacheWarmRegistry promptCacheWarmRegistry, ILogger logger) + : this( + config, + CreateCompatibilityServices(config, providerRegistry), + policyService, + eventStore, + runtimeMetrics, + providerUsage, + promptCacheCoordinator, + promptCacheWarmRegistry, + logger) { - _config = config; - _modelProfiles = modelProfiles; - _selectionPolicy = selectionPolicy; - _policyService = policyService; - _eventStore = eventStore; - _runtimeMetrics = runtimeMetrics; - _providerUsage = providerUsage; - _promptCacheCoordinator = promptCacheCoordinator; - _promptCacheWarmRegistry = promptCacheWarmRegistry; - _logger = logger; } public GatewayLlmExecutionService( GatewayConfig config, - LlmProviderRegistry registry, + ConfiguredModelProfileRegistry modelProfiles, + IModelSelectionPolicy selectionPolicy, ProviderPolicyService policyService, RuntimeEventStore eventStore, RuntimeMetrics runtimeMetrics, @@ -99,7 +97,8 @@ public GatewayLlmExecutionService( ILogger logger) : this( config, - registry, + modelProfiles, + selectionPolicy, policyService, eventStore, runtimeMetrics, @@ -110,9 +109,9 @@ public GatewayLlmExecutionService( { } - public GatewayLlmExecutionService( + private GatewayLlmExecutionService( GatewayConfig config, - LlmProviderRegistry registry, + CompatibilityServices compatibilityServices, ProviderPolicyService policyService, RuntimeEventStore eventStore, RuntimeMetrics runtimeMetrics, @@ -122,7 +121,8 @@ public GatewayLlmExecutionService( ILogger logger) : this( config, - CreateCompatibilityServices(config, registry), + compatibilityServices.Registry, + compatibilityServices.SelectionPolicy, policyService, eventStore, runtimeMetrics, @@ -133,9 +133,10 @@ public GatewayLlmExecutionService( { } - private GatewayLlmExecutionService( + public GatewayLlmExecutionService( GatewayConfig config, - CompatibilityServices compatibility, + ConfiguredModelProfileRegistry modelProfiles, + IModelSelectionPolicy selectionPolicy, ProviderPolicyService policyService, RuntimeEventStore eventStore, RuntimeMetrics runtimeMetrics, @@ -143,18 +144,17 @@ private GatewayLlmExecutionService( PromptCacheCoordinator promptCacheCoordinator, PromptCacheWarmRegistry promptCacheWarmRegistry, ILogger logger) - : this( - config, - compatibility.Registry, - compatibility.SelectionPolicy, - policyService, - eventStore, - runtimeMetrics, - providerUsage, - promptCacheCoordinator, - promptCacheWarmRegistry, - logger) { + _config = config; + _modelProfiles = modelProfiles; + _selectionPolicy = selectionPolicy; + _policyService = policyService; + _eventStore = eventStore; + _runtimeMetrics = runtimeMetrics; + _providerUsage = providerUsage; + _promptCacheCoordinator = promptCacheCoordinator; + _promptCacheWarmRegistry = promptCacheWarmRegistry; + _logger = logger; } public CircuitState DefaultCircuitState diff --git a/src/OpenClaw.Tests/GatewayAdminEndpointTests.cs b/src/OpenClaw.Tests/GatewayAdminEndpointTests.cs index aecf0e2..28ab43e 100644 --- a/src/OpenClaw.Tests/GatewayAdminEndpointTests.cs +++ b/src/OpenClaw.Tests/GatewayAdminEndpointTests.cs @@ -1,21 +1,16 @@ -using System.Collections.Concurrent; -using System.Net; -using System.Net.Http.Headers; -using System.Text.RegularExpressions; -using System.Text; -using System.Text.Json; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; +using ModelContextProtocol.AspNetCore; using NSubstitute; -using OpenClaw.Client; -using OpenClaw.Companion.Services; -using OpenClaw.Companion.ViewModels; using OpenClaw.Agent; using OpenClaw.Agent.Plugins; using OpenClaw.Channels; +using OpenClaw.Client; +using OpenClaw.Companion.Services; +using OpenClaw.Companion.ViewModels; using OpenClaw.Core.Abstractions; using OpenClaw.Core.Memory; using OpenClaw.Core.Middleware; @@ -27,11 +22,17 @@ using OpenClaw.Core.Sessions; using OpenClaw.Gateway; using OpenClaw.Gateway.Bootstrap; -using ModelContextProtocol.AspNetCore; using OpenClaw.Gateway.Composition; using OpenClaw.Gateway.Endpoints; using OpenClaw.Gateway.Extensions; using OpenClaw.Gateway.Mcp; +using OpenClaw.Gateway.Models; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; using Xunit; namespace OpenClaw.Tests; @@ -1854,6 +1855,8 @@ private static GatewayAppRuntime CreateRuntime( var approvalAuditStore = new ApprovalAuditStore(storagePath, NullLogger.Instance); var runtimeMetrics = new RuntimeMetrics(); var providerUsage = new ProviderUsageTracker(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); @@ -1865,11 +1868,12 @@ private static GatewayAppRuntime CreateRuntime( var pluginHealth = new PluginHealthService(storagePath, NullLogger.Instance); var llmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, - providerUsage, + providerUsage, NullLogger.Instance); var retentionCoordinator = Substitute.For(); retentionCoordinator.GetStatusAsync(Arg.Any()) diff --git a/src/OpenClaw.Tests/GatewayWorkersTests.cs b/src/OpenClaw.Tests/GatewayWorkersTests.cs index d997c31..c37cd42 100644 --- a/src/OpenClaw.Tests/GatewayWorkersTests.cs +++ b/src/OpenClaw.Tests/GatewayWorkersTests.cs @@ -1,8 +1,3 @@ -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Net; -using System.Text.Json; -using System.Threading.Channels; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; @@ -16,6 +11,12 @@ using OpenClaw.Core.Sessions; using OpenClaw.Gateway; using OpenClaw.Gateway.Extensions; +using OpenClaw.Gateway.Models; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Net; +using System.Text.Json; +using System.Threading.Channels; using Xunit; namespace OpenClaw.Tests; @@ -103,7 +104,9 @@ public async Task Start_LoopbackApprovalStillRequiresRequesterMatch() var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); - var providerRegistry = new LlmProviderRegistry(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); + var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); var operations = new RuntimeOperationsState @@ -112,7 +115,8 @@ public async Task Start_LoopbackApprovalStillRequiresRequesterMatch() ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, @@ -217,6 +221,8 @@ public async Task Start_ReusableApprovalGrant_BypassesPendingApprovalCreation() var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); @@ -237,7 +243,8 @@ public async Task Start_ReusableApprovalGrant_BypassesPendingApprovalCreation() ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, @@ -350,6 +357,8 @@ public async Task Start_RouteOverrides_AreAppliedToSessionBeforeRuntimeExecution var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); @@ -359,7 +368,8 @@ public async Task Start_RouteOverrides_AreAppliedToSessionBeforeRuntimeExecution ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, @@ -456,6 +466,8 @@ public async Task Start_StreamingVerboseFooter_IsSentBeforeAssistantDone() var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); @@ -465,7 +477,8 @@ public async Task Start_StreamingVerboseFooter_IsSentBeforeAssistantDone() ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, @@ -579,6 +592,8 @@ public async Task Start_ApprovalTimeout_RecordsTimedOutAuditAndRuntimeEvent() var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); @@ -588,7 +603,8 @@ public async Task Start_ApprovalTimeout_RecordsTimedOutAuditAndRuntimeEvent() ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, @@ -687,7 +703,10 @@ public async Task Start_ManagedHeartbeatOk_SuppressesDeliveryAndRecordsStatus() var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); + var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); var operations = new RuntimeOperationsState @@ -696,7 +715,8 @@ public async Task Start_ManagedHeartbeatOk_SuppressesDeliveryAndRecordsStatus() ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, @@ -791,6 +811,8 @@ public async Task Start_ManagedHeartbeatAlert_DeliversMessageAndRecordsStatus() var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); @@ -800,7 +822,8 @@ public async Task Start_ManagedHeartbeatAlert_DeliversMessageAndRecordsStatus() ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, @@ -901,6 +924,8 @@ public async Task Start_ManagedHeartbeatAlert_DeliveryFailureDoesNotMarkDelivere var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); @@ -910,7 +935,8 @@ public async Task Start_ManagedHeartbeatAlert_DeliveryFailureDoesNotMarkDelivere ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, @@ -1006,6 +1032,8 @@ public async Task Start_ManagedHeartbeatError_StaysInternalAndRecordsErrorStatus var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); @@ -1015,7 +1043,8 @@ public async Task Start_ManagedHeartbeatError_StaysInternalAndRecordsErrorStatus ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, @@ -1115,6 +1144,8 @@ public async Task Start_BridgedGroupMessage_UsesGroupIdForSessionReplyAndTyping( var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); @@ -1124,7 +1155,8 @@ public async Task Start_BridgedGroupMessage_UsesGroupIdForSessionReplyAndTyping( ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, @@ -1241,6 +1273,8 @@ public async Task Start_BridgedTypingCleanup_OnAgentFailure_SendsTypingStop() var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); @@ -1250,7 +1284,8 @@ public async Task Start_BridgedTypingCleanup_OnAgentFailure_SendsTypingStop() ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, @@ -1352,6 +1387,8 @@ public async Task Start_BridgedTypingCleanup_OnAgentCancellation_SendsTypingStop var pairingManager = new OpenClaw.Core.Security.PairingManager(storagePath, NullLogger.Instance); var commandProcessor = new ChatCommandProcessor(sessionManager); var runtimeMetrics = new OpenClaw.Core.Observability.RuntimeMetrics(); + var modelProfile = new ConfiguredModelProfileRegistry(config, NullLogger.Instance); + var modelselectionPolicy = new DefaultModelSelectionPolicy(modelProfile); var providerRegistry = new LlmProviderRegistry(); var providerPolicies = new ProviderPolicyService(storagePath, NullLogger.Instance); var runtimeEvents = new RuntimeEventStore(storagePath, NullLogger.Instance); @@ -1361,7 +1398,8 @@ public async Task Start_BridgedTypingCleanup_OnAgentCancellation_SendsTypingStop ProviderRegistry = providerRegistry, LlmExecution = new GatewayLlmExecutionService( config, - providerRegistry, + modelProfile, + modelselectionPolicy, providerPolicies, runtimeEvents, runtimeMetrics, diff --git a/src/OpenClaw.Tests/ModelProfileSelectionTests.cs b/src/OpenClaw.Tests/ModelProfileSelectionTests.cs index 9a42c58..f0de021 100644 --- a/src/OpenClaw.Tests/ModelProfileSelectionTests.cs +++ b/src/OpenClaw.Tests/ModelProfileSelectionTests.cs @@ -412,10 +412,22 @@ public async Task GatewayExecutionService_CompatibilityConstructor_UsesInjectedP { Provider = "fake-injected-provider", Model = "legacy-model" + }, + Models = new ModelsConfig + { + DefaultProfile = "default", + Profiles = new List + { + new ModelProfileConfig + { + Id = "default", + Provider = "fake-injected-provider", + Model = "legacy-model" + } + } } }; - - var providerRegistry = new LlmProviderRegistry(); + var providerRegistry = new LlmProviderRegistry(); providerRegistry.RegisterDefault(config.Llm, new EvaluationChatClient()); var service = new GatewayLlmExecutionService( config,