Skip to content

Commit e289972

Browse files
authored
Merge branch 'main' into rl.fix.make.changelog
2 parents ac6da09 + d18c258 commit e289972

File tree

16 files changed

+311
-14
lines changed

16 files changed

+311
-14
lines changed

.github/workflows/ai-nightlies.yml renamed to .github/workflows/ai-daily-tests.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1-
name: Firebase AI Nightlies
1+
name: Firebase AI Daily Tests
22

33
on:
44
schedule:
55
- cron: 2 7 * * * # Runs automatically once a day
66
workflow_dispatch: # Allow triggering the workflow manually
77

8+
permissions:
9+
contents: read
10+
811
jobs:
9-
nightlies:
10-
name: "Nightlies"
12+
dailies:
13+
name: "Daily Tests"
1114
runs-on: ubuntu-latest
1215

1316
steps:
1417
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
1518
with:
16-
fetch-depth: 2
1719
submodules: true
1820

1921
- name: Enable KVM
@@ -48,7 +50,7 @@ jobs:
4850
FTL_RESULTS_DIR: ${{ format('logs/{0}/{1}_{2}/artifacts/', github.workflow, github.run_id, github.run_attempt) }}
4951
FIREBASE_APP_CHECK_DEBUG_SECRET: ${{ secrets.FIREBASE_APP_CHECK_DEBUG_SECRET }}
5052
with:
51-
api-level: 31
53+
api-level: 34
5254
arch: x86_64
5355
ram-size: 4096M
5456
heap-size: 4096M

.github/workflows/dataconnect.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ env:
3434
FDC_JAVA_VERSION: ${{ inputs.javaVersion || '17' }}
3535
FDC_ANDROID_EMULATOR_API_LEVEL: ${{ inputs.androidEmulatorApiLevel || '34' }}
3636
FDC_NODEJS_VERSION: ${{ inputs.nodeJsVersion || '20' }}
37-
FDC_FIREBASE_TOOLS_VERSION: ${{ inputs.firebaseToolsVersion || '14.12.0' }}
37+
FDC_FIREBASE_TOOLS_VERSION: ${{ inputs.firebaseToolsVersion || '14.15.1' }}
3838
FDC_FIREBASE_TOOLS_DIR: /tmp/firebase-tools
3939
FDC_FIREBASE_COMMAND: /tmp/firebase-tools/node_modules/.bin/firebase
4040
FDC_PYTHON_VERSION: ${{ inputs.pythonVersion || '3.13' }}

.github/workflows/dataconnect_demo_app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ on:
1818

1919
env:
2020
FDC_NODE_VERSION: ${{ inputs.nodeVersion || '20' }}
21-
FDC_FIREBASE_TOOLS_VERSION: ${{ inputs.firebaseToolsVersion || '14.12.0' }}
21+
FDC_FIREBASE_TOOLS_VERSION: ${{ inputs.firebaseToolsVersion || '14.15.1' }}
2222
FDC_JAVA_VERSION: ${{ inputs.javaVersion || '17' }}
2323
FDC_FIREBASE_TOOLS_DIR: ${{ github.workspace }}/firebase-tools
2424
FDC_FIREBASE_COMMAND: ${{ github.workspace }}/firebase-tools/node_modules/.bin/firebase

firebase-ai/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Unreleased
22

3+
- [feature] Introduced `MissingPermissionsException`, which is thrown when the necessary permissions
4+
have not been granted by the user.
5+
- [feature] Added helper functions to `LiveSession` to allow developers to track the status of the
6+
audio session and the underlying websocket connection.
7+
- [changed] Added new values to `HarmCategory` (#7324)
8+
39
# 17.2.0
410

511
- [feature] Added support for returning thought summaries, which are synthesized versions of a

firebase-ai/api.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,10 @@ package com.google.firebase.ai.type {
531531
field public static final com.google.firebase.ai.type.HarmCategory DANGEROUS_CONTENT;
532532
field public static final com.google.firebase.ai.type.HarmCategory HARASSMENT;
533533
field public static final com.google.firebase.ai.type.HarmCategory HATE_SPEECH;
534+
field public static final com.google.firebase.ai.type.HarmCategory IMAGE_DANGEROUS_CONTENT;
535+
field public static final com.google.firebase.ai.type.HarmCategory IMAGE_HARASSMENT;
536+
field public static final com.google.firebase.ai.type.HarmCategory IMAGE_HATE;
537+
field public static final com.google.firebase.ai.type.HarmCategory IMAGE_SEXUALLY_EXPLICIT;
534538
field public static final com.google.firebase.ai.type.HarmCategory SEXUALLY_EXPLICIT;
535539
field public static final com.google.firebase.ai.type.HarmCategory UNKNOWN;
536540
}
@@ -879,6 +883,8 @@ package com.google.firebase.ai.type {
879883

880884
@com.google.firebase.ai.type.PublicPreviewAPI public final class LiveSession {
881885
method public suspend Object? close(kotlin.coroutines.Continuation<? super kotlin.Unit>);
886+
method public boolean isAudioConversationActive();
887+
method public boolean isClosed();
882888
method public kotlinx.coroutines.flow.Flow<com.google.firebase.ai.type.LiveServerMessage> receive();
883889
method public suspend Object? send(com.google.firebase.ai.type.Content content, kotlin.coroutines.Continuation<? super kotlin.Unit>);
884890
method public suspend Object? send(String text, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -918,6 +924,10 @@ package com.google.firebase.ai.type {
918924
method public static String? asTextOrNull(com.google.firebase.ai.type.Part);
919925
}
920926

927+
public final class PermissionMissingException extends com.google.firebase.ai.type.FirebaseAIException {
928+
ctor public PermissionMissingException(String message, Throwable? cause = null);
929+
}
930+
921931
public final class PromptBlockedException extends com.google.firebase.ai.type.FirebaseAIException {
922932
method public com.google.firebase.ai.type.GenerateContentResponse? getResponse();
923933
property public final com.google.firebase.ai.type.GenerateContentResponse? response;

firebase-ai/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
version=17.2.1
15+
version=17.3.0
1616
latestReleasedVersion=17.2.0

firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ internal constructor(
5555
private val tools: List<Tool>? = null,
5656
private val systemInstruction: Content? = null,
5757
private val location: String,
58-
private val controller: APIController,
58+
private val firebaseApp: FirebaseApp,
59+
private val controller: APIController
5960
) {
6061
internal constructor(
6162
modelName: String,
@@ -78,6 +79,7 @@ internal constructor(
7879
tools,
7980
systemInstruction,
8081
location,
82+
firebaseApp,
8183
APIController(
8284
apiKey,
8385
modelName,
@@ -119,7 +121,11 @@ internal constructor(
119121
val receivedJson = JSON.parseToJsonElement(receivedJsonStr)
120122

121123
return if (receivedJson is JsonObject && "setupComplete" in receivedJson) {
122-
LiveSession(session = webSession, blockingDispatcher = blockingDispatcher)
124+
LiveSession(
125+
session = webSession,
126+
blockingDispatcher = blockingDispatcher,
127+
firebaseApp = firebaseApp
128+
)
123129
} else {
124130
webSession.close()
125131
throw ServiceConnectionHandshakeFailedException("Unable to connect to the server")

firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Exceptions.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ public class AudioRecordInitializationFailedException(message: String) :
231231
public class ServiceConnectionHandshakeFailedException(message: String, cause: Throwable? = null) :
232232
FirebaseAIException(message, cause)
233233

234+
/** The request is missing a permission that is required to perform the requested operation. */
235+
public class PermissionMissingException(message: String, cause: Throwable? = null) :
236+
FirebaseAIException(message, cause)
237+
234238
/** Catch all case for exceptions not explicitly expected. */
235239
public class UnknownException internal constructor(message: String, cause: Throwable? = null) :
236240
FirebaseAIException(message, cause)

firebase-ai/src/main/kotlin/com/google/firebase/ai/type/HarmCategory.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ public class HarmCategory private constructor(public val ordinal: Int) {
3131
SEXUALLY_EXPLICIT -> Internal.SEXUALLY_EXPLICIT
3232
DANGEROUS_CONTENT -> Internal.DANGEROUS_CONTENT
3333
CIVIC_INTEGRITY -> Internal.CIVIC_INTEGRITY
34+
IMAGE_HATE -> Internal.IMAGE_HATE
35+
IMAGE_DANGEROUS_CONTENT -> Internal.IMAGE_DANGEROUS_CONTENT
36+
IMAGE_HARASSMENT -> Internal.IMAGE_HARASSMENT
37+
IMAGE_SEXUALLY_EXPLICIT -> Internal.IMAGE_SEXUALLY_EXPLICIT
3438
UNKNOWN -> Internal.UNKNOWN
3539
else -> throw makeMissingCaseException("HarmCategory", ordinal)
3640
}
@@ -41,7 +45,11 @@ public class HarmCategory private constructor(public val ordinal: Int) {
4145
@SerialName("HARM_CATEGORY_HATE_SPEECH") HATE_SPEECH,
4246
@SerialName("HARM_CATEGORY_SEXUALLY_EXPLICIT") SEXUALLY_EXPLICIT,
4347
@SerialName("HARM_CATEGORY_DANGEROUS_CONTENT") DANGEROUS_CONTENT,
44-
@SerialName("HARM_CATEGORY_CIVIC_INTEGRITY") CIVIC_INTEGRITY;
48+
@SerialName("HARM_CATEGORY_CIVIC_INTEGRITY") CIVIC_INTEGRITY,
49+
@SerialName("HARM_CATEGORY_IMAGE_HATE") IMAGE_HATE,
50+
@SerialName("HARM_CATEGORY_IMAGE_DANGEROUS_CONTENT") IMAGE_DANGEROUS_CONTENT,
51+
@SerialName("HARM_CATEGORY_IMAGE_HARASSMENT") IMAGE_HARASSMENT,
52+
@SerialName("HARM_CATEGORY_IMAGE_SEXUALLY_EXPLICIT") IMAGE_SEXUALLY_EXPLICIT;
4553

4654
internal object Serializer : KSerializer<Internal> by FirstOrdinalSerializer(Internal::class)
4755

@@ -52,6 +60,10 @@ public class HarmCategory private constructor(public val ordinal: Int) {
5260
SEXUALLY_EXPLICIT -> HarmCategory.SEXUALLY_EXPLICIT
5361
DANGEROUS_CONTENT -> HarmCategory.DANGEROUS_CONTENT
5462
CIVIC_INTEGRITY -> HarmCategory.CIVIC_INTEGRITY
63+
IMAGE_HATE -> HarmCategory.IMAGE_HATE
64+
IMAGE_DANGEROUS_CONTENT -> HarmCategory.IMAGE_DANGEROUS_CONTENT
65+
IMAGE_HARASSMENT -> HarmCategory.IMAGE_HARASSMENT
66+
IMAGE_SEXUALLY_EXPLICIT -> HarmCategory.IMAGE_SEXUALLY_EXPLICIT
5567
else -> HarmCategory.UNKNOWN
5668
}
5769
}
@@ -73,5 +85,17 @@ public class HarmCategory private constructor(public val ordinal: Int) {
7385

7486
/** Content that may be used to harm civic integrity. */
7587
@JvmField public val CIVIC_INTEGRITY: HarmCategory = HarmCategory(5)
88+
89+
/** Content that is image hate. */
90+
@JvmField public val IMAGE_HATE: HarmCategory = HarmCategory(6)
91+
92+
/** Image dangerous content. */
93+
@JvmField public val IMAGE_DANGEROUS_CONTENT: HarmCategory = HarmCategory(7)
94+
95+
/** Content is image harassment. */
96+
@JvmField public val IMAGE_HARASSMENT: HarmCategory = HarmCategory(8)
97+
98+
/** Image sexually explicit content. */
99+
@JvmField public val IMAGE_SEXUALLY_EXPLICIT: HarmCategory = HarmCategory(9)
76100
}
77101
}

firebase-ai/src/main/kotlin/com/google/firebase/ai/type/LiveSession.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
package com.google.firebase.ai.type
1818

1919
import android.Manifest.permission.RECORD_AUDIO
20+
import android.content.pm.PackageManager
2021
import android.media.AudioFormat
2122
import android.media.AudioTrack
2223
import android.util.Log
2324
import androidx.annotation.RequiresPermission
25+
import androidx.core.content.ContextCompat
26+
import com.google.firebase.FirebaseApp
2427
import com.google.firebase.ai.common.JSON
2528
import com.google.firebase.ai.common.util.CancelledCoroutineScope
2629
import com.google.firebase.ai.common.util.accumulateUntil
@@ -58,7 +61,8 @@ public class LiveSession
5861
internal constructor(
5962
private val session: ClientWebSocketSession,
6063
@Blocking private val blockingDispatcher: CoroutineContext,
61-
private var audioHelper: AudioHelper? = null
64+
private var audioHelper: AudioHelper? = null,
65+
private val firebaseApp: FirebaseApp,
6266
) {
6367
/**
6468
* Coroutine scope that we batch data on for [startAudioConversation].
@@ -93,6 +97,14 @@ internal constructor(
9397
public suspend fun startAudioConversation(
9498
functionCallHandler: ((FunctionCallPart) -> FunctionResponsePart)? = null
9599
) {
100+
101+
val context = firebaseApp.applicationContext
102+
if (
103+
ContextCompat.checkSelfPermission(context, RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED
104+
) {
105+
throw PermissionMissingException("Audio access not provided by the user")
106+
}
107+
96108
FirebaseAIException.catchAsync {
97109
if (scope.isActive) {
98110
Log.w(
@@ -131,6 +143,12 @@ internal constructor(
131143
}
132144
}
133145

146+
/** Indicates whether the underlying websocket connection is active. */
147+
public fun isClosed(): Boolean = !(session.isActive && !session.incoming.tryReceive().isClosed)
148+
149+
/** Indicates whether an audio conversation is being used for this session object. */
150+
public fun isAudioConversationActive(): Boolean = (audioHelper != null)
151+
134152
/**
135153
* Receives responses from the model for both streaming and standard requests.
136154
*

0 commit comments

Comments
 (0)