diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts
index 6de417594..ab72f7429 100644
--- a/androidApp/build.gradle.kts
+++ b/androidApp/build.gradle.kts
@@ -291,6 +291,7 @@ dependencies {
googleImplementation(libs.firebase.ai)
googleImplementation(libs.firebase.ai.ondevice)
googleImplementation(libs.mlkit.translate)
+ googleImplementation(libs.mlkit.genai.prompt)
googleImplementation(libs.androidx.appfunctions)
googleImplementation(libs.androidx.appfunctions.service)
diff --git a/androidApp/src/fdroid/kotlin/org/meshtastic/app/di/FdroidAiModule.kt b/androidApp/src/fdroid/kotlin/org/meshtastic/app/di/FdroidAiModule.kt
index 53edf9aca..2920bf22d 100644
--- a/androidApp/src/fdroid/kotlin/org/meshtastic/app/di/FdroidAiModule.kt
+++ b/androidApp/src/fdroid/kotlin/org/meshtastic/app/di/FdroidAiModule.kt
@@ -18,6 +18,8 @@ package org.meshtastic.app.di
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single
+import org.meshtastic.feature.discovery.ai.AlgorithmicSummaryProvider
+import org.meshtastic.feature.discovery.ai.DiscoverySummaryAiProvider
import org.meshtastic.feature.docs.ai.AIDocAssistant
import org.meshtastic.feature.docs.ai.KeywordFallbackAssistant
import org.meshtastic.feature.docs.translation.DocTranslationService
@@ -28,5 +30,7 @@ import org.meshtastic.feature.docs.translation.NoOpDocTranslator
class FdroidAiModule {
@Single fun aiDocAssistant(fallback: KeywordFallbackAssistant): AIDocAssistant = fallback
+ @Single fun discoverySummaryAiProvider(fallback: AlgorithmicSummaryProvider): DiscoverySummaryAiProvider = fallback
+
@Single fun docTranslationService(): DocTranslationService = NoOpDocTranslator()
}
diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/di/GoogleAiModule.kt b/androidApp/src/google/kotlin/org/meshtastic/app/di/GoogleAiModule.kt
index 0828a9493..121164eca 100644
--- a/androidApp/src/google/kotlin/org/meshtastic/app/di/GoogleAiModule.kt
+++ b/androidApp/src/google/kotlin/org/meshtastic/app/di/GoogleAiModule.kt
@@ -22,8 +22,11 @@ import okio.Path.Companion.toOkioPath
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single
import org.meshtastic.app.ai.GeminiNanoDocAssistant
+import org.meshtastic.app.discovery.GeminiNanoSummaryProvider
import org.meshtastic.app.translation.MlKitDocTranslator
import org.meshtastic.core.repository.NodeRepository
+import org.meshtastic.feature.discovery.DiscoverySummaryGenerator
+import org.meshtastic.feature.discovery.ai.DiscoverySummaryAiProvider
import org.meshtastic.feature.docs.ai.AIDocAssistant
import org.meshtastic.feature.docs.data.DocBundleLoader
import org.meshtastic.feature.docs.data.KeywordSearchEngine
@@ -44,6 +47,10 @@ class GoogleAiModule {
nodeRepository: NodeRepository,
): AIDocAssistant = GeminiNanoDocAssistant(searchEngine, bundleLoader, nodeRepository)
+ @Single
+ fun discoverySummaryAiProvider(generator: DiscoverySummaryGenerator): DiscoverySummaryAiProvider =
+ GeminiNanoSummaryProvider(generator)
+
@Single
fun docTranslationCache(context: Context): DocTranslationCache =
DocTranslationCache(cacheDir = context.cacheDir.toOkioPath(), fileSystem = FileSystem.SYSTEM)
diff --git a/feature/discovery/src/androidMain/kotlin/org/meshtastic/feature/discovery/ai/GeminiNanoSummaryProvider.kt b/androidApp/src/google/kotlin/org/meshtastic/app/discovery/GeminiNanoSummaryProvider.kt
similarity index 90%
rename from feature/discovery/src/androidMain/kotlin/org/meshtastic/feature/discovery/ai/GeminiNanoSummaryProvider.kt
rename to androidApp/src/google/kotlin/org/meshtastic/app/discovery/GeminiNanoSummaryProvider.kt
index 6fc800ef4..50395fc72 100644
--- a/feature/discovery/src/androidMain/kotlin/org/meshtastic/feature/discovery/ai/GeminiNanoSummaryProvider.kt
+++ b/androidApp/src/google/kotlin/org/meshtastic/app/discovery/GeminiNanoSummaryProvider.kt
@@ -14,26 +14,29 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.meshtastic.feature.discovery.ai
+package org.meshtastic.app.discovery
import co.touchlab.kermit.Logger
import com.google.mlkit.genai.prompt.Generation
import com.google.mlkit.genai.prompt.GenerativeModel
import com.google.mlkit.genai.prompt.TextPart
import com.google.mlkit.genai.prompt.generateContentRequest
-import org.koin.core.annotation.Single
import org.meshtastic.core.database.entity.DiscoveryPresetResultEntity
import org.meshtastic.core.database.entity.DiscoverySessionEntity
import org.meshtastic.feature.discovery.DiscoverySummaryGenerator
+import org.meshtastic.feature.discovery.ai.DiscoverySummaryAiProvider
/**
- * Android provider that uses Gemini Nano via ML Kit GenAI Prompt API for on-device AI summaries.
+ * Google-flavor provider that uses Gemini Nano via the ML Kit GenAI Prompt API for on-device AI summaries.
+ *
+ * Lives in the Google flavor source set (not the shared `:feature:discovery` module) so the proprietary ML Kit GenAI
+ * dependency never reaches the F-Droid build. The F-Droid flavor binds
+ * [org.meshtastic.feature.discovery.ai. AlgorithmicSummaryProvider] instead.
*
* Falls back to [DiscoverySummaryGenerator] when:
* - The on-device model is unavailable (unsupported hardware or not downloaded)
* - Generation fails for any reason
*/
-@Single(binds = [DiscoverySummaryAiProvider::class])
class GeminiNanoSummaryProvider(private val generator: DiscoverySummaryGenerator) : DiscoverySummaryAiProvider {
private val log = Logger.withTag("GeminiNanoSummary")
diff --git a/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt b/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt
index c47a1d4b0..3e7826074 100644
--- a/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt
+++ b/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt
@@ -87,6 +87,8 @@ import org.meshtastic.desktop.stub.NoopMeshLocationManager
import org.meshtastic.desktop.stub.NoopMeshWorkerManager
import org.meshtastic.desktop.stub.NoopPhoneLocationProvider
import org.meshtastic.desktop.stub.NoopPlatformAnalytics
+import org.meshtastic.feature.discovery.ai.AlgorithmicSummaryProvider
+import org.meshtastic.feature.discovery.ai.DiscoverySummaryAiProvider
import org.meshtastic.feature.docs.ai.AIDocAssistant
import org.meshtastic.feature.docs.ai.KeywordFallbackAssistant
import org.meshtastic.feature.docs.translation.DocTranslationService
@@ -227,6 +229,7 @@ private fun desktopPlatformStubsModule() = module {
// AI assistant: keyword-only fallback on desktop (no on-device model)
single { get() }
+ single { get() }
single { NoOpDocTranslator() }
// Desktop uses the real ApiService implementation (no flavor stub needed)
diff --git a/feature/discovery/build.gradle.kts b/feature/discovery/build.gradle.kts
index 8fa4407bc..bc26d4d78 100644
--- a/feature/discovery/build.gradle.kts
+++ b/feature/discovery/build.gradle.kts
@@ -51,7 +51,5 @@ kotlin {
}
commonTest.dependencies { implementation(projects.core.testing) }
-
- androidMain.dependencies { implementation(libs.mlkit.genai.prompt) }
}
}
diff --git a/feature/discovery/src/jvmMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt b/feature/discovery/src/commonMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt
similarity index 76%
rename from feature/discovery/src/jvmMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt
rename to feature/discovery/src/commonMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt
index 3fa5a96b5..c8d89eb62 100644
--- a/feature/discovery/src/jvmMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt
+++ b/feature/discovery/src/commonMain/kotlin/org/meshtastic/feature/discovery/ai/AlgorithmicSummaryProvider.kt
@@ -21,8 +21,14 @@ import org.meshtastic.core.database.entity.DiscoveryPresetResultEntity
import org.meshtastic.core.database.entity.DiscoverySessionEntity
import org.meshtastic.feature.discovery.DiscoverySummaryGenerator
-/** JVM/Desktop fallback that delegates to the algorithmic [DiscoverySummaryGenerator]. */
-@Single(binds = [DiscoverySummaryAiProvider::class])
+/**
+ * Algorithmic [DiscoverySummaryAiProvider] that delegates to the deterministic [DiscoverySummaryGenerator].
+ *
+ * Used wherever no on-device AI model is available: Desktop, iOS, and the Android F-Droid flavor. Registered with
+ * `binds = []` so it is injectable by concrete type but is not auto-bound to [DiscoverySummaryAiProvider]; each
+ * platform binds the interface explicitly (the Google flavor binds the Gemini Nano provider instead).
+ */
+@Single(binds = [])
class AlgorithmicSummaryProvider(private val generator: DiscoverySummaryGenerator) : DiscoverySummaryAiProvider {
override val isAvailable: Boolean = true