diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 0d432101b4..04adf1252e 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -197,8 +197,6 @@ secrets { propertiesFileName = "secrets.properties" } -ksp { arg("appfunctions:aggregateAppFunctions", "true") } - androidComponents { onVariants(selector().withBuildType("debug")) { variant -> variant.flavorName?.let { flavor -> variant.applicationId.set("com.geeksville.mesh.$flavor.debug") } diff --git a/androidApp/detekt-baseline.xml b/androidApp/detekt-baseline.xml index 0f71fbcb08..c1fd804fb9 100644 --- a/androidApp/detekt-baseline.xml +++ b/androidApp/detekt-baseline.xml @@ -32,13 +32,13 @@ ReturnCount:MlKitMessageTranslator.kt:MlKitMessageTranslator$override suspend fun downloadLanguageModels: DownloadResult ReturnCount:MlKitMessageTranslator.kt:MlKitMessageTranslator$override suspend fun translate: TranslationResult SwallowedException:MapViewModel.kt:MapViewModel$e: Exception - ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$@AppFunction(isDescribedByKDoc = true) suspend fun getChannelInfo: GetChannelInfoResponse - ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$@AppFunction(isDescribedByKDoc = true) suspend fun getDeviceStatus: GetDeviceStatusResponse - ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$@AppFunction(isDescribedByKDoc = true) suspend fun getMeshMetrics: GetMeshMetricsResponse - ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$@AppFunction(isDescribedByKDoc = true) suspend fun getNodeDetails: GetNodeDetailsResponse - ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$@AppFunction(isDescribedByKDoc = true) suspend fun getNodeList: GetNodeListResponse - ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$@AppFunction(isDescribedByKDoc = true) suspend fun getRecentMessages: GetRecentMessagesResponse - ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$@AppFunction(isDescribedByKDoc = true) suspend fun sendMessage: SendMessageResponse + ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$suspend fun getChannelInfo: GetChannelInfoResponse + ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$suspend fun getDeviceStatus: GetDeviceStatusResponse + ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$suspend fun getMeshMetrics: GetMeshMetricsResponse + ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$suspend fun getNodeDetails: GetNodeDetailsResponse + ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$suspend fun getNodeList: GetNodeListResponse + ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$suspend fun getRecentMessages: GetRecentMessagesResponse + ThrowsCount:MeshtasticAppFunctions.kt:MeshtasticAppFunctions$suspend fun sendMessage: SendMessageResponse TooGenericExceptionCaught:FdroidMapOverlayRenderer.kt:FdroidMapOverlayRenderer$e: Exception TooGenericExceptionCaught:GeminiNanoDocAssistant.kt:GeminiNanoDocAssistant$e: Exception TooGenericExceptionCaught:MapView.kt:e: Exception diff --git a/androidApp/src/google/AndroidManifest.xml b/androidApp/src/google/AndroidManifest.xml index 5a5890ac93..0e4fa20883 100644 --- a/androidApp/src/google/AndroidManifest.xml +++ b/androidApp/src/google/AndroidManifest.xml @@ -58,56 +58,18 @@ - + - - - - - - - - - - + android:value="meshtastic_app_function_service.xml" /> diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/GoogleMeshUtilApplication.kt b/androidApp/src/google/kotlin/org/meshtastic/app/GoogleMeshUtilApplication.kt index 379127456d..5645e9a986 100644 --- a/androidApp/src/google/kotlin/org/meshtastic/app/GoogleMeshUtilApplication.kt +++ b/androidApp/src/google/kotlin/org/meshtastic/app/GoogleMeshUtilApplication.kt @@ -16,21 +16,12 @@ */ package org.meshtastic.app -import androidx.appfunctions.AppFunctionConfiguration import kotlinx.coroutines.launch import org.koin.java.KoinJavaComponent.getKoin import org.meshtastic.app.ai.appfunctions.AppFunctionStateSync -import org.meshtastic.app.ai.appfunctions.MeshtasticAppFunctions -/** - * Google flavor Application subclass that configures App Functions. - * - * Registers a custom factory so the AppFunctions runtime can instantiate [MeshtasticAppFunctions] with its Koin-managed - * dependencies. - */ -class GoogleMeshUtilApplication : - MeshUtilApplication(), - AppFunctionConfiguration.Provider { +/** Google flavor Application subclass that starts the App Functions enabled-state sync. */ +class GoogleMeshUtilApplication : MeshUtilApplication() { override fun onCreate() { super.onCreate() @@ -40,12 +31,4 @@ class GoogleMeshUtilApplication : // Off-main: construction forces the AppFunctionsPrefs subgraph and fires AppSearch binder calls. applicationScope.launch { getKoin().get() } } - - override val appFunctionConfiguration: AppFunctionConfiguration - get() = - AppFunctionConfiguration.Builder() - .addEnclosingClassFactory(MeshtasticAppFunctions::class.java) { - getKoin().get() - } - .build() } diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/ai/appfunctions/AppFunctionStateSync.kt b/androidApp/src/google/kotlin/org/meshtastic/app/ai/appfunctions/AppFunctionStateSync.kt index 09c533b91c..d710e0f1d5 100644 --- a/androidApp/src/google/kotlin/org/meshtastic/app/ai/appfunctions/AppFunctionStateSync.kt +++ b/androidApp/src/google/kotlin/org/meshtastic/app/ai/appfunctions/AppFunctionStateSync.kt @@ -134,7 +134,8 @@ class AppFunctionStateSync( ): List> = if (actual == null) desired else desired.filter { (id, enabled) -> actual[id] != enabled } - private const val CLASS_PREFIX = "org.meshtastic.app.ai.appfunctions.MeshtasticAppFunctions#" + // Mirrors the generated MeshtasticAppFunctionService.FUNCTION_ID_* constants, which are API 36 only. + private const val CLASS_PREFIX = "org.meshtastic.app.ai.appfunctions.BaseMeshtasticAppFunctionService#" const val SEND_MESSAGE_ID = "${CLASS_PREFIX}sendMessage" const val GET_MESH_STATUS_ID = "${CLASS_PREFIX}getMeshStatus" diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/ai/appfunctions/BaseMeshtasticAppFunctionService.kt b/androidApp/src/google/kotlin/org/meshtastic/app/ai/appfunctions/BaseMeshtasticAppFunctionService.kt new file mode 100644 index 0000000000..7e44a3d309 --- /dev/null +++ b/androidApp/src/google/kotlin/org/meshtastic/app/ai/appfunctions/BaseMeshtasticAppFunctionService.kt @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2026 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.meshtastic.app.ai.appfunctions + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.appfunctions.AppFunction +import androidx.appfunctions.AppFunctionIntValueConstraint +import androidx.appfunctions.AppFunctionService +import androidx.appfunctions.AppFunctionServiceEntryPoint +import org.koin.android.ext.android.inject +import org.meshtastic.core.data.ai.AiFunctionProvider + +/** + * Exposes Meshtastic mesh networking capabilities to system AI assistants via the Android App Functions API. Functions + * declared here are discoverable by the system and can be invoked by AI agents such as Gemini. + * + * KSP generates the concrete [MeshtasticAppFunctionService] and its `meshtastic_app_function_service.xml` asset; the + * KDoc on each function is the description agents read. + */ +@RequiresApi(Build.VERSION_CODES.BAKLAVA) +@AppFunctionServiceEntryPoint( + serviceName = "MeshtasticAppFunctionService", + appFunctionXmlFileName = "meshtastic_app_function_service", +) +abstract class BaseMeshtasticAppFunctionService : AppFunctionService() { + + private val appFunctions: MeshtasticAppFunctions by inject() + + /** + * Send a text message over the Meshtastic mesh radio network. + * + * Messages are transmitted to nearby mesh nodes using LoRa radio. The mesh network is ideal for off-grid + * communications where cellular service is unavailable. + * + * @param text The message text to send (max 228 UTF-8 bytes — the mesh payload left after protobuf framing). + * @param recipientName Optional name of a specific node to send a direct message to. If omitted, the message is + * broadcast to all nodes on the specified channel. + * @param channelName Optional channel name to broadcast on. If omitted, uses the primary channel. Ignored when + * recipientName is specified. + * @return A [SendMessageResponse] with the message ID, channel, and timestamp. + */ + @AppFunction(isDescribedByKDoc = true) + suspend fun sendMessage( + text: String, + recipientName: String? = null, + channelName: String? = null, + ): SendMessageResponse = appFunctions.sendMessage(text, recipientName, channelName) + + /** + * Get the current status of the Meshtastic mesh network. + * + * Returns connection state, number of online nodes, total known nodes, the connected device's battery level, and + * the local node name. + * + * @return A [MeshStatusResponse] with the current mesh network status. + */ + @AppFunction(isDescribedByKDoc = true) + suspend fun getMeshStatus(): MeshStatusResponse = appFunctions.getMeshStatus() + + /** + * List all nodes currently visible on the Meshtastic mesh network. + * + * Returns detailed information about each node including name, battery level, and last heard time. Nodes are sorted + * by most recently heard first. + * + * @return A list of nodes with their current status and metrics. + */ + @AppFunction(isDescribedByKDoc = true) + suspend fun getNodeList(): GetNodeListResponse = appFunctions.getNodeList() + + /** + * List all available Meshtastic mesh channels and their configurations. + * + * Returns details about each channel including name, index, primary status, and uplink/downlink settings. + * + * @return A list of channels with their current configuration. + */ + @AppFunction(isDescribedByKDoc = true) + suspend fun getChannelInfo(): GetChannelInfoResponse = appFunctions.getChannelInfo() + + /** + * Get the status and metrics of the local Meshtastic radio device. + * + * Returns hardware model, firmware version, battery level, charging status, and current radio state. + * + * @return Device status with current metrics and configuration. + */ + @AppFunction(isDescribedByKDoc = true) + suspend fun getDeviceStatus(): GetDeviceStatusResponse = appFunctions.getDeviceStatus() + + /** + * Retrieve detailed telemetry and status for a specific mesh node. + * + * Returns per-node metrics including battery level, signal strength, hardware model, and location data. + * + * @param nodeId The target node ID (e.g., '!abc12345' or user ID). + * @return A [GetNodeDetailsResponse] with detailed node information. + */ + @AppFunction(isDescribedByKDoc = true) + suspend fun getNodeDetails(nodeId: String): GetNodeDetailsResponse = appFunctions.getNodeDetails(nodeId) + + /** + * Retrieve aggregate network metrics and statistics for the entire mesh. + * + * Returns mesh-wide analytics including total node count, online nodes, average battery level, and health score. + * + * @return A [GetMeshMetricsResponse] with mesh-wide statistics. + */ + @AppFunction(isDescribedByKDoc = true) + suspend fun getMeshMetrics(): GetMeshMetricsResponse = appFunctions.getMeshMetrics() + + /** + * Retrieve recent messages received over the Meshtastic mesh radio network. + * + * Returns a list of recent messages from the local message history. Messages are stored locally and do not require + * an active mesh connection. Useful for catching up on conversations or reviewing recent communications. + * + * @param contactName Optional name of a node or channel to filter messages from. If omitted, returns messages from + * all contacts sorted by most recent. + * @param limit Maximum number of messages to return: 1, 5, 10, 20 or 50. Defaults to 20. + * @return A [GetRecentMessagesResponse] containing the list of recent messages. + */ + @AppFunction(isDescribedByKDoc = true) + suspend fun getRecentMessages( + contactName: String? = null, + @AppFunctionIntValueConstraint(enumValues = [1, 5, 10, 20, 50]) + limit: Int = AiFunctionProvider.DEFAULT_MESSAGE_LIMIT, + ): GetRecentMessagesResponse = appFunctions.getRecentMessages(contactName, limit) + + /** + * Get a summary of unread messages across all Meshtastic mesh contacts. + * + * Returns the total unread count and a per-contact breakdown showing who sent unread messages, how many are unread, + * and a preview of the last message. Muted contacts are excluded. Does not require an active mesh connection. + * + * @return A [GetUnreadSummaryResponse] with the total unread count and per-contact details. + */ + @AppFunction(isDescribedByKDoc = true) + suspend fun getUnreadSummary(): GetUnreadSummaryResponse = appFunctions.getUnreadSummary() +} diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/ai/appfunctions/MeshtasticAppFunctions.kt b/androidApp/src/google/kotlin/org/meshtastic/app/ai/appfunctions/MeshtasticAppFunctions.kt index 46463302ba..0097575bbb 100644 --- a/androidApp/src/google/kotlin/org/meshtastic/app/ai/appfunctions/MeshtasticAppFunctions.kt +++ b/androidApp/src/google/kotlin/org/meshtastic/app/ai/appfunctions/MeshtasticAppFunctions.kt @@ -16,10 +16,7 @@ */ package org.meshtastic.app.ai.appfunctions -import androidx.appfunctions.AppFunction -import androidx.appfunctions.AppFunctionContext import androidx.appfunctions.AppFunctionElementNotFoundException -import androidx.appfunctions.AppFunctionIntValueConstraint import androidx.appfunctions.AppFunctionInvalidArgumentException import androidx.appfunctions.AppFunctionNotSupportedException import kotlinx.coroutines.TimeoutCancellationException @@ -27,35 +24,12 @@ import org.meshtastic.core.data.ai.AiFunctionProvider import org.meshtastic.core.data.ai.SendMessageResult /** - * Exposes Meshtastic mesh networking capabilities to system AI assistants via the Android App Functions API. Functions - * declared here are discoverable by the system and can be invoked by AI agents such as Gemini. + * Maps [AiFunctionProvider] results to App Functions responses and exceptions. [BaseMeshtasticAppFunctionService] + * declares the functions agents see and delegates each one here. */ -// The AppFunctions calling convention requires every @AppFunction to take AppFunctionContext as -// its first parameter, even when the implementation never reads it. -@Suppress("UnusedParameter") class MeshtasticAppFunctions(private val provider: AiFunctionProvider) { - /** - * Send a text message over the Meshtastic mesh radio network. - * - * Messages are transmitted to nearby mesh nodes using LoRa radio. The mesh network is ideal for off-grid - * communications where cellular service is unavailable. - * - * @param context The app function invocation context provided by the system. - * @param text The message text to send (max 228 UTF-8 bytes — the mesh payload left after protobuf framing). - * @param recipientName Optional name of a specific node to send a direct message to. If omitted, the message is - * broadcast to all nodes on the specified channel. - * @param channelName Optional channel name to broadcast on. If omitted, uses the primary channel. Ignored when - * recipientName is specified. - * @return A [SendMessageResponse] with the message ID, channel, and timestamp. - */ - @AppFunction(isDescribedByKDoc = true) - suspend fun sendMessage( - context: AppFunctionContext, - text: String, - recipientName: String? = null, - channelName: String? = null, - ): SendMessageResponse { + suspend fun sendMessage(text: String, recipientName: String?, channelName: String?): SendMessageResponse { val result = try { provider.sendMessage(text, recipientName, channelName) @@ -91,17 +65,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) { } } - /** - * Get the current status of the Meshtastic mesh network. - * - * Returns connection state, number of online nodes, total known nodes, the connected device's battery level, and - * the local node name. - * - * @param context The app function invocation context provided by the system. - * @return A [MeshStatusResponse] with the current mesh network status. - */ - @AppFunction(isDescribedByKDoc = true) - suspend fun getMeshStatus(context: AppFunctionContext): MeshStatusResponse { + suspend fun getMeshStatus(): MeshStatusResponse { val status = try { provider.getMeshStatus() @@ -120,17 +84,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) { ) } - /** - * List all nodes currently visible on the Meshtastic mesh network. - * - * Returns detailed information about each node including name, battery level, and last heard time. Nodes are sorted - * by most recently heard first. - * - * @param context The app function invocation context provided by the system. - * @return A list of nodes with their current status and metrics. - */ - @AppFunction(isDescribedByKDoc = true) - suspend fun getNodeList(context: AppFunctionContext): GetNodeListResponse { + suspend fun getNodeList(): GetNodeListResponse { val result = try { provider.getNodeList() @@ -163,16 +117,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) { } } - /** - * List all available Meshtastic mesh channels and their configurations. - * - * Returns details about each channel including name, index, primary status, and uplink/downlink settings. - * - * @param context The app function invocation context provided by the system. - * @return A list of channels with their current configuration. - */ - @AppFunction(isDescribedByKDoc = true) - suspend fun getChannelInfo(context: AppFunctionContext): GetChannelInfoResponse { + suspend fun getChannelInfo(): GetChannelInfoResponse { val result = try { provider.getChannelInfo() @@ -205,16 +150,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) { } } - /** - * Get the status and metrics of the local Meshtastic radio device. - * - * Returns hardware model, firmware version, battery level, charging status, and current radio state. - * - * @param context The app function invocation context provided by the system. - * @return Device status with current metrics and configuration. - */ - @AppFunction(isDescribedByKDoc = true) - suspend fun getDeviceStatus(context: AppFunctionContext): GetDeviceStatusResponse { + suspend fun getDeviceStatus(): GetDeviceStatusResponse { val result = try { provider.getDeviceStatus() @@ -243,17 +179,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) { } } - /** - * Retrieve detailed telemetry and status for a specific mesh node. - * - * Returns per-node metrics including battery level, signal strength, hardware model, and location data. - * - * @param context The app function invocation context provided by the system. - * @param nodeId The target node ID (e.g., '!abc12345' or user ID). - * @return A [GetNodeDetailsResponse] with detailed node information. - */ - @AppFunction(isDescribedByKDoc = true) - suspend fun getNodeDetails(context: AppFunctionContext, nodeId: String): GetNodeDetailsResponse { + suspend fun getNodeDetails(nodeId: String): GetNodeDetailsResponse { val result = try { provider.getNodeDetails(nodeId) @@ -294,16 +220,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) { } } - /** - * Retrieve aggregate network metrics and statistics for the entire mesh. - * - * Returns mesh-wide analytics including total node count, online nodes, average battery level, and health score. - * - * @param context The app function invocation context provided by the system. - * @return A [GetMeshMetricsResponse] with mesh-wide statistics. - */ - @AppFunction(isDescribedByKDoc = true) - suspend fun getMeshMetrics(context: AppFunctionContext): GetMeshMetricsResponse { + suspend fun getMeshMetrics(): GetMeshMetricsResponse { val result = try { provider.getMeshMetrics() @@ -332,25 +249,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) { } } - /** - * Retrieve recent messages received over the Meshtastic mesh radio network. - * - * Returns a list of recent messages from the local message history. Messages are stored locally and do not require - * an active mesh connection. Useful for catching up on conversations or reviewing recent communications. - * - * @param context The app function invocation context provided by the system. - * @param contactName Optional name of a node or channel to filter messages from. If omitted, returns messages from - * all contacts sorted by most recent. - * @param limit Maximum number of messages to return (1–50). Defaults to 20. - * @return A [GetRecentMessagesResponse] containing the list of recent messages. - */ - @AppFunction(isDescribedByKDoc = true) - suspend fun getRecentMessages( - context: AppFunctionContext, - contactName: String? = null, - @AppFunctionIntValueConstraint(enumValues = [1, 5, 10, 20, 50]) - limit: Int = AiFunctionProvider.DEFAULT_MESSAGE_LIMIT, - ): GetRecentMessagesResponse { + suspend fun getRecentMessages(contactName: String?, limit: Int): GetRecentMessagesResponse { val result = try { provider.getRecentMessages(contactName, limit) @@ -381,17 +280,7 @@ class MeshtasticAppFunctions(private val provider: AiFunctionProvider) { } } - /** - * Get a summary of unread messages across all Meshtastic mesh contacts. - * - * Returns the total unread count and a per-contact breakdown showing who sent unread messages, how many are unread, - * and a preview of the last message. Muted contacts are excluded. Does not require an active mesh connection. - * - * @param context The app function invocation context provided by the system. - * @return A [GetUnreadSummaryResponse] with the total unread count and per-contact details. - */ - @AppFunction(isDescribedByKDoc = true) - suspend fun getUnreadSummary(context: AppFunctionContext): GetUnreadSummaryResponse { + suspend fun getUnreadSummary(): GetUnreadSummaryResponse { val result = try { provider.getUnreadSummary() diff --git a/androidApp/src/testGoogle/kotlin/org/meshtastic/app/ai/appfunctions/AppFunctionStateSyncTest.kt b/androidApp/src/testGoogle/kotlin/org/meshtastic/app/ai/appfunctions/AppFunctionStateSyncTest.kt index 6a8e8e0f21..e44e667325 100644 --- a/androidApp/src/testGoogle/kotlin/org/meshtastic/app/ai/appfunctions/AppFunctionStateSyncTest.kt +++ b/androidApp/src/testGoogle/kotlin/org/meshtastic/app/ai/appfunctions/AppFunctionStateSyncTest.kt @@ -51,4 +51,35 @@ class AppFunctionStateSyncTest { assertEquals(listOf(AppFunctionStateSync.GET_NODE_LIST_ID to false), pendingWrites(desired, actual)) } + + @Test + fun `toggle ids match the generated function ids`() { + assertEquals(MeshtasticAppFunctionService.FUNCTION_ID_SEND_MESSAGE, AppFunctionStateSync.SEND_MESSAGE_ID) + assertEquals(MeshtasticAppFunctionService.FUNCTION_ID_GET_MESH_STATUS, AppFunctionStateSync.GET_MESH_STATUS_ID) + assertEquals(MeshtasticAppFunctionService.FUNCTION_ID_GET_NODE_LIST, AppFunctionStateSync.GET_NODE_LIST_ID) + assertEquals( + MeshtasticAppFunctionService.FUNCTION_ID_GET_CHANNEL_INFO, + AppFunctionStateSync.GET_CHANNEL_INFO_ID, + ) + assertEquals( + MeshtasticAppFunctionService.FUNCTION_ID_GET_DEVICE_STATUS, + AppFunctionStateSync.GET_DEVICE_STATUS_ID, + ) + assertEquals( + MeshtasticAppFunctionService.FUNCTION_ID_GET_NODE_DETAILS, + AppFunctionStateSync.GET_NODE_DETAILS_ID, + ) + assertEquals( + MeshtasticAppFunctionService.FUNCTION_ID_GET_MESH_METRICS, + AppFunctionStateSync.GET_MESH_METRICS_ID, + ) + assertEquals( + MeshtasticAppFunctionService.FUNCTION_ID_GET_RECENT_MESSAGES, + AppFunctionStateSync.GET_RECENT_MESSAGES_ID, + ) + assertEquals( + MeshtasticAppFunctionService.FUNCTION_ID_GET_UNREAD_SUMMARY, + AppFunctionStateSync.GET_UNREAD_SUMMARY_ID, + ) + } } diff --git a/androidApp/src/testGoogle/kotlin/org/meshtastic/app/ai/appfunctions/MeshtasticAppFunctionsTest.kt b/androidApp/src/testGoogle/kotlin/org/meshtastic/app/ai/appfunctions/MeshtasticAppFunctionsTest.kt index edc7bb3479..17c146fc4a 100644 --- a/androidApp/src/testGoogle/kotlin/org/meshtastic/app/ai/appfunctions/MeshtasticAppFunctionsTest.kt +++ b/androidApp/src/testGoogle/kotlin/org/meshtastic/app/ai/appfunctions/MeshtasticAppFunctionsTest.kt @@ -16,7 +16,6 @@ */ package org.meshtastic.app.ai.appfunctions -import androidx.appfunctions.AppFunctionContext import androidx.appfunctions.AppFunctionElementNotFoundException import androidx.appfunctions.AppFunctionInvalidArgumentException import androidx.appfunctions.AppFunctionNotSupportedException @@ -56,7 +55,6 @@ import kotlin.test.assertTrue class MeshtasticAppFunctionsTest { private val provider: AiFunctionProvider = mock(MockMode.autofill) - private val context: AppFunctionContext = mock(MockMode.autofill) private val appFunctions = MeshtasticAppFunctions(provider) @Test @@ -64,7 +62,7 @@ class MeshtasticAppFunctionsTest { everySuspend { provider.sendMessage("Hello", "Alice", null) } returns SendMessageResult.Success(messageId = 1234, channel = "Primary", timestamp = 1700000000L) - val response = appFunctions.sendMessage(context, "Hello", "Alice", null) + val response = appFunctions.sendMessage("Hello", "Alice", null) assertEquals(1234, response.messageId) assertEquals("Primary", response.channel) @@ -77,9 +75,7 @@ class MeshtasticAppFunctionsTest { SendMessageResult.AmbiguousName(listOf("Alice", "Albert")) val exception = - assertFailsWith { - appFunctions.sendMessage(context, "Hello", "Al", null) - } + assertFailsWith { appFunctions.sendMessage("Hello", "Al", null) } assertTrue(exception.message!!.contains("Multiple nodes match that name")) } @@ -88,7 +84,7 @@ class MeshtasticAppFunctionsTest { everySuspend { provider.sendMessage("Hello", "Alice", null) } returns SendMessageResult.NotConnected("Not connected") - assertFailsWith { appFunctions.sendMessage(context, "Hello", "Alice", null) } + assertFailsWith { appFunctions.sendMessage("Hello", "Alice", null) } } @Test @@ -102,7 +98,7 @@ class MeshtasticAppFunctionsTest { localNodeName = "MyNode", ) - val response = appFunctions.getMeshStatus(context) + val response = appFunctions.getMeshStatus() assertEquals("CONNECTED", response.connectionState) assertEquals(5, response.onlineNodeCount) @@ -120,7 +116,7 @@ class MeshtasticAppFunctionsTest { ) everySuspend { provider.getNodeList() } returns GetNodeListResult.Success(nodes) - val response = appFunctions.getNodeList(context) + val response = appFunctions.getNodeList() assertEquals(2, response.nodes.size) assertEquals("1", response.nodes[0].id) @@ -152,7 +148,7 @@ class MeshtasticAppFunctionsTest { ) everySuspend { provider.getChannelInfo() } returns GetChannelInfoResult.Success(channels) - val response = appFunctions.getChannelInfo(context) + val response = appFunctions.getChannelInfo() assertEquals(2, response.channels.size) assertEquals("Primary", response.channels[0].name) @@ -174,7 +170,7 @@ class MeshtasticAppFunctionsTest { ) everySuspend { provider.getDeviceStatus() } returns GetDeviceStatusResult.Success(device) - val response = appFunctions.getDeviceStatus(context) + val response = appFunctions.getDeviceStatus() assertEquals("T-Beam", response.model) assertEquals("2.3.15", response.firmwareVersion) @@ -207,7 +203,7 @@ class MeshtasticAppFunctionsTest { ) everySuspend { provider.getNodeDetails("!abc12345") } returns GetNodeDetailsResult.Success(nodeDetails) - val response = appFunctions.getNodeDetails(context, "!abc12345") + val response = appFunctions.getNodeDetails("!abc12345") assertEquals("!abc12345", response.id) assertEquals("TestNode", response.name) @@ -220,7 +216,7 @@ class MeshtasticAppFunctionsTest { fun getNodeDetails_notFound() = runTest { everySuspend { provider.getNodeDetails("!unknown") } returns GetNodeDetailsResult.NotFound("Node not found") - assertFailsWith { appFunctions.getNodeDetails(context, "!unknown") } + assertFailsWith { appFunctions.getNodeDetails("!unknown") } } @Test @@ -237,7 +233,7 @@ class MeshtasticAppFunctionsTest { ) everySuspend { provider.getMeshMetrics() } returns GetMeshMetricsResult.Success(metrics) - val response = appFunctions.getMeshMetrics(context) + val response = appFunctions.getMeshMetrics() assertEquals(12, response.totalNodeCount) assertEquals(4, response.onlineNodeCount) @@ -260,7 +256,7 @@ class MeshtasticAppFunctionsTest { ) everySuspend { provider.getRecentMessages(null, 10) } returns GetRecentMessagesResult.Success(messages) - val response = appFunctions.getRecentMessages(context, null, 10) + val response = appFunctions.getRecentMessages(null, 10) assertEquals(1, response.messages.size) assertEquals("Alice", response.messages[0].senderName) @@ -284,7 +280,7 @@ class MeshtasticAppFunctionsTest { ) everySuspend { provider.getUnreadSummary() } returns GetUnreadSummaryResult.Success(summary) - val response = appFunctions.getUnreadSummary(context) + val response = appFunctions.getUnreadSummary() assertEquals(3, response.totalUnreadCount) assertEquals(1, response.contacts.size)