From 84bed80febdc808c38a6ebc633e70d9cffda8f74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 May 2026 06:50:56 +0000 Subject: [PATCH 1/4] Initial plan From 7a3409969ac53cc7ed002c3ad43093565c4ac363 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 May 2026 06:53:56 +0000 Subject: [PATCH 2/4] feat(flutter): add custom API URL field per AI provider Agent-Logs-Url: https://github.com/mithun50/openclaw-termux/sessions/0cf373fa-8392-4c58-9180-0e7b224d24f2 Co-authored-by: mithun50 <99024517+mithun50@users.noreply.github.com> --- .../lib/screens/provider_detail_screen.dart | 29 +++++++++++++++++++ flutter_app/lib/screens/providers_screen.dart | 1 + .../lib/services/provider_config_service.dart | 7 +++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/flutter_app/lib/screens/provider_detail_screen.dart b/flutter_app/lib/screens/provider_detail_screen.dart index 8601b88..85de7a0 100644 --- a/flutter_app/lib/screens/provider_detail_screen.dart +++ b/flutter_app/lib/screens/provider_detail_screen.dart @@ -8,12 +8,14 @@ class ProviderDetailScreen extends StatefulWidget { final AiProvider provider; final String? existingApiKey; final String? existingModel; + final String? existingBaseUrl; const ProviderDetailScreen({ super.key, required this.provider, this.existingApiKey, this.existingModel, + this.existingBaseUrl, }); @override @@ -24,6 +26,7 @@ class _ProviderDetailScreenState extends State { static const _customModelSentinel = '__custom__'; late final TextEditingController _apiKeyController; + late final TextEditingController _baseUrlController; late final TextEditingController _customModelController; late String _selectedModel; bool _isCustomModel = false; @@ -41,6 +44,9 @@ class _ProviderDetailScreenState extends State { void initState() { super.initState(); _apiKeyController = TextEditingController(text: widget.existingApiKey ?? ''); + _baseUrlController = TextEditingController( + text: widget.existingBaseUrl ?? widget.provider.baseUrl, + ); _customModelController = TextEditingController(); final existing = widget.existingModel ?? widget.provider.defaultModels.first; @@ -57,6 +63,7 @@ class _ProviderDetailScreenState extends State { @override void dispose() { _apiKeyController.dispose(); + _baseUrlController.dispose(); _customModelController.dispose(); super.dispose(); } @@ -69,6 +76,13 @@ class _ProviderDetailScreenState extends State { ); return; } + final baseUrl = _baseUrlController.text.trim(); + if (baseUrl.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('API URL cannot be empty')), + ); + return; + } final model = _effectiveModel; if (model.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( @@ -82,6 +96,7 @@ class _ProviderDetailScreenState extends State { await ProviderConfigService.saveProviderConfig( provider: widget.provider, apiKey: apiKey, + baseUrl: baseUrl, model: model, ); if (mounted) { @@ -214,6 +229,20 @@ class _ProviderDetailScreenState extends State { ), const SizedBox(height: 24), + // API URL + Text( + 'API URL', + style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 8), + TextField( + controller: _baseUrlController, + decoration: const InputDecoration( + hintText: 'https://api.example.com/v1', + ), + ), + const SizedBox(height: 24), + // Model selection Text( 'Model', diff --git a/flutter_app/lib/screens/providers_screen.dart b/flutter_app/lib/screens/providers_screen.dart index 3bbb535..5347dea 100644 --- a/flutter_app/lib/screens/providers_screen.dart +++ b/flutter_app/lib/screens/providers_screen.dart @@ -41,6 +41,7 @@ class _ProvidersScreenState extends State { builder: (_) => ProviderDetailScreen( provider: provider, existingApiKey: providerConfig?['apiKey'] as String?, + existingBaseUrl: providerConfig?['baseUrl'] as String?, existingModel: _activeModel, ), ), diff --git a/flutter_app/lib/services/provider_config_service.dart b/flutter_app/lib/services/provider_config_service.dart index 41f1cfc..efa1bea 100644 --- a/flutter_app/lib/services/provider_config_service.dart +++ b/flutter_app/lib/services/provider_config_service.dart @@ -53,17 +53,18 @@ class ProviderConfigService { } } - /// Save a provider's API key and set its model as the active model. + /// Save a provider's API key/base URL and set its model as the active model. /// Tries a Node.js one-liner in proot first, then falls back to a direct /// file write via NativeBridge.writeRootfsFile if proot/DNS is unavailable. static Future saveProviderConfig({ required AiProvider provider, required String apiKey, + required String baseUrl, required String model, }) async { final providerIdJson = jsonEncode(provider.id); final apiKeyJson = jsonEncode(apiKey); - final baseUrlJson = jsonEncode(provider.baseUrl); + final baseUrlJson = jsonEncode(baseUrl); final modelJson = jsonEncode(model); // Build the provider object with the model as an object containing `id`, @@ -100,7 +101,7 @@ fs.writeFileSync(p, JSON.stringify(c, null, 2)); await _saveConfigDirect( providerId: provider.id, apiKey: apiKey, - baseUrl: provider.baseUrl, + baseUrl: baseUrl, model: model, ); } From dd7cd8a860abc80bcef90337681d82596d8234ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 May 2026 07:01:59 +0000 Subject: [PATCH 3/4] fix(flutter): read provider model from openclaw provider config Agent-Logs-Url: https://github.com/mithun50/openclaw-termux/sessions/49ca22ca-e3e1-4cf7-b12e-cec8afa5cb53 Co-authored-by: mithun50 <99024517+mithun50@users.noreply.github.com> --- flutter_app/lib/screens/providers_screen.dart | 2 +- .../lib/services/provider_config_service.dart | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/flutter_app/lib/screens/providers_screen.dart b/flutter_app/lib/screens/providers_screen.dart index 5347dea..4d9d3ae 100644 --- a/flutter_app/lib/screens/providers_screen.dart +++ b/flutter_app/lib/screens/providers_screen.dart @@ -42,7 +42,7 @@ class _ProvidersScreenState extends State { provider: provider, existingApiKey: providerConfig?['apiKey'] as String?, existingBaseUrl: providerConfig?['baseUrl'] as String?, - existingModel: _activeModel, + existingModel: providerConfig?['model'] as String? ?? _activeModel, ), ), ); diff --git a/flutter_app/lib/services/provider_config_service.dart b/flutter_app/lib/services/provider_config_service.dart index efa1bea..d40747b 100644 --- a/flutter_app/lib/services/provider_config_service.dart +++ b/flutter_app/lib/services/provider_config_service.dart @@ -11,9 +11,20 @@ class ProviderConfigService { return "'${s.replaceAll("'", "'\\''")}'"; } + static String? _extractPrimaryModel(dynamic modelsRaw) { + if (modelsRaw is! List || modelsRaw.isEmpty) return null; + final first = modelsRaw.first; + if (first is String) return first; + if (first is Map) { + final id = first['id']; + if (id is String && id.isNotEmpty) return id; + } + return null; + } + /// Read the current config and return a map with: /// - `activeModel`: the current primary model string (or null) - /// - `providers`: Map for configured providers + /// - `providers`: Map for configured providers static Future> readConfig() async { try { final content = await NativeBridge.readRootfsFile(_configPath); @@ -42,7 +53,11 @@ class ProviderConfigService { final providerEntries = modelsSection['providers'] as Map?; if (providerEntries != null) { for (final entry in providerEntries.entries) { - providers[entry.key] = entry.value; + if (entry.value is Map) { + final normalized = Map.from(entry.value as Map); + normalized['model'] = _extractPrimaryModel(normalized['models']); + providers[entry.key] = normalized; + } } } } From 519014ab1652161d08b2300030521faacdf384ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 May 2026 07:02:44 +0000 Subject: [PATCH 4/4] fix(flutter): avoid writing null normalized provider model Agent-Logs-Url: https://github.com/mithun50/openclaw-termux/sessions/49ca22ca-e3e1-4cf7-b12e-cec8afa5cb53 Co-authored-by: mithun50 <99024517+mithun50@users.noreply.github.com> --- flutter_app/lib/services/provider_config_service.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flutter_app/lib/services/provider_config_service.dart b/flutter_app/lib/services/provider_config_service.dart index d40747b..fd59e34 100644 --- a/flutter_app/lib/services/provider_config_service.dart +++ b/flutter_app/lib/services/provider_config_service.dart @@ -55,7 +55,10 @@ class ProviderConfigService { for (final entry in providerEntries.entries) { if (entry.value is Map) { final normalized = Map.from(entry.value as Map); - normalized['model'] = _extractPrimaryModel(normalized['models']); + final model = _extractPrimaryModel(normalized['models']); + if (model != null) { + normalized['model'] = model; + } providers[entry.key] = normalized; } }