From f605cdc0db54605baf9e2c765313b0750f60ed5f Mon Sep 17 00:00:00 2001 From: Caleb Areeveso Date: Fri, 28 Aug 2026 05:27:01 +0000 Subject: [PATCH] Adopt @AppFunctionUriValueConstraint in ChatApp and Agent Reference PR: Demonstrates how to adopt @AppFunctionUriValueConstraint across target apps and the agent orchestrator. Builds against snapshot build 16181473. Change-Id: I2a151eefa493871c5afd571f880149e4f1504ee3 --- ChatApp/gradle/libs.versions.toml | 2 +- ChatApp/settings.gradle.kts | 6 ++++ .../BaseChatAppFunctionService.kt | 3 ++ .../agent/data/GeminiToolConverter.kt | 18 +--------- .../agent/domain/AgentOrchestrator.kt | 36 +++++++------------ .../agent/data/GeminiToolConverterTest.kt | 8 +++-- agent/gradle/libs.versions.toml | 2 +- agent/settings.gradle.kts | 6 ++++ 8 files changed, 37 insertions(+), 44 deletions(-) diff --git a/ChatApp/gradle/libs.versions.toml b/ChatApp/gradle/libs.versions.toml index 82d99a5..0ddff17 100644 --- a/ChatApp/gradle/libs.versions.toml +++ b/ChatApp/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] agp = "9.3.1" -appfunctions = "1.0.0-alpha10" +appfunctions = "1.0.0-SNAPSHOT" kotlin = "2.4.10" coreKtx = "1.19.0" junit = "4.13.2" diff --git a/ChatApp/settings.gradle.kts b/ChatApp/settings.gradle.kts index 57f6f82..e0a39a6 100644 --- a/ChatApp/settings.gradle.kts +++ b/ChatApp/settings.gradle.kts @@ -15,6 +15,9 @@ */ pluginManagement { repositories { + maven { + url = uri("https://androidx.dev/snapshots/builds/16181473/artifacts/repository") + } google { content { includeGroupByRegex("com\\.android.*") @@ -29,6 +32,9 @@ pluginManagement { dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { + maven { + url = uri("https://androidx.dev/snapshots/builds/16181473/artifacts/repository") + } google() mavenCentral() } diff --git a/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/BaseChatAppFunctionService.kt b/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/BaseChatAppFunctionService.kt index 3ed0ead..803520e 100644 --- a/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/BaseChatAppFunctionService.kt +++ b/ChatApp/shared/src/main/kotlin/com/example/chatapp/appfunctions/BaseChatAppFunctionService.kt @@ -16,6 +16,7 @@ package com.example.chatapp.appfunctions import android.app.PendingIntent +import android.content.ContentResolver import android.content.Intent import android.net.Uri import androidx.annotation.RequiresApi @@ -26,6 +27,7 @@ import androidx.appfunctions.AppFunctionInvalidArgumentException import androidx.appfunctions.AppFunctionService import androidx.appfunctions.AppFunctionServiceEntryPoint import androidx.appfunctions.AppFunctionStringValueConstraint +import androidx.appfunctions.AppFunctionUriValueConstraint import com.example.chatapp.data.CallManager import com.example.chatapp.data.DisplayMessage import com.example.chatapp.data.MessageRepository @@ -184,6 +186,7 @@ abstract class BaseChatAppFunctionService : AppFunctionService() { @AppFunction(isDescribedByKDoc = true) suspend fun updateChatWallpaper( chatId: String, + @AppFunctionUriValueConstraint(allowedSchemes = [ContentResolver.SCHEME_CONTENT]) wallpaperUri: Uri, ): Boolean { val resolvedId = diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/data/GeminiToolConverter.kt b/agent/app/src/main/java/com/example/appfunctions/agent/data/GeminiToolConverter.kt index cc10981..46de3de 100644 --- a/agent/app/src/main/java/com/example/appfunctions/agent/data/GeminiToolConverter.kt +++ b/agent/app/src/main/java/com/example/appfunctions/agent/data/GeminiToolConverter.kt @@ -134,7 +134,7 @@ class GeminiToolConverter is AppFunctionStringTypeMetadata -> buildJsonObject { put(KEY_TYPE, JsonPrimitive(VALUE_STRING)) - if (isFileReferenceParameter(parameterName)) { + if (dataType.format == AppFunctionStringTypeMetadata.FORMAT_URI || dataType.format == "uri") { put(KEY_FORMAT, JsonPrimitive(VALUE_FILE_REFERENCE)) } val enumValues = dataType.enumValues @@ -261,13 +261,6 @@ class GeminiToolConverter } } - private fun isFileReferenceParameter(parameterName: String?): Boolean { - if (parameterName == null) return false - if (parameterName in KNOWN_FILE_REFERENCE_PARAM_NAMES) return true - return parameterName.endsWith("Uri", ignoreCase = true) || - parameterName.endsWith("Uris", ignoreCase = true) - } - companion object { private const val TOOL_ID_SEPARATOR = "_" private const val KEY_NAME = "name" @@ -276,15 +269,6 @@ class GeminiToolConverter private const val KEY_TYPE = "type" private const val KEY_FORMAT = "format" private const val VALUE_FILE_REFERENCE = "file_reference" - private val KNOWN_FILE_REFERENCE_PARAM_NAMES = - setOf( - "wallpaperUri", - "imageUri", - "attachmentUri", - "ringtoneUri", - "profilePictureUri", - "audioUri", - ) private const val VALUE_OBJECT = "object" private const val KEY_PROPERTIES = "properties" private const val KEY_REQUIRED = "required" diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/domain/AgentOrchestrator.kt b/agent/app/src/main/java/com/example/appfunctions/agent/domain/AgentOrchestrator.kt index 5e3b04d..45b2d20 100644 --- a/agent/app/src/main/java/com/example/appfunctions/agent/domain/AgentOrchestrator.kt +++ b/agent/app/src/main/java/com/example/appfunctions/agent/domain/AgentOrchestrator.kt @@ -26,6 +26,7 @@ import androidx.appfunctions.metadata.AppFunctionMetadata import androidx.appfunctions.metadata.AppFunctionObjectTypeMetadata import androidx.appfunctions.metadata.AppFunctionParameterMetadata import androidx.appfunctions.metadata.AppFunctionReferenceTypeMetadata +import androidx.appfunctions.metadata.AppFunctionStringTypeMetadata import androidx.core.content.FileProvider import com.example.appfunctions.agent.data.AgentInternalTools import com.example.appfunctions.agent.data.LlmProviderName @@ -549,8 +550,7 @@ class AgentOrchestrator ): Any { return when (value) { is String -> { - val shouldResolve = - isFileReferenceParameter(paramName) || isUriMetadata(dataType) + val shouldResolve = isUriMetadata(dataType) if (shouldResolve && ( value.startsWith( "http://", @@ -661,16 +661,19 @@ class AgentOrchestrator } } - private fun isFileReferenceParameter(parameterName: String?): Boolean { - if (parameterName == null) return false - if (parameterName in KNOWN_FILE_REFERENCE_PARAM_NAMES) return true - return parameterName.endsWith("Uri", ignoreCase = true) || - parameterName.endsWith("Uris", ignoreCase = true) - } - private fun isUriMetadata(dataType: AppFunctionDataTypeMetadata?): Boolean { if (dataType == null) return false - if (dataType is AppFunctionObjectTypeMetadata && dataType.qualifiedName == "android.net.Uri") return true + if (dataType is AppFunctionObjectTypeMetadata && dataType.qualifiedName == "android.net.Uri") { + val innerProp = dataType.properties["uri"] as? AppFunctionStringTypeMetadata + val pattern = innerProp?.pattern + return pattern == null || pattern.contains("content") || innerProp?.format == AppFunctionStringTypeMetadata.FORMAT_URI + } + if (dataType is AppFunctionStringTypeMetadata) { + val pattern = dataType.pattern + return (pattern != null && pattern.contains("content")) || + dataType.format == AppFunctionStringTypeMetadata.FORMAT_URI || + dataType.format == "uri" + } if (dataType is AppFunctionReferenceTypeMetadata && dataType.referenceDataType == "android.net.Uri") return true return false } @@ -719,17 +722,4 @@ class AgentOrchestrator } } } - - companion object { - private val KNOWN_FILE_REFERENCE_PARAM_NAMES = - setOf( - "wallpaperUri", - "imageUri", - "attachmentUri", - "ringtoneUri", - "profilePictureUri", - "audioUri", - "voiceNoteUri", - ) - } } diff --git a/agent/app/src/test/java/com/example/appfunctions/agent/data/GeminiToolConverterTest.kt b/agent/app/src/test/java/com/example/appfunctions/agent/data/GeminiToolConverterTest.kt index 62cb16a..0dcec02 100644 --- a/agent/app/src/test/java/com/example/appfunctions/agent/data/GeminiToolConverterTest.kt +++ b/agent/app/src/test/java/com/example/appfunctions/agent/data/GeminiToolConverterTest.kt @@ -410,12 +410,16 @@ class GeminiToolConverterTest { } @Test - fun convert_stringParameterEndingWithUri_injectsFileReferenceFormat() { + fun convert_stringParameterWithUriFormat_injectsFileReferenceFormat() { val parameter = AppFunctionParameterMetadata( name = "wallpaperUri", isRequired = true, - dataType = AppFunctionStringTypeMetadata(isNullable = false), + dataType = + AppFunctionStringTypeMetadata( + isNullable = false, + format = AppFunctionStringTypeMetadata.FORMAT_URI, + ), description = "A URI parameter", ) val tool = diff --git a/agent/gradle/libs.versions.toml b/agent/gradle/libs.versions.toml index 94cc165..af7e443 100644 --- a/agent/gradle/libs.versions.toml +++ b/agent/gradle/libs.versions.toml @@ -20,7 +20,7 @@ mockk = "1.14.11" ksp = "2.3.10" hilt = "2.60.1" androidxHiltNavigationCompose = "1.4.0" -appfunctions = "1.0.0-alpha10" +appfunctions = "1.0.0-SNAPSHOT" datastore = "1.2.1" screenshot = "0.0.1-alpha15" coil = "2.7.0" diff --git a/agent/settings.gradle.kts b/agent/settings.gradle.kts index c422771..9be45ec 100644 --- a/agent/settings.gradle.kts +++ b/agent/settings.gradle.kts @@ -15,6 +15,9 @@ */ pluginManagement { repositories { + maven { + url = uri("https://androidx.dev/snapshots/builds/16181473/artifacts/repository") + } google { content { includeGroupByRegex("com\\.android.*") @@ -30,6 +33,9 @@ pluginManagement { dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { + maven { + url = uri("https://androidx.dev/snapshots/builds/16181473/artifacts/repository") + } google() mavenCentral() }