From f1b461e26afacc51707b58ee958114686d91d5c7 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Fri, 10 Apr 2026 14:59:39 +0800 Subject: [PATCH] Add dynamic bridge provider picker sources --- lib/app/app_controller_desktop_core.dart | 21 +++++--- ...ler_desktop_runtime_coordination_impl.dart | 13 +++-- ...pp_controller_desktop_thread_sessions.dart | 4 +- .../acp_bridge_provider_hub_suite.dart | 54 +++++++++++++++++++ 4 files changed, 81 insertions(+), 11 deletions(-) diff --git a/lib/app/app_controller_desktop_core.dart b/lib/app/app_controller_desktop_core.dart index 00e9470e..8385e70f 100644 --- a/lib/app/app_controller_desktop_core.dart +++ b/lib/app/app_controller_desktop_core.dart @@ -306,7 +306,8 @@ class AppController extends ChangeNotifier { final Map aiGatewayStreamingTextBySessionInternal = {}; final Map - syncedGoAgentProvidersInternal = {}; + syncedGoAgentProvidersInternal = + {}; final DesktopThreadArtifactService threadArtifactServiceInternal = DesktopThreadArtifactService(); List singleAgentSharedImportedSkillsInternal = @@ -575,10 +576,16 @@ class AppController extends ChangeNotifier { List get configuredSingleAgentProviders => normalizeSingleAgentProviderList( - (availableSingleAgentProvidersOverrideInternal ?? - settings.savedSingleAgentProviders) - .where((item) => item != SingleAgentProvider.auto) - .map(settings.resolveSingleAgentProvider), + availableSingleAgentProvidersOverrideInternal ?? + [ + ...settings.savedSingleAgentProviders, + ...singleAgentCapabilitiesByProviderInternal.keys, + ...singleAgentCapabilitiesByProviderInternal.values.expand( + (item) => item.supportedProviders, + ), + ] + .where((item) => item != SingleAgentProvider.auto) + .map(settings.resolveSingleAgentProvider), ); List get availableSingleAgentProviders => @@ -599,7 +606,9 @@ class AppController extends ChangeNotifier { } return [ AssistantExecutionTarget.singleAgent, - ...visible.where((target) => target != AssistantExecutionTarget.singleAgent), + ...visible.where( + (target) => target != AssistantExecutionTarget.singleAgent, + ), ]; } diff --git a/lib/app/app_controller_desktop_runtime_coordination_impl.dart b/lib/app/app_controller_desktop_runtime_coordination_impl.dart index 0991e6e6..5ed5e9b8 100644 --- a/lib/app/app_controller_desktop_runtime_coordination_impl.dart +++ b/lib/app/app_controller_desktop_runtime_coordination_impl.dart @@ -90,11 +90,16 @@ Future refreshSingleAgentCapabilitiesRuntimeInternal( forceRefresh: forceRefresh, ); final next = {}; - for (final provider in controller.configuredSingleAgentProviders) { + final candidateProviders = + normalizeSingleAgentProviderList([ + ...controller.configuredSingleAgentProviders, + ...capabilities.providers.map( + controller.settings.resolveSingleAgentProvider, + ), + ]); + for (final provider in candidateProviders) { if (!capabilities.providers.contains(provider)) { - next[provider] = const SingleAgentCapabilities.unavailable( - endpoint: '', - ); + next[provider] = const SingleAgentCapabilities.unavailable(endpoint: ''); continue; } next[provider] = SingleAgentCapabilities( diff --git a/lib/app/app_controller_desktop_thread_sessions.dart b/lib/app/app_controller_desktop_thread_sessions.dart index 0f3ca2ff..8d88f33d 100644 --- a/lib/app/app_controller_desktop_thread_sessions.dart +++ b/lib/app/app_controller_desktop_thread_sessions.dart @@ -319,7 +319,9 @@ extension AppControllerDesktopThreadSessions on AppController { singleAgentShouldShowModelControlForSession(currentSessionKey); List get singleAgentProviderOptions => - configuredSingleAgentProviders; + availableSingleAgentProviders.isNotEmpty + ? availableSingleAgentProviders + : configuredSingleAgentProviders; String singleAgentProviderLabelForSession(String sessionKey) { return singleAgentProviderForSession(sessionKey).label; diff --git a/test/runtime/acp_bridge_provider_hub_suite.dart b/test/runtime/acp_bridge_provider_hub_suite.dart index c3a3fe2f..8df2ae54 100644 --- a/test/runtime/acp_bridge_provider_hub_suite.dart +++ b/test/runtime/acp_bridge_provider_hub_suite.dart @@ -7,9 +7,11 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:xworkmate/app/app_controller.dart'; import 'package:xworkmate/app/app_controller_desktop_external_acp_routing.dart'; +import 'package:xworkmate/runtime/go_task_service_client.dart'; import 'package:xworkmate/runtime/runtime_models.dart'; import '../test_support.dart'; +import 'app_controller_ai_gateway_chat_suite_fakes.dart'; void main() { group('ACP bridge provider hub', () { @@ -88,6 +90,58 @@ void main() { ); }, ); + + test( + 'self-hosted bridge capabilities add dynamic builtin providers to the single-agent picker', + () async { + SharedPreferences.setMockInitialValues({}); + final store = createIsolatedTestStore(enableSecureStorage: false); + final controller = AppController( + store: store, + goTaskServiceClient: FakeGoTaskServiceClientInternal( + capabilities: ExternalCodeAgentAcpCapabilities( + singleAgent: true, + multiAgent: false, + providers: { + SingleAgentProvider.codex, + SingleAgentProvider.opencode, + }, + raw: {}, + ), + ), + ); + addTearDown(controller.dispose); + await _waitFor(() => !controller.initializing); + + await controller.saveSettings( + controller.settings.copyWith( + acpBridgeServerModeConfig: controller + .settings + .acpBridgeServerModeConfig + .copyWith( + mode: AcpBridgeServerMode.selfHosted, + selfHosted: controller + .settings + .acpBridgeServerModeConfig + .selfHosted + .copyWith(serverUrl: 'https://xworkmate-bridge.svc.plus'), + ), + ), + refreshAfterSave: false, + ); + + await controller.refreshSingleAgentCapabilitiesInternal( + forceRefresh: true, + ); + + expect( + controller.singleAgentProviderOptions + .map((item) => item.providerId) + .toList(growable: false), + const ['opencode', 'codex'], + ); + }, + ); }); }