diff --git a/ai-logic/firebase-ai/api.txt b/ai-logic/firebase-ai/api.txt index 59758115639..8369022fcc6 100644 --- a/ai-logic/firebase-ai/api.txt +++ b/ai-logic/firebase-ai/api.txt @@ -273,6 +273,7 @@ package com.google.firebase.ai.java { method public static final com.google.firebase.ai.java.LiveSessionFutures from(com.google.firebase.ai.type.LiveSession session); method public abstract org.reactivestreams.Publisher receive(); method public abstract com.google.common.util.concurrent.ListenableFuture send(com.google.firebase.ai.type.Content content); + method public abstract com.google.common.util.concurrent.ListenableFuture send(com.google.firebase.ai.type.Content content, boolean turnComplete); method public abstract com.google.common.util.concurrent.ListenableFuture send(String text); method public abstract com.google.common.util.concurrent.ListenableFuture sendAudioRealtime(com.google.firebase.ai.type.InlineData audio); method public abstract com.google.common.util.concurrent.ListenableFuture sendFunctionResponse(java.util.List functionList); @@ -1441,6 +1442,11 @@ package com.google.firebase.ai.type { ctor public RequestOptions(long timeoutInMillis = 180.seconds.inWholeMilliseconds, int autoFunctionCallingTurnLimit = 10); } + public final class RequestOptionsFactory { + method public com.google.firebase.ai.type.RequestOptions createWithCustomHeader(com.google.firebase.ai.type.RequestOptions baseOptions, String customApiClientHeader); + field public static final com.google.firebase.ai.type.RequestOptionsFactory INSTANCE; + } + public final class RequestTimeoutException extends com.google.firebase.ai.type.FirebaseAIException { } diff --git a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/GenerativeModel.kt b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/GenerativeModel.kt index 389d54434c5..ba94589c364 100644 --- a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/GenerativeModel.kt +++ b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/GenerativeModel.kt @@ -328,6 +328,14 @@ internal constructor( * @return A [GenerativeModelProvider] that uses the cloud backend. */ internal fun buildCloudModelProvider(isHybrid: Boolean = false): GenerativeModelProvider { + var finalApiClient = apiClient + if (!requestOptions.customApiClientHeader.isNullOrEmpty()) { + finalApiClient = "$finalApiClient ${requestOptions.customApiClientHeader}" + } + if (isHybrid) { + finalApiClient = "$finalApiClient hybrid" + } + return CloudGenerativeModelProvider( modelName = modelName, generationConfig = generationConfig, @@ -341,7 +349,7 @@ internal constructor( apiKey, modelName, requestOptions, - if (isHybrid) "${apiClient} hybrid" else apiClient, + finalApiClient, firebaseApp, AppCheckHeaderProvider( TAG, diff --git a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt index 55e2008382a..0cd1512ef46 100644 --- a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt +++ b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt @@ -92,7 +92,10 @@ internal constructor( apiKey, modelName, requestOptions, - "gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}", + "gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}".let { base -> + if (requestOptions.customApiClientHeader.isNullOrEmpty()) base + else "$base ${requestOptions.customApiClientHeader}" + }, firebaseApp, AppCheckHeaderProvider( TAG, diff --git a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/TemplateGenerativeModel.kt b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/TemplateGenerativeModel.kt index 5882902d0a2..edba701929e 100644 --- a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/TemplateGenerativeModel.kt +++ b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/TemplateGenerativeModel.kt @@ -78,7 +78,10 @@ internal constructor( apiKey, "", requestOptions, - "gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}", + "gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}".let { base -> + if (requestOptions.customApiClientHeader.isNullOrEmpty()) base + else "$base ${requestOptions.customApiClientHeader}" + }, firebaseApp, AppCheckHeaderProvider( TAG, diff --git a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/RequestOptions.kt b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/RequestOptions.kt index a1e3f6020ca..2492e9d3dc6 100644 --- a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/RequestOptions.kt +++ b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/RequestOptions.kt @@ -27,7 +27,8 @@ internal constructor( internal val timeout: Duration, internal val endpoint: String = "https://firebasevertexai.googleapis.com", internal val apiVersion: String = "v1beta", - internal val autoFunctionCallingTurnLimit: Int + internal val autoFunctionCallingTurnLimit: Int, + internal val customApiClientHeader: String? = null ) { /** @@ -39,9 +40,10 @@ internal constructor( @JvmOverloads public constructor( timeoutInMillis: Long = 180.seconds.inWholeMilliseconds, - autoFunctionCallingTurnLimit: Int = 10 + autoFunctionCallingTurnLimit: Int = 10, ) : this( timeout = timeoutInMillis.toDuration(DurationUnit.MILLISECONDS), - autoFunctionCallingTurnLimit = autoFunctionCallingTurnLimit + autoFunctionCallingTurnLimit = autoFunctionCallingTurnLimit, + customApiClientHeader = null ) } diff --git a/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/RequestOptionsFactory.kt b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/RequestOptionsFactory.kt new file mode 100644 index 00000000000..3a59c39c6fd --- /dev/null +++ b/ai-logic/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/RequestOptionsFactory.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.ai.type + +/** + * Factory for creating [RequestOptions] with custom configurations. This is primarily intended for + * use by wrapper libraries and internal tools. + */ +public object RequestOptionsFactory { + public fun createWithCustomHeader( + baseOptions: RequestOptions, + customApiClientHeader: String + ): RequestOptions { + return RequestOptions( + timeout = baseOptions.timeout, + endpoint = baseOptions.endpoint, + apiVersion = baseOptions.apiVersion, + autoFunctionCallingTurnLimit = baseOptions.autoFunctionCallingTurnLimit, + customApiClientHeader = customApiClientHeader + ) + } +}