From 0eeb7a2ec94b9ac1c4dcb9d21200c79f63a50c71 Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 9 Jul 2025 12:04:35 -0500 Subject: [PATCH 01/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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 }