From 0eeb7a2ec94b9ac1c4dcb9d21200c79f63a50c71 Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 9 Jul 2025 12:04:35 -0500 Subject: [PATCH 01/40] Add config support --- FirebaseAI/Sources/FirebaseAI.swift | 37 ++++++++--- FirebaseAI/Sources/FirebaseAIConfig.swift | 63 +++++++++++++++++++ FirebaseAI/Sources/GenerativeAIService.swift | 24 ++++++- FirebaseAI/Sources/GenerativeModel.swift | 6 +- .../Types/Public/Imagen/ImagenModel.swift | 5 +- 5 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 FirebaseAI/Sources/FirebaseAIConfig.swift diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index 48f7183d4e6..d7a9db704b1 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -32,13 +32,18 @@ public final class FirebaseAI: Sendable { /// ``FirebaseApp``. /// - backend: The backend API for the Firebase AI SDK; if not specified, uses the default /// ``Backend/googleAI()`` (Gemini Developer API). + /// - config: Configuration options for the Firebase AI SDK that propogate across all models + /// created. Uses default options when not specified, see the ``FirebaseAIConfig`` + /// documentation for more information. /// - Returns: A `FirebaseAI` instance, configured with the custom `FirebaseApp`. public static func firebaseAI(app: FirebaseApp? = nil, - backend: Backend = .googleAI()) -> FirebaseAI { + backend: Backend = .googleAI(), + config: FirebaseAIConfig = FirebaseAIConfig()) -> FirebaseAI { let instance = createInstance( app: app, location: backend.location, - apiConfig: backend.apiConfig + apiConfig: backend.apiConfig, + aiConfig: config ) // Verify that the `FirebaseAI` instance is always configured with the production endpoint since // this is the public API surface for creating an instance. @@ -90,7 +95,8 @@ public final class FirebaseAI: Sendable { tools: tools, toolConfig: toolConfig, systemInstruction: systemInstruction, - requestOptions: requestOptions + requestOptions: requestOptions, + aiConfig: aiConfig ) } @@ -126,7 +132,8 @@ public final class FirebaseAI: Sendable { apiConfig: apiConfig, generationConfig: generationConfig, safetySettings: safetySettings, - requestOptions: requestOptions + requestOptions: requestOptions, + aiConfig: aiConfig ) } @@ -141,6 +148,8 @@ public final class FirebaseAI: Sendable { let apiConfig: APIConfig + let aiConfig: FirebaseAIConfig + /// A map of active `FirebaseAI` instances keyed by the `FirebaseApp` name and the `location`, /// in the format `appName:location`. private nonisolated(unsafe) static var instances: [InstanceKey: FirebaseAI] = [:] @@ -156,7 +165,7 @@ public final class FirebaseAI: Sendable { ) static func createInstance(app: FirebaseApp?, location: String?, - apiConfig: APIConfig) -> FirebaseAI { + apiConfig: APIConfig, aiConfig: FirebaseAIConfig) -> FirebaseAI { guard let app = app ?? FirebaseApp.app() else { fatalError("No instance of the default Firebase app was found.") } @@ -166,16 +175,26 @@ public final class FirebaseAI: Sendable { // Unlock before the function returns. defer { os_unfair_lock_unlock(&instancesLock) } - let instanceKey = InstanceKey(appName: app.name, location: location, apiConfig: apiConfig) + let instanceKey = InstanceKey( + appName: app.name, + location: location, + apiConfig: apiConfig, + aiConfig: aiConfig + ) if let instance = instances[instanceKey] { return instance } - let newInstance = FirebaseAI(app: app, location: location, apiConfig: apiConfig) + let newInstance = FirebaseAI( + app: app, + location: location, + apiConfig: apiConfig, + aiConfig: aiConfig + ) instances[instanceKey] = newInstance return newInstance } - init(app: FirebaseApp, location: String?, apiConfig: APIConfig) { + init(app: FirebaseApp, location: String?, apiConfig: APIConfig, aiConfig: FirebaseAIConfig) { guard let projectID = app.options.projectID else { fatalError("The Firebase app named \"\(app.name)\" has no project ID in its configuration.") } @@ -195,6 +214,7 @@ public final class FirebaseAI: Sendable { ) self.apiConfig = apiConfig self.location = location + self.aiConfig = aiConfig } func modelResourceName(modelName: String) -> String { @@ -249,5 +269,6 @@ public final class FirebaseAI: Sendable { let appName: String let location: String? let apiConfig: APIConfig + let aiConfig: FirebaseAIConfig } } diff --git a/FirebaseAI/Sources/FirebaseAIConfig.swift b/FirebaseAI/Sources/FirebaseAIConfig.swift new file mode 100644 index 00000000000..31d2fda2f5b --- /dev/null +++ b/FirebaseAI/Sources/FirebaseAIConfig.swift @@ -0,0 +1,63 @@ +// Copyright 2025 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. + +/// Configuration options for ``FirebaseAI``, which persists across all models. +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +public struct FirebaseAIConfig: Sendable, Hashable, Encodable { + /// Options for App Check specific behavior within a ``FirebaseAI`` instance. + let appCheck: AppCheckOptions + + /// Creates a new ``FirebaseAI`` value. + /// + /// - Parameters: + /// - appCheck: Optionally configure certain behavior with how App Check is used. + public init(appCheck: AppCheckOptions = AppCheckOptions()) { + self.appCheck = appCheck + } +} + +/// Configurable options for how App Check is used within a ``FirebaseAI`` instance. +/// +/// Can be set when creating a ``FirebaseAIConfig``. +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +public struct AppCheckOptions: Sendable, Hashable, Encodable { + /// Use `limitedUseTokens`, instead of the standard cached tokens, when sending requests + /// to the backend. + let requireLimitedUseTokens: Bool + + /// Creates a new ``AppCheckOptions`` value. + /// + /// - Parameters: + /// - requiredLimitedUseTokens: When sending tokens to the backend, this option enables + /// the usage of App Check's `limitedUseTokens` instead of the standard cached tokens. + /// + /// A new `limitedUseToken` will be generated for each request; providing a lower attack + /// surface for malicious parties to hijack tokens. When used alongside [replay protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection), + /// `limitedUseTokens` are also _consumed_ after each request, ensuring they can't be used + /// again. + /// + /// _To prevent breakage, this flag is set to `false` by default._ + /// + /// > Important: Replay protection is not currently supported for the FirebaseAI backend. + /// > While this feature is being developed, you can still migrate to using `limitedUseTokens`. + /// > Because `limitedUseTokens` are backwards compatable, you can still use them without replay + /// > protection. Due to their shorter TTL over standard App Check tokens, they still provide a + /// > security benefit. + /// > + /// > Migrating to `limitedUseTokens` ahead of time will also allow you to enable replay + /// > protection down the road (when support is added), without breaking your users. + public init(requiredLimitedUseTokens: Bool = false) { + requireLimitedUseTokens = requiredLimitedUseTokens + } +} diff --git a/FirebaseAI/Sources/GenerativeAIService.swift b/FirebaseAI/Sources/GenerativeAIService.swift index e1538af997f..f63f2869d4e 100644 --- a/FirebaseAI/Sources/GenerativeAIService.swift +++ b/FirebaseAI/Sources/GenerativeAIService.swift @@ -30,9 +30,12 @@ struct GenerativeAIService { private let urlSession: URLSession - init(firebaseInfo: FirebaseInfo, urlSession: URLSession) { + private let aiConfig: FirebaseAIConfig + + init(firebaseInfo: FirebaseInfo, urlSession: URLSession, aiConfig: FirebaseAIConfig) { self.firebaseInfo = firebaseInfo self.urlSession = urlSession + self.aiConfig = aiConfig } func loadRequest(request: T) async throws -> T.Response { @@ -177,7 +180,7 @@ struct GenerativeAIService { urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") if let appCheck = firebaseInfo.appCheck { - let tokenResult = await appCheck.getToken(forcingRefresh: false) + let tokenResult = await fetchAppCheckToken(appCheck: appCheck) urlRequest.setValue(tokenResult.token, forHTTPHeaderField: "X-Firebase-AppCheck") if let error = tokenResult.error { AILog.error( @@ -207,6 +210,23 @@ struct GenerativeAIService { return urlRequest } + private func fetchAppCheckToken(appCheck: AppCheckInterop) async + -> FIRAppCheckTokenResultInterop { + if aiConfig.appCheck.requireLimitedUseTokens { + if let token = await appCheck.getLimitedUseToken?() { + return token + } + + AILog.error( + code: .appCheckTokenFetchFailed, + "Missing getLimitedUseToken() function, but requireLimitedUseTokens was enabled." + ) + // falls back to standard token + } + + return await appCheck.getToken(forcingRefresh: false) + } + private func httpResponse(urlResponse: URLResponse) throws -> HTTPURLResponse { // The following condition should always be true: "Whenever you make HTTP URL load requests, any // response objects you get back from the URLSession, NSURLConnection, or NSURLDownload class diff --git a/FirebaseAI/Sources/GenerativeModel.swift b/FirebaseAI/Sources/GenerativeModel.swift index 8d3f5e043a7..8caa33bcaf7 100644 --- a/FirebaseAI/Sources/GenerativeModel.swift +++ b/FirebaseAI/Sources/GenerativeModel.swift @@ -76,6 +76,7 @@ public final class GenerativeModel: Sendable { /// only text content is supported. /// - requestOptions: Configuration parameters for sending requests to the backend. /// - urlSession: The `URLSession` to use for requests; defaults to `URLSession.shared`. + /// - aiConfig: Configuration for various behavior shared across models. init(modelName: String, modelResourceName: String, firebaseInfo: FirebaseInfo, @@ -86,13 +87,14 @@ public final class GenerativeModel: Sendable { toolConfig: ToolConfig? = nil, systemInstruction: ModelContent? = nil, requestOptions: RequestOptions, - urlSession: URLSession = GenAIURLSession.default) { + urlSession: URLSession = GenAIURLSession.default, aiConfig: FirebaseAIConfig) { self.modelName = modelName self.modelResourceName = modelResourceName self.apiConfig = apiConfig generativeAIService = GenerativeAIService( firebaseInfo: firebaseInfo, - urlSession: urlSession + urlSession: urlSession, + aiConfig: aiConfig ) self.generationConfig = generationConfig self.safetySettings = safetySettings diff --git a/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift index e6f96df511a..dca91acb3f9 100644 --- a/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift +++ b/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift @@ -53,12 +53,13 @@ public final class ImagenModel { generationConfig: ImagenGenerationConfig?, safetySettings: ImagenSafetySettings?, requestOptions: RequestOptions, - urlSession: URLSession = GenAIURLSession.default) { + urlSession: URLSession = GenAIURLSession.default, aiConfig: FirebaseAIConfig) { self.modelResourceName = modelResourceName self.apiConfig = apiConfig generativeAIService = GenerativeAIService( firebaseInfo: firebaseInfo, - urlSession: urlSession + urlSession: urlSession, + aiConfig: aiConfig ) self.generationConfig = generationConfig self.safetySettings = safetySettings From 6c878ff0559e2677a5c25ad00f6d66e51ccdee0c Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 9 Jul 2025 12:06:19 -0500 Subject: [PATCH 02/40] fmt --- FirebaseAI/Sources/FirebaseAIConfig.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseAI/Sources/FirebaseAIConfig.swift b/FirebaseAI/Sources/FirebaseAIConfig.swift index 31d2fda2f5b..f0f2846a121 100644 --- a/FirebaseAI/Sources/FirebaseAIConfig.swift +++ b/FirebaseAI/Sources/FirebaseAIConfig.swift @@ -50,10 +50,10 @@ public struct AppCheckOptions: Sendable, Hashable, Encodable { /// _To prevent breakage, this flag is set to `false` by default._ /// /// > Important: Replay protection is not currently supported for the FirebaseAI backend. - /// > While this feature is being developed, you can still migrate to using `limitedUseTokens`. - /// > Because `limitedUseTokens` are backwards compatable, you can still use them without replay - /// > protection. Due to their shorter TTL over standard App Check tokens, they still provide a - /// > security benefit. + /// > While this feature is being developed, you can still migrate to using + /// > `limitedUseTokens`. Because `limitedUseTokens` are backwards compatable, you can still + /// > use them without replay protection. Due to their shorter TTL over standard App Check + /// > tokens, they still provide a security benefit. /// > /// > Migrating to `limitedUseTokens` ahead of time will also allow you to enable replay /// > protection down the road (when support is added), without breaking your users. From e6de038fcef32d55c5a36631e828423c052cc33c Mon Sep 17 00:00:00 2001 From: Daymon Date: Fri, 11 Jul 2025 12:03:30 -0500 Subject: [PATCH 03/40] Migrate to nested config --- ...seAIConfig.swift => AppCheckOptions.swift} | 23 +++------------ FirebaseAI/Sources/FirebaseAI.swift | 29 ++++++++++++++----- 2 files changed, 26 insertions(+), 26 deletions(-) rename FirebaseAI/Sources/{FirebaseAIConfig.swift => AppCheckOptions.swift} (72%) diff --git a/FirebaseAI/Sources/FirebaseAIConfig.swift b/FirebaseAI/Sources/AppCheckOptions.swift similarity index 72% rename from FirebaseAI/Sources/FirebaseAIConfig.swift rename to FirebaseAI/Sources/AppCheckOptions.swift index f0f2846a121..6411eb39e12 100644 --- a/FirebaseAI/Sources/FirebaseAIConfig.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -12,24 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// Configuration options for ``FirebaseAI``, which persists across all models. -@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public struct FirebaseAIConfig: Sendable, Hashable, Encodable { - /// Options for App Check specific behavior within a ``FirebaseAI`` instance. - let appCheck: AppCheckOptions - - /// Creates a new ``FirebaseAI`` value. - /// - /// - Parameters: - /// - appCheck: Optionally configure certain behavior with how App Check is used. - public init(appCheck: AppCheckOptions = AppCheckOptions()) { - self.appCheck = appCheck - } -} - /// Configurable options for how App Check is used within a ``FirebaseAI`` instance. /// -/// Can be set when creating a ``FirebaseAIConfig``. +/// Can be set when creating a ``FirebaseAI.Config``. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct AppCheckOptions: Sendable, Hashable, Encodable { /// Use `limitedUseTokens`, instead of the standard cached tokens, when sending requests @@ -39,7 +24,7 @@ public struct AppCheckOptions: Sendable, Hashable, Encodable { /// Creates a new ``AppCheckOptions`` value. /// /// - Parameters: - /// - requiredLimitedUseTokens: When sending tokens to the backend, this option enables + /// - requireLimitedUseTokens: When sending tokens to the backend, this option enables /// the usage of App Check's `limitedUseTokens` instead of the standard cached tokens. /// /// A new `limitedUseToken` will be generated for each request; providing a lower attack @@ -57,7 +42,7 @@ public struct AppCheckOptions: Sendable, Hashable, Encodable { /// > /// > Migrating to `limitedUseTokens` ahead of time will also allow you to enable replay /// > protection down the road (when support is added), without breaking your users. - public init(requiredLimitedUseTokens: Bool = false) { - requireLimitedUseTokens = requiredLimitedUseTokens + public init(requireLimitedUseTokens: Bool = false) { + self.requireLimitedUseTokens = requireLimitedUseTokens } } diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index d7a9db704b1..18b2653598e 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -33,12 +33,12 @@ public final class FirebaseAI: Sendable { /// - backend: The backend API for the Firebase AI SDK; if not specified, uses the default /// ``Backend/googleAI()`` (Gemini Developer API). /// - config: Configuration options for the Firebase AI SDK that propogate across all models - /// created. Uses default options when not specified, see the ``FirebaseAIConfig`` + /// created. Uses default options when not specified, see the ``FirebaseAI.Config`` /// documentation for more information. /// - Returns: A `FirebaseAI` instance, configured with the custom `FirebaseApp`. public static func firebaseAI(app: FirebaseApp? = nil, backend: Backend = .googleAI(), - config: FirebaseAIConfig = FirebaseAIConfig()) -> FirebaseAI { + config: FirebaseAI.Config = .config(appCheck: AppCheckOptions())) -> FirebaseAI { let instance = createInstance( app: app, location: backend.location, @@ -140,7 +140,22 @@ public final class FirebaseAI: Sendable { /// Class to enable FirebaseAI to register via the Objective-C based Firebase component system /// to include FirebaseAI in the userAgent. @objc(FIRVertexAIComponent) class FirebaseVertexAIComponent: NSObject {} - + + /// Configuration options for ``FirebaseAI``, which persists across all models. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) + public struct Config : Sendable, Hashable, Encodable { + /// Options for App Check specific behavior within a ``FirebaseAI`` instance. + let appCheck: AppCheckOptions + + /// Creates a new ``FirebaseAI.Config`` value. + /// + /// - Parameters: + /// - appCheck: Optionally configure certain behavior with how App Check is used. + public static func config(appCheck: AppCheckOptions = AppCheckOptions()) -> Config { + Config(appCheck: appCheck) + } + } + // MARK: - Private /// Firebase data relevant to Firebase AI. @@ -148,7 +163,7 @@ public final class FirebaseAI: Sendable { let apiConfig: APIConfig - let aiConfig: FirebaseAIConfig + let aiConfig: FirebaseAI.Config /// A map of active `FirebaseAI` instances keyed by the `FirebaseApp` name and the `location`, /// in the format `appName:location`. @@ -165,7 +180,7 @@ public final class FirebaseAI: Sendable { ) static func createInstance(app: FirebaseApp?, location: String?, - apiConfig: APIConfig, aiConfig: FirebaseAIConfig) -> FirebaseAI { + apiConfig: APIConfig, aiConfig: FirebaseAI.Config) -> FirebaseAI { guard let app = app ?? FirebaseApp.app() else { fatalError("No instance of the default Firebase app was found.") } @@ -194,7 +209,7 @@ public final class FirebaseAI: Sendable { return newInstance } - init(app: FirebaseApp, location: String?, apiConfig: APIConfig, aiConfig: FirebaseAIConfig) { + init(app: FirebaseApp, location: String?, apiConfig: APIConfig, aiConfig: FirebaseAI.Config) { guard let projectID = app.options.projectID else { fatalError("The Firebase app named \"\(app.name)\" has no project ID in its configuration.") } @@ -269,6 +284,6 @@ public final class FirebaseAI: Sendable { let appName: String let location: String? let apiConfig: APIConfig - let aiConfig: FirebaseAIConfig + let aiConfig: FirebaseAI.Config } } From 086e673479c42ded07a895b1e0b4cccaf4fa254d Mon Sep 17 00:00:00 2001 From: Daymon Date: Fri, 11 Jul 2025 12:04:09 -0500 Subject: [PATCH 04/40] Mix invalid references --- FirebaseAI/Sources/GenerativeAIService.swift | 4 ++-- FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FirebaseAI/Sources/GenerativeAIService.swift b/FirebaseAI/Sources/GenerativeAIService.swift index f63f2869d4e..beb0fcda3c3 100644 --- a/FirebaseAI/Sources/GenerativeAIService.swift +++ b/FirebaseAI/Sources/GenerativeAIService.swift @@ -30,9 +30,9 @@ struct GenerativeAIService { private let urlSession: URLSession - private let aiConfig: FirebaseAIConfig + private let aiConfig: FirebaseAI.Config - init(firebaseInfo: FirebaseInfo, urlSession: URLSession, aiConfig: FirebaseAIConfig) { + init(firebaseInfo: FirebaseInfo, urlSession: URLSession, aiConfig: FirebaseAI.Config) { self.firebaseInfo = firebaseInfo self.urlSession = urlSession self.aiConfig = aiConfig diff --git a/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift index dca91acb3f9..254e1fe21ea 100644 --- a/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift +++ b/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift @@ -53,7 +53,7 @@ public final class ImagenModel { generationConfig: ImagenGenerationConfig?, safetySettings: ImagenSafetySettings?, requestOptions: RequestOptions, - urlSession: URLSession = GenAIURLSession.default, aiConfig: FirebaseAIConfig) { + urlSession: URLSession = GenAIURLSession.default, aiConfig: FirebaseAI.Config) { self.modelResourceName = modelResourceName self.apiConfig = apiConfig generativeAIService = GenerativeAIService( From 4c9d28b47c9de8c84a0528f5919a066581565c27 Mon Sep 17 00:00:00 2001 From: Daymon Date: Fri, 11 Jul 2025 12:04:38 -0500 Subject: [PATCH 05/40] Fix invalid reference (again) --- FirebaseAI/Sources/GenerativeModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/GenerativeModel.swift b/FirebaseAI/Sources/GenerativeModel.swift index 8caa33bcaf7..0a229d3489c 100644 --- a/FirebaseAI/Sources/GenerativeModel.swift +++ b/FirebaseAI/Sources/GenerativeModel.swift @@ -87,7 +87,7 @@ public final class GenerativeModel: Sendable { toolConfig: ToolConfig? = nil, systemInstruction: ModelContent? = nil, requestOptions: RequestOptions, - urlSession: URLSession = GenAIURLSession.default, aiConfig: FirebaseAIConfig) { + urlSession: URLSession = GenAIURLSession.default, aiConfig: FirebaseAI.Config) { self.modelName = modelName self.modelResourceName = modelResourceName self.apiConfig = apiConfig From 8a06ee5d9a834bc44914c49ca43ca3cfedeebeba Mon Sep 17 00:00:00 2001 From: Daymon Date: Fri, 11 Jul 2025 12:06:51 -0500 Subject: [PATCH 06/40] fmt --- FirebaseAI/Sources/FirebaseAI.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index 18b2653598e..b9938ced25b 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -38,7 +38,8 @@ public final class FirebaseAI: Sendable { /// - Returns: A `FirebaseAI` instance, configured with the custom `FirebaseApp`. public static func firebaseAI(app: FirebaseApp? = nil, backend: Backend = .googleAI(), - config: FirebaseAI.Config = .config(appCheck: AppCheckOptions())) -> FirebaseAI { + config: FirebaseAI + .Config = .config(appCheck: AppCheckOptions())) -> FirebaseAI { let instance = createInstance( app: app, location: backend.location, @@ -140,13 +141,13 @@ public final class FirebaseAI: Sendable { /// Class to enable FirebaseAI to register via the Objective-C based Firebase component system /// to include FirebaseAI in the userAgent. @objc(FIRVertexAIComponent) class FirebaseVertexAIComponent: NSObject {} - + /// Configuration options for ``FirebaseAI``, which persists across all models. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) - public struct Config : Sendable, Hashable, Encodable { + public struct Config: Sendable, Hashable, Encodable { /// Options for App Check specific behavior within a ``FirebaseAI`` instance. let appCheck: AppCheckOptions - + /// Creates a new ``FirebaseAI.Config`` value. /// /// - Parameters: @@ -155,7 +156,7 @@ public final class FirebaseAI: Sendable { Config(appCheck: appCheck) } } - + // MARK: - Private /// Firebase data relevant to Firebase AI. From 865945590b87bf1fb2c72eeb08cedfbe7eb00e08 Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 15 Jul 2025 12:36:42 -0500 Subject: [PATCH 07/40] Remove unnecessary Encodable --- FirebaseAI/Sources/AppCheckOptions.swift | 2 +- FirebaseAI/Sources/FirebaseAI.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift index 6411eb39e12..9244c24df62 100644 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -16,7 +16,7 @@ /// /// Can be set when creating a ``FirebaseAI.Config``. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public struct AppCheckOptions: Sendable, Hashable, Encodable { +public struct AppCheckOptions: Sendable, Hashable { /// Use `limitedUseTokens`, instead of the standard cached tokens, when sending requests /// to the backend. let requireLimitedUseTokens: Bool diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index b9938ced25b..a791232a743 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -144,7 +144,7 @@ public final class FirebaseAI: Sendable { /// Configuration options for ``FirebaseAI``, which persists across all models. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) - public struct Config: Sendable, Hashable, Encodable { + public struct Config: Sendable, Hashable { /// Options for App Check specific behavior within a ``FirebaseAI`` instance. let appCheck: AppCheckOptions From 56d1f5f4cf2cb0b78a732ad466a03af10a1f0a51 Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 16 Jul 2025 15:33:21 -0500 Subject: [PATCH 08/40] Update AppCheckOptions.swift Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/Sources/AppCheckOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift index 9244c24df62..a61842ead65 100644 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -16,7 +16,7 @@ /// /// Can be set when creating a ``FirebaseAI.Config``. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public struct AppCheckOptions: Sendable, Hashable { +public struct AppCheckOptions: Sendable, Hashable, Equatable { /// Use `limitedUseTokens`, instead of the standard cached tokens, when sending requests /// to the backend. let requireLimitedUseTokens: Bool From 1b6c09c6789e11d5f76ee9de4b3ed6be356bfbb5 Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 16 Jul 2025 15:33:28 -0500 Subject: [PATCH 09/40] Update FirebaseAI.swift Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/Sources/FirebaseAI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index a791232a743..720de7cf1e6 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -144,7 +144,7 @@ public final class FirebaseAI: Sendable { /// Configuration options for ``FirebaseAI``, which persists across all models. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) - public struct Config: Sendable, Hashable { + public struct Config: Sendable, Hashable, Equatable { /// Options for App Check specific behavior within a ``FirebaseAI`` instance. let appCheck: AppCheckOptions From 1dfbce5cf176f53f4effea2b9fa4d53943a6ced6 Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 13 Aug 2025 10:57:41 -0500 Subject: [PATCH 10/40] Update FirebaseAI/Sources/AppCheckOptions.swift Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/Sources/AppCheckOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift index a61842ead65..6cb21e4f7f4 100644 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -27,7 +27,7 @@ public struct AppCheckOptions: Sendable, Hashable, Equatable { /// - requireLimitedUseTokens: When sending tokens to the backend, this option enables /// the usage of App Check's `limitedUseTokens` instead of the standard cached tokens. /// - /// A new `limitedUseToken` will be generated for each request; providing a lower attack + /// A new `limitedUseToken` will be generated for each request; providing a smaller attack /// surface for malicious parties to hijack tokens. When used alongside [replay protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection), /// `limitedUseTokens` are also _consumed_ after each request, ensuring they can't be used /// again. From 6dddd094de87067353aa1beefcdbfad60850fa60 Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 13 Aug 2025 10:58:14 -0500 Subject: [PATCH 11/40] Update FirebaseAI/Sources/AppCheckOptions.swift Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/Sources/AppCheckOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift index 6cb21e4f7f4..e4922bbbd79 100644 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -32,7 +32,7 @@ public struct AppCheckOptions: Sendable, Hashable, Equatable { /// `limitedUseTokens` are also _consumed_ after each request, ensuring they can't be used /// again. /// - /// _To prevent breakage, this flag is set to `false` by default._ + /// _This flag is set to `false` by default._ /// /// > Important: Replay protection is not currently supported for the FirebaseAI backend. /// > While this feature is being developed, you can still migrate to using From a188a795c8126e9f7924e969ee7fbe57cf8c6741 Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 13 Aug 2025 10:58:21 -0500 Subject: [PATCH 12/40] Update FirebaseAI/Sources/AppCheckOptions.swift Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/Sources/AppCheckOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift index e4922bbbd79..27ac1546b32 100644 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -36,7 +36,7 @@ public struct AppCheckOptions: Sendable, Hashable, Equatable { /// /// > Important: Replay protection is not currently supported for the FirebaseAI backend. /// > While this feature is being developed, you can still migrate to using - /// > `limitedUseTokens`. Because `limitedUseTokens` are backwards compatable, you can still + /// > `limitedUseTokens`. Because `limitedUseTokens` are backwards compatible, you can still /// > use them without replay protection. Due to their shorter TTL over standard App Check /// > tokens, they still provide a security benefit. /// > From fa2bc361ff71df0ab6865dbf1b300e3c21b23eda Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 13 Aug 2025 11:10:55 -0500 Subject: [PATCH 13/40] Add changelog entry --- FirebaseAI/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/FirebaseAI/CHANGELOG.md b/FirebaseAI/CHANGELOG.md index 47b2627da67..53697bbf001 100644 --- a/FirebaseAI/CHANGELOG.md +++ b/FirebaseAI/CHANGELOG.md @@ -1,3 +1,11 @@ +# Unreleased +- [feature] Added support for using App Check's `limitedUseTokens` feature when generating tokens. +This must be explicitly enabled via the new `FirebaseAI.Config` struct, when initializing an +instance of `FirebaseAI`. While [replay protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection) +is not currently supported for the Firebase AI Logic SDK, we plan to roll it out in the future. As +such, consumers should migrate to using `limitedUseTokens` _now_, so that when replay protection +support is added, consumers can enable it without breaking their app for users. + # 12.0.0 - [added] Added support for Grounding with Google Search. (#15014) - [removed] Removed `CountTokensResponse.totalBillableCharacters` which was From 8ab664c99b9a4b49145cac263dd9e769c1bed280 Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 13 Aug 2025 11:18:01 -0500 Subject: [PATCH 14/40] Update tests using `createInstance` --- FirebaseAI/Tests/Unit/VertexComponentTests.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/FirebaseAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift index 7202e01f4d6..31fff4b8d02 100644 --- a/FirebaseAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -155,12 +155,14 @@ class VertexComponentTests: XCTestCase { let vertex1 = FirebaseAI.createInstance( app: VertexComponentTests.app, location: location, - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta), + aiConfig: .config() ) let vertex2 = FirebaseAI.createInstance( app: VertexComponentTests.app, location: location, - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1), + aiConfig: .config() ) // Ensure they are different instances. @@ -181,7 +183,8 @@ class VertexComponentTests: XCTestCase { let vertex = FirebaseAI( app: app1, location: "transitory location", - apiConfig: FirebaseAI.defaultVertexAIAPIConfig + apiConfig: FirebaseAI.defaultVertexAIAPIConfig, + aiConfig: .config() ) weakVertex = vertex XCTAssertNotNil(weakVertex) @@ -208,7 +211,7 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_developerAPI_generativeLanguage() throws { let app = try XCTUnwrap(VertexComponentTests.app) let apiConfig = APIConfig(service: .googleAI(endpoint: .googleAIBypassProxy), version: .v1beta) - let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig) + let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig, aiConfig: .config()) let model = "test-model-name" let modelResourceName = vertex.modelResourceName(modelName: model) @@ -222,7 +225,7 @@ class VertexComponentTests: XCTestCase { service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) - let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig) + let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig, aiConfig: .config()) let model = "test-model-name" let projectID = vertex.firebaseInfo.projectID @@ -253,7 +256,7 @@ class VertexComponentTests: XCTestCase { service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) - let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig) + let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig, aiConfig: .config()) let modelResourceName = vertex.modelResourceName(modelName: modelName) let expectedSystemInstruction = ModelContent(role: nil, parts: systemInstruction.parts) From 860ee49b41b880819266700bd25b2d559c41a4f9 Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 13 Aug 2025 11:18:18 -0500 Subject: [PATCH 15/40] Use default params when initializing config --- FirebaseAI/Sources/FirebaseAI.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index 720de7cf1e6..d32de317c33 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -38,8 +38,7 @@ public final class FirebaseAI: Sendable { /// - Returns: A `FirebaseAI` instance, configured with the custom `FirebaseApp`. public static func firebaseAI(app: FirebaseApp? = nil, backend: Backend = .googleAI(), - config: FirebaseAI - .Config = .config(appCheck: AppCheckOptions())) -> FirebaseAI { + config: FirebaseAI.Config = .config()) -> FirebaseAI { let instance = createInstance( app: app, location: backend.location, From 187de4f34936b5013e9a03e90109c637cf35e6ba Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 13 Aug 2025 11:20:02 -0500 Subject: [PATCH 16/40] formatting --- .../Tests/Unit/VertexComponentTests.swift | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/FirebaseAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift index 31fff4b8d02..4d7309ca697 100644 --- a/FirebaseAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -211,7 +211,12 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_developerAPI_generativeLanguage() throws { let app = try XCTUnwrap(VertexComponentTests.app) let apiConfig = APIConfig(service: .googleAI(endpoint: .googleAIBypassProxy), version: .v1beta) - let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig, aiConfig: .config()) + let vertex = FirebaseAI.createInstance( + app: app, + location: nil, + apiConfig: apiConfig, + aiConfig: .config() + ) let model = "test-model-name" let modelResourceName = vertex.modelResourceName(modelName: model) @@ -225,7 +230,12 @@ class VertexComponentTests: XCTestCase { service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) - let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig, aiConfig: .config()) + let vertex = FirebaseAI.createInstance( + app: app, + location: nil, + apiConfig: apiConfig, + aiConfig: .config() + ) let model = "test-model-name" let projectID = vertex.firebaseInfo.projectID @@ -256,7 +266,12 @@ class VertexComponentTests: XCTestCase { service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) - let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig, aiConfig: .config()) + let vertex = FirebaseAI.createInstance( + app: app, + location: nil, + apiConfig: apiConfig, + aiConfig: .config() + ) let modelResourceName = vertex.modelResourceName(modelName: modelName) let expectedSystemInstruction = ModelContent(role: nil, parts: systemInstruction.parts) From 10f445cead2e5303103e464180b24bafb1b8b6b3 Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 13 Aug 2025 11:20:46 -0500 Subject: [PATCH 17/40] manual formatting --- FirebaseAI/Sources/GenerativeModel.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/GenerativeModel.swift b/FirebaseAI/Sources/GenerativeModel.swift index 0a229d3489c..d3c3bddeebf 100644 --- a/FirebaseAI/Sources/GenerativeModel.swift +++ b/FirebaseAI/Sources/GenerativeModel.swift @@ -87,7 +87,8 @@ public final class GenerativeModel: Sendable { toolConfig: ToolConfig? = nil, systemInstruction: ModelContent? = nil, requestOptions: RequestOptions, - urlSession: URLSession = GenAIURLSession.default, aiConfig: FirebaseAI.Config) { + urlSession: URLSession = GenAIURLSession.default, + aiConfig: FirebaseAI.Config) { self.modelName = modelName self.modelResourceName = modelResourceName self.apiConfig = apiConfig From 8ce15d28b2b787a363029280cb28e38a6aff5e4b Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 13 Aug 2025 11:29:14 -0500 Subject: [PATCH 18/40] Fix broken link in unit tests --- FirebaseAI/Tests/Unit/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Tests/Unit/README.md b/FirebaseAI/Tests/Unit/README.md index 9463d595294..88019041f9f 100644 --- a/FirebaseAI/Tests/Unit/README.md +++ b/FirebaseAI/Tests/Unit/README.md @@ -1,3 +1,3 @@ See the Firebase AI SDK -[README](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseVertexAI#unit-tests) +[README](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseAI#unit-tests) for required setup instructions. From ce0da477adb9ce1324ebdcde995101796bc91651 Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 13 Aug 2025 11:34:15 -0500 Subject: [PATCH 19/40] Add missing default arg on GenerativeModel for config --- FirebaseAI/Sources/GenerativeModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/GenerativeModel.swift b/FirebaseAI/Sources/GenerativeModel.swift index d3c3bddeebf..28dfa4e7eed 100644 --- a/FirebaseAI/Sources/GenerativeModel.swift +++ b/FirebaseAI/Sources/GenerativeModel.swift @@ -88,7 +88,7 @@ public final class GenerativeModel: Sendable { systemInstruction: ModelContent? = nil, requestOptions: RequestOptions, urlSession: URLSession = GenAIURLSession.default, - aiConfig: FirebaseAI.Config) { + aiConfig: FirebaseAI.Config = .config()) { self.modelName = modelName self.modelResourceName = modelResourceName self.apiConfig = apiConfig From 9a9a1c5b1e363d8af344ef3e38dc53f4d344b1c5 Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 13 Aug 2025 13:52:02 -0500 Subject: [PATCH 20/40] Update FirebaseAI/CHANGELOG.md Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/CHANGELOG.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/FirebaseAI/CHANGELOG.md b/FirebaseAI/CHANGELOG.md index 53697bbf001..3bcf708d016 100644 --- a/FirebaseAI/CHANGELOG.md +++ b/FirebaseAI/CHANGELOG.md @@ -1,10 +1,11 @@ # Unreleased -- [feature] Added support for using App Check's `limitedUseTokens` feature when generating tokens. -This must be explicitly enabled via the new `FirebaseAI.Config` struct, when initializing an -instance of `FirebaseAI`. While [replay protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection) -is not currently supported for the Firebase AI Logic SDK, we plan to roll it out in the future. As -such, consumers should migrate to using `limitedUseTokens` _now_, so that when replay protection -support is added, consumers can enable it without breaking their app for users. +- [feature] Added a new configuration option to require limited-use App + Check tokens for attesting Firebase AI Logic requests. This enhances + security against replay attacks. To use this feature, configure it + explicitly via the new `FirebaseAI.Config` struct when initializing + `FirebaseAI`. We recommend migrating to limited-use tokens now, so + your app will be ready to take advantage of replay protection when + it becomes available for Firebase AI Logic. # 12.0.0 - [added] Added support for Grounding with Google Search. (#15014) From 446f62d180cc6631b993f89c79bd1559bdc82683 Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 13 Aug 2025 14:01:35 -0500 Subject: [PATCH 21/40] Add error message --- FirebaseAI/Sources/GenerativeAIService.swift | 28 ++++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/FirebaseAI/Sources/GenerativeAIService.swift b/FirebaseAI/Sources/GenerativeAIService.swift index beb0fcda3c3..6e70beeb869 100644 --- a/FirebaseAI/Sources/GenerativeAIService.swift +++ b/FirebaseAI/Sources/GenerativeAIService.swift @@ -180,7 +180,7 @@ struct GenerativeAIService { urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") if let appCheck = firebaseInfo.appCheck { - let tokenResult = await fetchAppCheckToken(appCheck: appCheck) + let tokenResult = try await fetchAppCheckToken(appCheck: appCheck) urlRequest.setValue(tokenResult.token, forHTTPHeaderField: "X-Firebase-AppCheck") if let error = tokenResult.error { AILog.error( @@ -210,18 +210,24 @@ struct GenerativeAIService { return urlRequest } - private func fetchAppCheckToken(appCheck: AppCheckInterop) async + private func fetchAppCheckToken(appCheck: AppCheckInterop) async throws -> FIRAppCheckTokenResultInterop { - if aiConfig.appCheck.requireLimitedUseTokens { - if let token = await appCheck.getLimitedUseToken?() { - return token - } + if aiConfig.appCheck.requireLimitedUseTokens { + if let token = await appCheck.getLimitedUseToken?() { + return token + } - AILog.error( - code: .appCheckTokenFetchFailed, - "Missing getLimitedUseToken() function, but requireLimitedUseTokens was enabled." - ) - // falls back to standard token + let errorMessage = "The provided App Check token provider doesn't implement getLimitedUseToken(), but requireLimitedUseTokens was enabled."; + + #if Debug + fatalError(errorMessage) + #else + throw NSError( + domain: "com.google.firebase.ai.GenerativeAIService", + code: AILog.MessageCode.appCheckTokenFetchFailed.rawValue, + userInfo: [NSLocalizedDescriptionKey: errorMessage] + ) + #endif } return await appCheck.getToken(forcingRefresh: false) From 61c5ad6dd0757cfa85468ec1cf3fd4d32341f7d8 Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 13 Aug 2025 14:02:39 -0500 Subject: [PATCH 22/40] formatting --- FirebaseAI/Sources/GenerativeAIService.swift | 29 ++++++++++---------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/FirebaseAI/Sources/GenerativeAIService.swift b/FirebaseAI/Sources/GenerativeAIService.swift index 6e70beeb869..66f12528eff 100644 --- a/FirebaseAI/Sources/GenerativeAIService.swift +++ b/FirebaseAI/Sources/GenerativeAIService.swift @@ -212,22 +212,23 @@ struct GenerativeAIService { private func fetchAppCheckToken(appCheck: AppCheckInterop) async throws -> FIRAppCheckTokenResultInterop { - if aiConfig.appCheck.requireLimitedUseTokens { - if let token = await appCheck.getLimitedUseToken?() { - return token - } + if aiConfig.appCheck.requireLimitedUseTokens { + if let token = await appCheck.getLimitedUseToken?() { + return token + } - let errorMessage = "The provided App Check token provider doesn't implement getLimitedUseToken(), but requireLimitedUseTokens was enabled."; + let errorMessage = + "The provided App Check token provider doesn't implement getLimitedUseToken(), but requireLimitedUseTokens was enabled." - #if Debug - fatalError(errorMessage) - #else - throw NSError( - domain: "com.google.firebase.ai.GenerativeAIService", - code: AILog.MessageCode.appCheckTokenFetchFailed.rawValue, - userInfo: [NSLocalizedDescriptionKey: errorMessage] - ) - #endif + #if Debug + fatalError(errorMessage) + #else + throw NSError( + domain: "com.google.firebase.ai.GenerativeAIService", + code: AILog.MessageCode.appCheckTokenFetchFailed.rawValue, + userInfo: [NSLocalizedDescriptionKey: errorMessage] + ) + #endif } return await appCheck.getToken(forcingRefresh: false) From 9c551c77b25f1a2f6b0245150a4da98b93decfcc Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 13 Aug 2025 14:07:45 -0500 Subject: [PATCH 23/40] Update domain to be more adaptive --- FirebaseAI/Sources/GenerativeAIService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/GenerativeAIService.swift b/FirebaseAI/Sources/GenerativeAIService.swift index 66f12528eff..3c76630822d 100644 --- a/FirebaseAI/Sources/GenerativeAIService.swift +++ b/FirebaseAI/Sources/GenerativeAIService.swift @@ -224,7 +224,7 @@ struct GenerativeAIService { fatalError(errorMessage) #else throw NSError( - domain: "com.google.firebase.ai.GenerativeAIService", + domain: "\(Constants.baseErrorDomain).\(Self.self)", code: AILog.MessageCode.appCheckTokenFetchFailed.rawValue, userInfo: [NSLocalizedDescriptionKey: errorMessage] ) From cc578bdf9b079c29fb8e315a5e4298d34e0ca384 Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:02:42 -0500 Subject: [PATCH 24/40] Update AppCheckOptions.swift Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/Sources/AppCheckOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift index 27ac1546b32..4c9d7a981ee 100644 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -29,7 +29,7 @@ public struct AppCheckOptions: Sendable, Hashable, Equatable { /// /// A new `limitedUseToken` will be generated for each request; providing a smaller attack /// surface for malicious parties to hijack tokens. When used alongside [replay protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection), - /// `limitedUseTokens` are also _consumed_ after each request, ensuring they can't be used + /// limited-use tokens are also _consumed_ after each request, ensuring they can't be used /// again. /// /// _This flag is set to `false` by default._ From 604ec4c091a3d02f47ccf5030f228a45c599ab7d Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:02:51 -0500 Subject: [PATCH 25/40] Update AppCheckOptions.swift Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/Sources/AppCheckOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift index 4c9d7a981ee..c253d776562 100644 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -27,7 +27,7 @@ public struct AppCheckOptions: Sendable, Hashable, Equatable { /// - requireLimitedUseTokens: When sending tokens to the backend, this option enables /// the usage of App Check's `limitedUseTokens` instead of the standard cached tokens. /// - /// A new `limitedUseToken` will be generated for each request; providing a smaller attack + /// A new limited-use tokens will be generated for each request; providing a smaller attack /// surface for malicious parties to hijack tokens. When used alongside [replay protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection), /// limited-use tokens are also _consumed_ after each request, ensuring they can't be used /// again. From 9ef2511fe00cb6dedeb075600c0765f58ba35b59 Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:03:06 -0500 Subject: [PATCH 26/40] Update AppCheckOptions.swift Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/Sources/AppCheckOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift index c253d776562..062b989af5d 100644 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -25,7 +25,7 @@ public struct AppCheckOptions: Sendable, Hashable, Equatable { /// /// - Parameters: /// - requireLimitedUseTokens: When sending tokens to the backend, this option enables - /// the usage of App Check's `limitedUseTokens` instead of the standard cached tokens. + /// the usage of App Check's limited-use tokens instead of the standard cached tokens. /// /// A new limited-use tokens will be generated for each request; providing a smaller attack /// surface for malicious parties to hijack tokens. When used alongside [replay protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection), From 0857ffaaa92f799d3ff89f7bca8370bc46075fcb Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:34:02 -0500 Subject: [PATCH 27/40] Update AppCheckOptions.swift Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/Sources/AppCheckOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift index 062b989af5d..990f644e1de 100644 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -28,7 +28,7 @@ public struct AppCheckOptions: Sendable, Hashable, Equatable { /// the usage of App Check's limited-use tokens instead of the standard cached tokens. /// /// A new limited-use tokens will be generated for each request; providing a smaller attack - /// surface for malicious parties to hijack tokens. When used alongside [replay protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection), + /// surface for malicious parties to hijack tokens. When used alongside replay protection, /// limited-use tokens are also _consumed_ after each request, ensuring they can't be used /// again. /// From d3e31eb9656f3381af54e722c7b446896b6140eb Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:34:09 -0500 Subject: [PATCH 28/40] Update AppCheckOptions.swift Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/Sources/AppCheckOptions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift index 990f644e1de..235f689e4de 100644 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -36,11 +36,11 @@ public struct AppCheckOptions: Sendable, Hashable, Equatable { /// /// > Important: Replay protection is not currently supported for the FirebaseAI backend. /// > While this feature is being developed, you can still migrate to using - /// > `limitedUseTokens`. Because `limitedUseTokens` are backwards compatible, you can still + /// > limited-use tokens. Because limited-use tokens are backwards compatible, you can still /// > use them without replay protection. Due to their shorter TTL over standard App Check /// > tokens, they still provide a security benefit. /// > - /// > Migrating to `limitedUseTokens` ahead of time will also allow you to enable replay + /// > Migrating to limited-use tokens ahead of time will also allow you to enable replay /// > protection down the road (when support is added), without breaking your users. public init(requireLimitedUseTokens: Bool = false) { self.requireLimitedUseTokens = requireLimitedUseTokens From df24a840cc4564b3d0333aa38ed856f97333b217 Mon Sep 17 00:00:00 2001 From: Daymon <17409137+daymxn@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:58:38 -0500 Subject: [PATCH 29/40] Update FirebaseAI/Sources/AppCheckOptions.swift Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebaseAI/Sources/AppCheckOptions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift index 235f689e4de..c3eec18c0e8 100644 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ b/FirebaseAI/Sources/AppCheckOptions.swift @@ -40,8 +40,8 @@ public struct AppCheckOptions: Sendable, Hashable, Equatable { /// > use them without replay protection. Due to their shorter TTL over standard App Check /// > tokens, they still provide a security benefit. /// > - /// > Migrating to limited-use tokens ahead of time will also allow you to enable replay - /// > protection down the road (when support is added), without breaking your users. + /// > Migrating to limited-use tokens sooner minimizes disruption when support for replay + /// > protection is added. public init(requireLimitedUseTokens: Bool = false) { self.requireLimitedUseTokens = requireLimitedUseTokens } From 3e6c17d5ce5aff14412ef2f4c3dec8691bc10779 Mon Sep 17 00:00:00 2001 From: Daymon Date: Mon, 18 Aug 2025 12:27:44 -0500 Subject: [PATCH 30/40] Migrate to parameter instead of config struct --- FirebaseAI/Sources/AppCheckOptions.swift | 48 ---------------- FirebaseAI/Sources/FirebaseAI.swift | 55 ++++++++++--------- FirebaseAI/Sources/GenerativeAIService.swift | 8 +-- FirebaseAI/Sources/GenerativeModel.swift | 6 +- .../Types/Public/Imagen/ImagenModel.swift | 5 +- .../Tests/Unit/VertexComponentTests.swift | 10 ++-- 6 files changed, 44 insertions(+), 88 deletions(-) delete mode 100644 FirebaseAI/Sources/AppCheckOptions.swift diff --git a/FirebaseAI/Sources/AppCheckOptions.swift b/FirebaseAI/Sources/AppCheckOptions.swift deleted file mode 100644 index c3eec18c0e8..00000000000 --- a/FirebaseAI/Sources/AppCheckOptions.swift +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2025 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. - -/// Configurable options for how App Check is used within a ``FirebaseAI`` instance. -/// -/// Can be set when creating a ``FirebaseAI.Config``. -@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public struct AppCheckOptions: Sendable, Hashable, Equatable { - /// Use `limitedUseTokens`, instead of the standard cached tokens, when sending requests - /// to the backend. - let requireLimitedUseTokens: Bool - - /// Creates a new ``AppCheckOptions`` value. - /// - /// - Parameters: - /// - requireLimitedUseTokens: When sending tokens to the backend, this option enables - /// the usage of App Check's limited-use tokens instead of the standard cached tokens. - /// - /// A new limited-use tokens will be generated for each request; providing a smaller attack - /// surface for malicious parties to hijack tokens. When used alongside replay protection, - /// limited-use tokens are also _consumed_ after each request, ensuring they can't be used - /// again. - /// - /// _This flag is set to `false` by default._ - /// - /// > Important: Replay protection is not currently supported for the FirebaseAI backend. - /// > While this feature is being developed, you can still migrate to using - /// > limited-use tokens. Because limited-use tokens are backwards compatible, you can still - /// > use them without replay protection. Due to their shorter TTL over standard App Check - /// > tokens, they still provide a security benefit. - /// > - /// > Migrating to limited-use tokens sooner minimizes disruption when support for replay - /// > protection is added. - public init(requireLimitedUseTokens: Bool = false) { - self.requireLimitedUseTokens = requireLimitedUseTokens - } -} diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index d32de317c33..61c39e2b762 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -35,15 +35,33 @@ public final class FirebaseAI: Sendable { /// - config: Configuration options for the Firebase AI SDK that propogate across all models /// created. Uses default options when not specified, see the ``FirebaseAI.Config`` /// documentation for more information. + /// - useLimitedUseAppCheckTokens: When sending tokens to the backend, this option enables + /// the usage of App Check's limited-use tokens instead of the standard cached tokens. + /// + /// A new limited-use tokens will be generated for each request; providing a smaller attack + /// surface for malicious parties to hijack tokens. When used alongside replay protection, + /// limited-use tokens are also _consumed_ after each request, ensuring they can't be used + /// again. + /// + /// _This flag is set to `false` by default._ + /// + /// > Important: Replay protection is not currently supported for the FirebaseAI backend. + /// > While this feature is being developed, you can still migrate to using + /// > limited-use tokens. Because limited-use tokens are backwards compatible, you can still + /// > use them without replay protection. Due to their shorter TTL over standard App Check + /// > tokens, they still provide a security benefit. + /// > + /// > Migrating to limited-use tokens sooner minimizes disruption when support for replay + /// > protection is added. /// - Returns: A `FirebaseAI` instance, configured with the custom `FirebaseApp`. public static func firebaseAI(app: FirebaseApp? = nil, backend: Backend = .googleAI(), - config: FirebaseAI.Config = .config()) -> FirebaseAI { + useLimitedUseAppCheckTokens: Bool = false) -> FirebaseAI { let instance = createInstance( app: app, location: backend.location, apiConfig: backend.apiConfig, - aiConfig: config + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) // Verify that the `FirebaseAI` instance is always configured with the production endpoint since // this is the public API surface for creating an instance. @@ -96,7 +114,7 @@ public final class FirebaseAI: Sendable { toolConfig: toolConfig, systemInstruction: systemInstruction, requestOptions: requestOptions, - aiConfig: aiConfig + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) } @@ -133,7 +151,7 @@ public final class FirebaseAI: Sendable { generationConfig: generationConfig, safetySettings: safetySettings, requestOptions: requestOptions, - aiConfig: aiConfig + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) } @@ -141,21 +159,6 @@ public final class FirebaseAI: Sendable { /// to include FirebaseAI in the userAgent. @objc(FIRVertexAIComponent) class FirebaseVertexAIComponent: NSObject {} - /// Configuration options for ``FirebaseAI``, which persists across all models. - @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) - public struct Config: Sendable, Hashable, Equatable { - /// Options for App Check specific behavior within a ``FirebaseAI`` instance. - let appCheck: AppCheckOptions - - /// Creates a new ``FirebaseAI.Config`` value. - /// - /// - Parameters: - /// - appCheck: Optionally configure certain behavior with how App Check is used. - public static func config(appCheck: AppCheckOptions = AppCheckOptions()) -> Config { - Config(appCheck: appCheck) - } - } - // MARK: - Private /// Firebase data relevant to Firebase AI. @@ -163,7 +166,7 @@ public final class FirebaseAI: Sendable { let apiConfig: APIConfig - let aiConfig: FirebaseAI.Config + let useLimitedUseAppCheckTokens: Bool /// A map of active `FirebaseAI` instances keyed by the `FirebaseApp` name and the `location`, /// in the format `appName:location`. @@ -180,7 +183,7 @@ public final class FirebaseAI: Sendable { ) static func createInstance(app: FirebaseApp?, location: String?, - apiConfig: APIConfig, aiConfig: FirebaseAI.Config) -> FirebaseAI { + apiConfig: APIConfig, useLimitedUseAppCheckTokens: Bool) -> FirebaseAI { guard let app = app ?? FirebaseApp.app() else { fatalError("No instance of the default Firebase app was found.") } @@ -194,7 +197,7 @@ public final class FirebaseAI: Sendable { appName: app.name, location: location, apiConfig: apiConfig, - aiConfig: aiConfig + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) if let instance = instances[instanceKey] { return instance @@ -203,13 +206,13 @@ public final class FirebaseAI: Sendable { app: app, location: location, apiConfig: apiConfig, - aiConfig: aiConfig + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) instances[instanceKey] = newInstance return newInstance } - init(app: FirebaseApp, location: String?, apiConfig: APIConfig, aiConfig: FirebaseAI.Config) { + init(app: FirebaseApp, location: String?, apiConfig: APIConfig, useLimitedUseAppCheckTokens: Bool) { guard let projectID = app.options.projectID else { fatalError("The Firebase app named \"\(app.name)\" has no project ID in its configuration.") } @@ -229,7 +232,7 @@ public final class FirebaseAI: Sendable { ) self.apiConfig = apiConfig self.location = location - self.aiConfig = aiConfig + self.useLimitedUseAppCheckTokens = useLimitedUseAppCheckTokens } func modelResourceName(modelName: String) -> String { @@ -284,6 +287,6 @@ public final class FirebaseAI: Sendable { let appName: String let location: String? let apiConfig: APIConfig - let aiConfig: FirebaseAI.Config + let useLimitedUseAppCheckTokens: Bool } } diff --git a/FirebaseAI/Sources/GenerativeAIService.swift b/FirebaseAI/Sources/GenerativeAIService.swift index 3c76630822d..158018f5edb 100644 --- a/FirebaseAI/Sources/GenerativeAIService.swift +++ b/FirebaseAI/Sources/GenerativeAIService.swift @@ -30,12 +30,12 @@ struct GenerativeAIService { private let urlSession: URLSession - private let aiConfig: FirebaseAI.Config + private let useLimitedUseAppCheckTokens: Bool - init(firebaseInfo: FirebaseInfo, urlSession: URLSession, aiConfig: FirebaseAI.Config) { + init(firebaseInfo: FirebaseInfo, urlSession: URLSession, useLimitedUseAppCheckTokens: Bool) { self.firebaseInfo = firebaseInfo self.urlSession = urlSession - self.aiConfig = aiConfig + self.useLimitedUseAppCheckTokens = useLimitedUseAppCheckTokens } func loadRequest(request: T) async throws -> T.Response { @@ -212,7 +212,7 @@ struct GenerativeAIService { private func fetchAppCheckToken(appCheck: AppCheckInterop) async throws -> FIRAppCheckTokenResultInterop { - if aiConfig.appCheck.requireLimitedUseTokens { + if useLimitedUseAppCheckTokens { if let token = await appCheck.getLimitedUseToken?() { return token } diff --git a/FirebaseAI/Sources/GenerativeModel.swift b/FirebaseAI/Sources/GenerativeModel.swift index 28dfa4e7eed..50b718607be 100644 --- a/FirebaseAI/Sources/GenerativeModel.swift +++ b/FirebaseAI/Sources/GenerativeModel.swift @@ -76,7 +76,7 @@ public final class GenerativeModel: Sendable { /// only text content is supported. /// - requestOptions: Configuration parameters for sending requests to the backend. /// - urlSession: The `URLSession` to use for requests; defaults to `URLSession.shared`. - /// - aiConfig: Configuration for various behavior shared across models. + /// - useLimitedUseAppCheckTokens: Use App Check's limited-use tokens instead of the standard cached tokens. init(modelName: String, modelResourceName: String, firebaseInfo: FirebaseInfo, @@ -88,14 +88,14 @@ public final class GenerativeModel: Sendable { systemInstruction: ModelContent? = nil, requestOptions: RequestOptions, urlSession: URLSession = GenAIURLSession.default, - aiConfig: FirebaseAI.Config = .config()) { + useLimitedUseAppCheckTokens: Bool) { self.modelName = modelName self.modelResourceName = modelResourceName self.apiConfig = apiConfig generativeAIService = GenerativeAIService( firebaseInfo: firebaseInfo, urlSession: urlSession, - aiConfig: aiConfig + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) self.generationConfig = generationConfig self.safetySettings = safetySettings diff --git a/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift index 254e1fe21ea..536a43acd87 100644 --- a/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift +++ b/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift @@ -53,13 +53,14 @@ public final class ImagenModel { generationConfig: ImagenGenerationConfig?, safetySettings: ImagenSafetySettings?, requestOptions: RequestOptions, - urlSession: URLSession = GenAIURLSession.default, aiConfig: FirebaseAI.Config) { + urlSession: URLSession = GenAIURLSession.default, + useLimitedUseAppCheckTokens: Bool) { self.modelResourceName = modelResourceName self.apiConfig = apiConfig generativeAIService = GenerativeAIService( firebaseInfo: firebaseInfo, urlSession: urlSession, - aiConfig: aiConfig + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) self.generationConfig = generationConfig self.safetySettings = safetySettings diff --git a/FirebaseAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift index 4d7309ca697..0636ddf0fd2 100644 --- a/FirebaseAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -156,13 +156,13 @@ class VertexComponentTests: XCTestCase { app: VertexComponentTests.app, location: location, apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta), - aiConfig: .config() + useLimitedUseAppCheckTokens: false ) let vertex2 = FirebaseAI.createInstance( app: VertexComponentTests.app, location: location, apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1), - aiConfig: .config() + useLimitedUseAppCheckTokens: false ) // Ensure they are different instances. @@ -215,7 +215,7 @@ class VertexComponentTests: XCTestCase { app: app, location: nil, apiConfig: apiConfig, - aiConfig: .config() + useLimitedUseAppCheckTokens: false ) let model = "test-model-name" @@ -234,7 +234,7 @@ class VertexComponentTests: XCTestCase { app: app, location: nil, apiConfig: apiConfig, - aiConfig: .config() + useLimitedUseAppCheckTokens: false ) let model = "test-model-name" let projectID = vertex.firebaseInfo.projectID @@ -270,7 +270,7 @@ class VertexComponentTests: XCTestCase { app: app, location: nil, apiConfig: apiConfig, - aiConfig: .config() + useLimitedUseAppCheckTokens: false ) let modelResourceName = vertex.modelResourceName(modelName: modelName) let expectedSystemInstruction = ModelContent(role: nil, parts: systemInstruction.parts) From 0a0976b1bc9e34dba9262fd052fc3cebba25f9e7 Mon Sep 17 00:00:00 2001 From: Daymon Date: Mon, 18 Aug 2025 12:27:47 -0500 Subject: [PATCH 31/40] Update CHANGELOG.md --- FirebaseAI/CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/FirebaseAI/CHANGELOG.md b/FirebaseAI/CHANGELOG.md index 3bcf708d016..e3c28ae7b31 100644 --- a/FirebaseAI/CHANGELOG.md +++ b/FirebaseAI/CHANGELOG.md @@ -1,11 +1,11 @@ # Unreleased -- [feature] Added a new configuration option to require limited-use App +- [feature] Added a new configuration option to use limited-use App Check tokens for attesting Firebase AI Logic requests. This enhances security against replay attacks. To use this feature, configure it - explicitly via the new `FirebaseAI.Config` struct when initializing - `FirebaseAI`. We recommend migrating to limited-use tokens now, so - your app will be ready to take advantage of replay protection when - it becomes available for Firebase AI Logic. + explicitly via the new `useLimitedUseAppCheckTokens` parameter when + initializing `FirebaseAI`. We recommend migrating to limited-use + tokens now, so your app will be ready to take advantage of replay + protection when it becomes available for Firebase AI Logic. # 12.0.0 - [added] Added support for Grounding with Google Search. (#15014) From cb0d3c8015bf1dae3945ec017012696b43d78889 Mon Sep 17 00:00:00 2001 From: Daymon Date: Mon, 18 Aug 2025 12:28:53 -0500 Subject: [PATCH 32/40] formatting --- FirebaseAI/Sources/FirebaseAI.swift | 6 ++++-- FirebaseAI/Sources/GenerativeModel.swift | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index 61c39e2b762..8f2c43c61ea 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -183,7 +183,8 @@ public final class FirebaseAI: Sendable { ) static func createInstance(app: FirebaseApp?, location: String?, - apiConfig: APIConfig, useLimitedUseAppCheckTokens: Bool) -> FirebaseAI { + apiConfig: APIConfig, + useLimitedUseAppCheckTokens: Bool) -> FirebaseAI { guard let app = app ?? FirebaseApp.app() else { fatalError("No instance of the default Firebase app was found.") } @@ -212,7 +213,8 @@ public final class FirebaseAI: Sendable { return newInstance } - init(app: FirebaseApp, location: String?, apiConfig: APIConfig, useLimitedUseAppCheckTokens: Bool) { + init(app: FirebaseApp, location: String?, apiConfig: APIConfig, + useLimitedUseAppCheckTokens: Bool) { guard let projectID = app.options.projectID else { fatalError("The Firebase app named \"\(app.name)\" has no project ID in its configuration.") } diff --git a/FirebaseAI/Sources/GenerativeModel.swift b/FirebaseAI/Sources/GenerativeModel.swift index 50b718607be..cdbebebcfa2 100644 --- a/FirebaseAI/Sources/GenerativeModel.swift +++ b/FirebaseAI/Sources/GenerativeModel.swift @@ -76,7 +76,8 @@ public final class GenerativeModel: Sendable { /// only text content is supported. /// - requestOptions: Configuration parameters for sending requests to the backend. /// - urlSession: The `URLSession` to use for requests; defaults to `URLSession.shared`. - /// - useLimitedUseAppCheckTokens: Use App Check's limited-use tokens instead of the standard cached tokens. + /// - useLimitedUseAppCheckTokens: Use App Check's limited-use tokens instead of the standard + /// cached tokens. init(modelName: String, modelResourceName: String, firebaseInfo: FirebaseInfo, From 17113f3c3ba160ec6f5f25ebcc0999595555aacb Mon Sep 17 00:00:00 2001 From: Daymon Date: Mon, 18 Aug 2025 12:30:46 -0500 Subject: [PATCH 33/40] Remove trailing whitespace --- FirebaseAI/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FirebaseAI/CHANGELOG.md b/FirebaseAI/CHANGELOG.md index e3c28ae7b31..258b748d506 100644 --- a/FirebaseAI/CHANGELOG.md +++ b/FirebaseAI/CHANGELOG.md @@ -2,9 +2,9 @@ - [feature] Added a new configuration option to use limited-use App Check tokens for attesting Firebase AI Logic requests. This enhances security against replay attacks. To use this feature, configure it - explicitly via the new `useLimitedUseAppCheckTokens` parameter when - initializing `FirebaseAI`. We recommend migrating to limited-use - tokens now, so your app will be ready to take advantage of replay + explicitly via the new `useLimitedUseAppCheckTokens` parameter when + initializing `FirebaseAI`. We recommend migrating to limited-use + tokens now, so your app will be ready to take advantage of replay protection when it becomes available for Firebase AI Logic. # 12.0.0 From 4774502cf8b5e5a9612fd951e4512e99d506ff22 Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 19 Aug 2025 13:00:39 -0500 Subject: [PATCH 34/40] Fix left overs from config struct --- FirebaseAI/Sources/GenerativeModel.swift | 2 +- FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift | 2 +- FirebaseAI/Tests/Unit/VertexComponentTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FirebaseAI/Sources/GenerativeModel.swift b/FirebaseAI/Sources/GenerativeModel.swift index cdbebebcfa2..2807022de59 100644 --- a/FirebaseAI/Sources/GenerativeModel.swift +++ b/FirebaseAI/Sources/GenerativeModel.swift @@ -89,7 +89,7 @@ public final class GenerativeModel: Sendable { systemInstruction: ModelContent? = nil, requestOptions: RequestOptions, urlSession: URLSession = GenAIURLSession.default, - useLimitedUseAppCheckTokens: Bool) { + useLimitedUseAppCheckTokens: Bool = false) { self.modelName = modelName self.modelResourceName = modelResourceName self.apiConfig = apiConfig diff --git a/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift index 68d62742db6..542f842362c 100644 --- a/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift +++ b/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift @@ -54,7 +54,7 @@ public final class ImagenModel { safetySettings: ImagenSafetySettings?, requestOptions: RequestOptions, urlSession: URLSession = GenAIURLSession.default, - useLimitedUseAppCheckTokens: Bool) { + useLimitedUseAppCheckTokens: Bool = false) { self.modelResourceName = modelResourceName self.apiConfig = apiConfig generativeAIService = GenerativeAIService( diff --git a/FirebaseAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift index 0636ddf0fd2..702c6e50871 100644 --- a/FirebaseAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -184,7 +184,7 @@ class VertexComponentTests: XCTestCase { app: app1, location: "transitory location", apiConfig: FirebaseAI.defaultVertexAIAPIConfig, - aiConfig: .config() + useLimitedUseAppCheckTokens: false ) weakVertex = vertex XCTAssertNotNil(weakVertex) From e80fadec8b1a0a82a24baa42b916b199badeaafb Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 19 Aug 2025 14:02:24 -0500 Subject: [PATCH 35/40] Make app check token result sendable --- FirebaseAI/Sources/GenerativeAIService.swift | 21 ++++++++++++++++++- .../Unit/Fakes/AppCheckInteropFake.swift | 6 +++--- .../FIRAppCheckTokenResultInterop.h | 1 + .../Sources/Core/FIRAppCheckTokenResult.h | 1 + 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/FirebaseAI/Sources/GenerativeAIService.swift b/FirebaseAI/Sources/GenerativeAIService.swift index 158018f5edb..25a1377ec7c 100644 --- a/FirebaseAI/Sources/GenerativeAIService.swift +++ b/FirebaseAI/Sources/GenerativeAIService.swift @@ -213,7 +213,7 @@ struct GenerativeAIService { private func fetchAppCheckToken(appCheck: AppCheckInterop) async throws -> FIRAppCheckTokenResultInterop { if useLimitedUseAppCheckTokens { - if let token = await appCheck.getLimitedUseToken?() { + if let token = await getLimitedUseAppCheckToken(appCheck: appCheck) { return token } @@ -234,6 +234,25 @@ struct GenerativeAIService { return await appCheck.getToken(forcingRefresh: false) } + private func getLimitedUseAppCheckToken(appCheck: AppCheckInterop) async -> FIRAppCheckTokenResultInterop? { + // At the moment, `await` doesn’t get along with Objective-C’s optional protocol methods. + await withCheckedContinuation { (continuation: CheckedContinuation) in + guard + useLimitedUseAppCheckTokens, + // `getLimitedUseToken(completion:)` is an optional protocol method. Optional binding + // is performed to make sure `continuation` is called even if the method’s not implemented. + let limitedUseTokenClosure = appCheck.getLimitedUseToken + else { + return continuation.resume(returning: nil) + } + + limitedUseTokenClosure { tokenResult in + // The placeholder token should be used in the case of App Check error. + continuation.resume(returning: tokenResult) + } + } + } + private func httpResponse(urlResponse: URLResponse) throws -> HTTPURLResponse { // The following condition should always be true: "Whenever you make HTTP URL load requests, any // response objects you get back from the URLSession, NSURLConnection, or NSURLDownload class diff --git a/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift b/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift index 62fc753ae68..7048d89d793 100644 --- a/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift +++ b/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift @@ -52,9 +52,9 @@ class AppCheckInteropFake: NSObject, AppCheckInterop { fatalError("\(#function) not implemented.") } - private class AppCheckTokenResultInteropFake: NSObject, FIRAppCheckTokenResultInterop { - var token: String - var error: Error? + private class AppCheckTokenResultInteropFake: NSObject, FIRAppCheckTokenResultInterop, @unchecked Sendable { + let token: String + let error: Error? init(token: String, error: Error?) { self.token = token diff --git a/FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckTokenResultInterop.h b/FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckTokenResultInterop.h index cb86a1bffe8..f87820b9790 100644 --- a/FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckTokenResultInterop.h +++ b/FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckTokenResultInterop.h @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN +NS_SWIFT_SENDABLE @protocol FIRAppCheckTokenResultInterop /// App Check token in the case of success or a dummy token in the case of a failure. diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h b/FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h index 37b57b2adef..9cf6ddadec1 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN +NS_SWIFT_SENDABLE @interface FIRAppCheckTokenResult : NSObject - (instancetype)initWithToken:(NSString *)token error:(nullable NSError *)error; From cd7312a1079aee0767fca1d0e99bd90cbad57edd Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 19 Aug 2025 14:10:16 -0500 Subject: [PATCH 36/40] formatting --- FirebaseAI/Sources/GenerativeAIService.swift | 8 ++++++-- FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/FirebaseAI/Sources/GenerativeAIService.swift b/FirebaseAI/Sources/GenerativeAIService.swift index 25a1377ec7c..5b676162c94 100644 --- a/FirebaseAI/Sources/GenerativeAIService.swift +++ b/FirebaseAI/Sources/GenerativeAIService.swift @@ -234,9 +234,13 @@ struct GenerativeAIService { return await appCheck.getToken(forcingRefresh: false) } - private func getLimitedUseAppCheckToken(appCheck: AppCheckInterop) async -> FIRAppCheckTokenResultInterop? { + private func getLimitedUseAppCheckToken(appCheck: AppCheckInterop) async + -> FIRAppCheckTokenResultInterop? { // At the moment, `await` doesn’t get along with Objective-C’s optional protocol methods. - await withCheckedContinuation { (continuation: CheckedContinuation) in + await withCheckedContinuation { (continuation: CheckedContinuation< + FIRAppCheckTokenResultInterop?, + Never + >) in guard useLimitedUseAppCheckTokens, // `getLimitedUseToken(completion:)` is an optional protocol method. Optional binding diff --git a/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift b/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift index 7048d89d793..1bbf2906ba0 100644 --- a/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift +++ b/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift @@ -52,7 +52,8 @@ class AppCheckInteropFake: NSObject, AppCheckInterop { fatalError("\(#function) not implemented.") } - private class AppCheckTokenResultInteropFake: NSObject, FIRAppCheckTokenResultInterop, @unchecked Sendable { + private class AppCheckTokenResultInteropFake: NSObject, FIRAppCheckTokenResultInterop, + @unchecked Sendable { let token: String let error: Error? From b5940dc44f3329ff160f18873b49d5cb2b5b5846 Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 19 Aug 2025 14:31:04 -0500 Subject: [PATCH 37/40] Fix missing param --- .../Tests/TestApp/Tests/Utilities/InstanceConfig.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift index 21554d28250..2c6dceb24fd 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift @@ -121,7 +121,8 @@ extension FirebaseAI { return FirebaseAI.createInstance( app: instanceConfig.app, location: location, - apiConfig: instanceConfig.apiConfig + apiConfig: instanceConfig.apiConfig, + useLimitedUseAppCheckTokens: false ) case .googleAI: assert( @@ -131,7 +132,8 @@ extension FirebaseAI { return FirebaseAI.createInstance( app: instanceConfig.app, location: nil, - apiConfig: instanceConfig.apiConfig + apiConfig: instanceConfig.apiConfig, + useLimitedUseAppCheckTokens: false ) } } From 4a8ea120ea37e7f03eb0b6611174701c327aab3d Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 19 Aug 2025 14:31:11 -0500 Subject: [PATCH 38/40] Add limited use tests --- .../Unit/Fakes/AppCheckInteropFake.swift | 4 +++ .../Unit/GenerativeModelVertexAITests.swift | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift b/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift index 1bbf2906ba0..81b6ac1bb7c 100644 --- a/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift +++ b/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift @@ -40,6 +40,10 @@ class AppCheckInteropFake: NSObject, AppCheckInterop { return AppCheckTokenResultInteropFake(token: token, error: error) } + func getLimitedUseToken() async -> any FIRAppCheckTokenResultInterop { + return AppCheckTokenResultInteropFake(token: "limited_use_\(token)", error: error) + } + func tokenDidChangeNotificationName() -> String { fatalError("\(#function) not implemented.") } diff --git a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift index 7c23726f152..80a383eb461 100644 --- a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift @@ -501,6 +501,31 @@ final class GenerativeModelVertexAITests: XCTestCase { _ = try await model.generateContent(testPrompt) } + func testGenerateContent_appCheck_validToken_limitedUse() async throws { + let appCheckToken = "test-valid-token" + model = GenerativeModel( + modelName: testModelName, + modelResourceName: testModelResourceName, + firebaseInfo: GenerativeModelTestUtil.testFirebaseInfo( + appCheck: AppCheckInteropFake(token: appCheckToken) + ), + apiConfig: apiConfig, + tools: nil, + requestOptions: RequestOptions(), + urlSession: urlSession, + useLimitedUseAppCheckTokens: true + ) + MockURLProtocol + .requestHandler = try GenerativeModelTestUtil.httpRequestHandler( + forResource: "unary-success-basic-reply-short", + withExtension: "json", + subdirectory: vertexSubdirectory, + appCheckToken: "limited_use_\(appCheckToken)" + ) + + _ = try await model.generateContent(testPrompt) + } + func testGenerateContent_dataCollectionOff() async throws { let appCheckToken = "test-valid-token" model = GenerativeModel( From 1dc7d7f49cf5078772d67317ae0da62d50bed9e5 Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 19 Aug 2025 15:11:12 -0500 Subject: [PATCH 39/40] Update FirebaseAI.swift --- FirebaseAI/Sources/FirebaseAI.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index 8f2c43c61ea..d4c2e9d545d 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -32,9 +32,6 @@ public final class FirebaseAI: Sendable { /// ``FirebaseApp``. /// - backend: The backend API for the Firebase AI SDK; if not specified, uses the default /// ``Backend/googleAI()`` (Gemini Developer API). - /// - config: Configuration options for the Firebase AI SDK that propogate across all models - /// created. Uses default options when not specified, see the ``FirebaseAI.Config`` - /// documentation for more information. /// - useLimitedUseAppCheckTokens: When sending tokens to the backend, this option enables /// the usage of App Check's limited-use tokens instead of the standard cached tokens. /// From 19b452240a1adc0736979dfb8e28a13aad958178 Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 19 Aug 2025 16:06:40 -0500 Subject: [PATCH 40/40] Update CHANGELOG.md --- FirebaseAI/CHANGELOG.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/FirebaseAI/CHANGELOG.md b/FirebaseAI/CHANGELOG.md index 848c10098d6..b5f3199a797 100644 --- a/FirebaseAI/CHANGELOG.md +++ b/FirebaseAI/CHANGELOG.md @@ -1,15 +1,13 @@ -# Unreleased +# 12.2.0 +- [feature] Added support for returning thought summaries, which are synthesized + versions of a model's internal reasoning process. (#15096) - [feature] Added a new configuration option to use limited-use App Check tokens for attesting Firebase AI Logic requests. This enhances security against replay attacks. To use this feature, configure it explicitly via the new `useLimitedUseAppCheckTokens` parameter when initializing `FirebaseAI`. We recommend migrating to limited-use tokens now, so your app will be ready to take advantage of replay - protection when it becomes available for Firebase AI Logic. - -# 12.2.0 -- [feature] Added support for returning thought summaries, which are synthesized - versions of a model's internal reasoning process. (#15096) + protection when it becomes available for Firebase AI Logic. (#15099) # 12.0.0 - [feature] Added support for Grounding with Google Search. (#15014)