Sfoglia il codice sorgente

Merge branch 'main' into release-11.0-merge

# Conflicts:
#	Firebase.podspec
#	FirebaseABTesting.podspec
#	FirebaseAnalytics.podspec
#	FirebaseAnalyticsOnDeviceConversion.podspec
#	FirebaseAppCheck.podspec
#	FirebaseAppCheckInterop.podspec
#	FirebaseAppDistribution.podspec
#	FirebaseAuth.podspec
#	FirebaseAuthInterop.podspec
#	FirebaseCore.podspec
#	FirebaseCoreExtension.podspec
#	FirebaseCoreInternal.podspec
#	FirebaseCrashlytics.podspec
#	FirebaseDatabase.podspec
#	FirebaseDynamicLinks.podspec
#	FirebaseFirestore.podspec
#	FirebaseFirestoreInternal.podspec
#	FirebaseFunctions.podspec
#	FirebaseInAppMessaging.podspec
#	FirebaseInstallations.podspec
#	FirebaseMLModelDownloader.podspec
#	FirebaseMessaging.podspec
#	FirebaseMessaging/CHANGELOG.md
#	FirebaseMessagingInterop.podspec
#	FirebasePerformance.podspec
#	FirebaseRemoteConfig.podspec
#	FirebaseRemoteConfigInterop.podspec
#	FirebaseSessions.podspec
#	FirebaseSharedSwift.podspec
#	FirebaseStorage.podspec
#	FirebaseVertexAI/Sources/VertexAIComponent.swift
#	GoogleAppMeasurement.podspec
#	GoogleAppMeasurementOnDeviceConversion.podspec
#	Package.swift
#	ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift
Andrew Heard 1 anno fa
parent
commit
61384c1223
100 ha cambiato i file con 777 aggiunte e 334 eliminazioni
  1. 7 1
      .github/actions/notices_generation/app.rb
  2. 34 4
      .github/workflows/vertexai.yml
  3. 6 0
      Crashlytics/CHANGELOG.md
  4. 0 2
      Crashlytics/Crashlytics/Controllers/FIRCLSMetricKitManager.m
  5. 2 1
      Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.h
  6. 16 3
      Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.m
  7. 6 2
      Crashlytics/Crashlytics/FIRCrashlytics.m
  8. 0 2
      Crashlytics/Crashlytics/Handlers/FIRCLSMachException.c
  9. 2 14
      Crashlytics/Crashlytics/Handlers/FIRCLSSignal.c
  10. 1 2
      Crashlytics/Crashlytics/Handlers/FIRCLSSignal.h
  11. 13 5
      Crashlytics/UnitTests/FIRCLSRolloutsPersistenceManagerTests.m
  12. 1 1
      FirebaseAnalytics.podspec
  13. 1 1
      FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckInterop.h
  14. 4 4
      FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckProtocol.h
  15. 32 0
      FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckTokenProtocol.h
  16. 1 0
      FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FirebaseAppCheckInterop.h
  17. 30 0
      FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h
  18. 3 1
      FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h
  19. 53 0
      FirebaseAppCheck/Tests/Interop/ObjC/FIRAppCheckInteropAPITests.m
  20. 51 0
      FirebaseAppCheck/Tests/Interop/ObjC/FIRAppCheckProtocolAPITests.m
  21. 64 0
      FirebaseAppCheck/Tests/Interop/Swift/AppCheckInteropAPITests.swift
  22. 58 0
      FirebaseAppCheck/Tests/Interop/Swift/AppCheckProtocolAPITests.swift
  23. 32 4
      FirebaseAppCheckInterop.podspec
  24. 0 1
      FirebaseCore/Extension/FIRLogger.h
  25. 5 14
      FirebaseMLModelDownloader/Sources/DeviceLogger.swift
  26. 0 1
      FirebaseMLModelDownloader/Tests/Unit/ModelDownloaderUnitTests.swift
  27. 4 0
      FirebaseMessaging/CHANGELOG.md
  28. 1 1
      FirebaseMessaging/Sources/Token/FIRMessagingBackupExcludedPlist.h
  29. 1 1
      FirebaseMessaging/Sources/Token/FIRMessagingBackupExcludedPlist.m
  30. 2 2
      FirebaseMessaging/Sources/Token/FIRMessagingCheckinStore.m
  31. 2 2
      FirebaseMessaging/Tests/UnitTests/FIRMessagingBackupExcludedPlistTest.m
  32. 2 2
      FirebaseMessaging/Tests/UnitTests/FIRMessagingCheckinStoreTest.m
  33. 2 2
      FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenStoreTest.m
  34. 2 1
      FirebasePerformance/CHANGELOG.md
  35. 4 0
      FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h
  36. 49 3
      FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.m
  37. 3 33
      FirebasePerformance/Sources/FPRNanoPbUtils.m
  38. 8 0
      FirebaseVertexAI/CHANGELOG.md
  39. 1 1
      FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift
  40. 1 1
      FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift
  41. 1 1
      FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift
  42. 1 1
      FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift
  43. 1 1
      FirebaseVertexAI/Sources/Chat.swift
  44. 0 1
      FirebaseVertexAI/Sources/Constants.swift
  45. 5 5
      FirebaseVertexAI/Sources/CountTokensRequest.swift
  46. 1 1
      FirebaseVertexAI/Sources/GenerateContentError.swift
  47. 3 3
      FirebaseVertexAI/Sources/GenerateContentRequest.swift
  48. 14 14
      FirebaseVertexAI/Sources/GenerateContentResponse.swift
  49. 2 2
      FirebaseVertexAI/Sources/GenerationConfig.swift
  50. 2 2
      FirebaseVertexAI/Sources/GenerativeAIRequest.swift
  51. 1 1
      FirebaseVertexAI/Sources/GenerativeAIService.swift
  52. 2 2
      FirebaseVertexAI/Sources/GenerativeModel.swift
  53. 1 1
      FirebaseVertexAI/Sources/Logging.swift
  54. 3 3
      FirebaseVertexAI/Sources/ModelContent.swift
  55. 4 4
      FirebaseVertexAI/Sources/PartsRepresentable+Image.swift
  56. 6 6
      FirebaseVertexAI/Sources/PartsRepresentable.swift
  57. 9 9
      FirebaseVertexAI/Sources/Safety.swift
  58. 3 3
      FirebaseVertexAI/Sources/VertexAI.swift
  59. 74 0
      FirebaseVertexAI/Tests/Integration/IntegrationTests.swift
  60. 0 0
      FirebaseVertexAI/Tests/Integration/Resources/placeholder.txt
  61. 2 2
      FirebaseVertexAI/Tests/Unit/ChatTests.swift
  62. 1 1
      FirebaseVertexAI/Tests/Unit/Fakes/AuthInteropFake.swift
  63. 0 2
      FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-failure-finish-reason-safety.txt
  64. 0 6
      FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-failure-recitation-no-content.txt
  65. 0 1
      FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-success-basic-reply-short.txt
  66. 0 7
      FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-success-citations.txt
  67. 0 53
      FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-finish-reason-safety.json
  68. 0 50
      FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-success-basic-reply-long.json
  69. 1 1
      FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift
  70. 14 18
      FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift
  71. 1 1
      FirebaseVertexAI/Tests/Unit/MockURLProtocol.swift
  72. 1 1
      FirebaseVertexAI/Tests/Unit/ModelContentTests.swift
  73. 5 4
      FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift
  74. 1 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-empty-content.txt
  75. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-error-mid-stream.txt
  76. 2 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-finish-reason-safety.txt
  77. 1 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-invalid-json.txt
  78. 1 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-malformed-content.txt
  79. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-prompt-blocked-safety.txt
  80. 6 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-recitation-no-content.txt
  81. 1 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-unknown-finish-enum.txt
  82. 12 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-success-basic-reply-long.txt
  83. 16 15
      FirebaseVertexAI/Tests/Unit/Responses/streaming-success-basic-reply-parts.txt
  84. 2 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-success-basic-reply-short.txt
  85. 12 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-success-citations.txt
  86. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/streaming-success-unknown-safety-enum.txt
  87. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-api-key.json
  88. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-empty-content.json
  89. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-finish-reason-safety-no-content.json
  90. 54 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-finish-reason-safety.json
  91. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-firebaseml-api-not-enabled.json
  92. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-image-rejected.json
  93. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-invalid-response.json
  94. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-malformed-content.json
  95. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-model-not-found.json
  96. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-prompt-blocked-safety.json
  97. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-unknown-enum-finish-reason.json
  98. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-unknown-enum-prompt-blocked.json
  99. 0 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-failure-unknown-model.json
  100. 7 0
      FirebaseVertexAI/Tests/Unit/Responses/unary-success-basic-reply-long.json

+ 7 - 1
.github/actions/notices_generation/app.rb

@@ -81,7 +81,13 @@ def create_podfile(path: , sources: , target: , pods: [], min_ios_version: , sea
       # pod search Firebase | grep "pod.*" -m 1
       # will generate
       # pod 'Firebase', '~> 9.0.0'
-      output += `pod search "#{pod}" | grep "pod.*" -m 1`
+      # TODO: Re-enable below line post 10.28.0
+      # output += `pod search "#{pod}" | grep "pod.*" -m 1`
+      if pod.include?("FirebaseAppDistribution") || pod.include?("FirebaseInAppMessaging") || pod.include?("FirebaseMLModelDownloader")
+        output += "pod \'#{pod}\', \'~> 10.28.0-beta\'\n"
+      else
+        output += "pod \'#{pod}\', \'~> 10.28.0\'\n"
+      end
     else
       output += "pod \'#{pod}\'\n"
     end

+ 34 - 4
.github/workflows/vertexai.yml

@@ -15,13 +15,13 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
-  spm:
+  spm-unit:
     strategy:
       matrix:
-        target: [iOS, macOS, catalyst]
-        os: [macos-13]
+        target: [iOS, macOS, catalyst, tvOS, visionOS]
+        os: [macos-14]
         include:
-          - os: macos-13
+          - os: macos-14
             xcode: Xcode_15.2
     runs-on: ${{ matrix.os }}
     env:
@@ -40,6 +40,36 @@ jobs:
         retry_wait_seconds: 120
         command: scripts/build.sh FirebaseVertexAIUnit ${{ matrix.target }} spm
 
+  spm-integration:
+    strategy:
+      matrix:
+        target: [iOS]
+        os: [macos-14]
+        include:
+          - os: macos-14
+            xcode: Xcode_15.2
+    runs-on: ${{ matrix.os }}
+    env:
+      TEST_RUNNER_VertexAIRunIntegrationTests: 1
+      FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
+      plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }}
+    steps:
+    - uses: actions/checkout@v4
+    - name: Install Secret GoogleService-Info.plist
+      run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/vertexai-integration.plist.gpg \
+        FirebaseVertexAI/Tests/Integration/Resources/GoogleService-Info.plist "$plist_secret"
+    - name: Xcode
+      run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer
+    - name: Initialize xcodebuild
+      run: scripts/setup_spm_tests.sh
+    - uses: nick-fields/retry@v3
+      with:
+        timeout_minutes: 120
+        max_attempts: 3
+        retry_on: error
+        retry_wait_seconds: 120
+        command: scripts/build.sh FirebaseVertexAIIntegration ${{ matrix.target }} spm
+
   sample:
     strategy:
       matrix:

+ 6 - 0
Crashlytics/CHANGELOG.md

@@ -1,3 +1,9 @@
+# 10.28.1
+- [changed] Reverted "Add SIGTERM support (#12881)" (#13117)
+
+# 10.28.0
+- [fixed] Created a new queue for rollouts persistence writes and made sure rollouts logging queue is not nil while dispatching (#12913).
+
 # 10.27.0
 - [added] Added support for catching the SIGTERM signal (#12881).
 - [fixed] Fixed a hang when persisting Remote Config Rollouts to disk (#12913).

+ 0 - 2
Crashlytics/Crashlytics/Controllers/FIRCLSMetricKitManager.m

@@ -438,8 +438,6 @@
       return @"SIGSYS";
     case SIGTRAP:
       return @"SIGTRAP";
-    case SIGTERM:
-      return @"SIGTERM";
     default:
       return @"UNKNOWN";
   }

+ 2 - 1
Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.h

@@ -25,7 +25,8 @@
 
 @interface FIRCLSRolloutsPersistenceManager : NSObject <FIRCLSPersistenceLog>
 
-- (instancetype _Nullable)initWithFileManager:(FIRCLSFileManager *_Nonnull)fileManager;
+- (instancetype _Nullable)initWithFileManager:(FIRCLSFileManager *_Nonnull)fileManager
+                                     andQueue:(dispatch_queue_t _Nonnull)queue;
 - (instancetype _Nonnull)init NS_UNAVAILABLE;
 + (instancetype _Nonnull)new NS_UNAVAILABLE;
 

+ 16 - 3
Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.m

@@ -13,7 +13,6 @@
 // limitations under the License.
 
 #import <Foundation/Foundation.h>
-#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
 #include "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
 #import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
 #import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
@@ -32,15 +31,23 @@
 
 @interface FIRCLSRolloutsPersistenceManager : NSObject <FIRCLSPersistenceLog>
 @property(nonatomic, readonly) FIRCLSFileManager *fileManager;
+@property(nonnull, nonatomic, readonly) dispatch_queue_t rolloutsLoggingQueue;
 @end
 
 @implementation FIRCLSRolloutsPersistenceManager
-- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager {
+- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager
+                           andQueue:(dispatch_queue_t)queue {
   self = [super init];
   if (!self) {
     return nil;
   }
   _fileManager = fileManager;
+
+  if (!queue) {
+    FIRCLSDebugLog(@"Failed to intialize FIRCLSRolloutsPersistenceManager, logging queue is nil");
+    return nil;
+  }
+  _rolloutsLoggingQueue = queue;
   return self;
 }
 
@@ -52,12 +59,18 @@
     if (![_fileManager createFileAtPath:rolloutsPath contents:nil attributes:nil]) {
       FIRCLSDebugLog(@"Could not create rollouts.clsrecord file. Error was code: %d - message: %s",
                      errno, strerror(errno));
+      return;
     }
   }
 
   NSFileHandle *rolloutsFile = [NSFileHandle fileHandleForUpdatingAtPath:rolloutsPath];
 
-  dispatch_async(FIRCLSGetLoggingQueue(), ^{
+  if (!_rolloutsLoggingQueue) {
+    FIRCLSDebugLog(@"Rollouts logging queue is dealloccated");
+    return;
+  }
+
+  dispatch_async(_rolloutsLoggingQueue, ^{
     @try {
       [rolloutsFile seekToEndOfFile];
       NSMutableData *rolloutsWithNewLineData = [rollouts mutableCopy];

+ 6 - 2
Crashlytics/Crashlytics/FIRCrashlytics.m

@@ -211,7 +211,11 @@ NSString *const FIRCLSGoogleTransportMappingID = @"1206";
       FIRCLSDebugLog(@"Registering RemoteConfig SDK subscription for rollouts data");
 
       FIRCLSRolloutsPersistenceManager *persistenceManager =
-          [[FIRCLSRolloutsPersistenceManager alloc] initWithFileManager:_fileManager];
+          [[FIRCLSRolloutsPersistenceManager alloc]
+              initWithFileManager:_fileManager
+                         andQueue:dispatch_queue_create(
+                                      "com.google.firebase.FIRCLSRolloutsPersistence",
+                                      DISPATCH_QUEUE_SERIAL)];
       _remoteConfigManager =
           [[FIRCLSRemoteConfigManager alloc] initWithRemoteConfig:remoteConfig
                                               persistenceDelegate:persistenceManager];
@@ -360,7 +364,7 @@ NSString *const FIRCLSGoogleTransportMappingID = @"1206";
 }
 
 #pragma mark - API: Development Platform
-// These two methods are depercated by our own API, so
+// These two methods are deprecated by our own API, so
 // its ok to implement them
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-implementations"

+ 0 - 2
Crashlytics/Crashlytics/Handlers/FIRCLSMachException.c

@@ -150,8 +150,6 @@ exception_mask_t FIRCLSMachExceptionMaskForSignal(int signal) {
       return EXC_MASK_CRASH;
     case SIGFPE:
       return EXC_MASK_ARITHMETIC;
-    case SIGTERM:
-      return EXC_MASK_CRASH;
   }
 
   return 0;

+ 2 - 14
Crashlytics/Crashlytics/Handlers/FIRCLSSignal.c

@@ -21,17 +21,8 @@
 #include <stdlib.h>
 
 #if CLS_SIGNAL_SUPPORTED
-static const int FIRCLSFatalSignals[FIRCLSSignalCount] = {
-    SIGABRT, SIGBUS, SIGFPE, SIGILL,
-    SIGSEGV, SIGSYS, SIGTRAP,
-    // SIGTERM can be caught and is usually sent by iOS and variants
-    // when Apple wants to try and gracefully shutdown the app
-    // before sending a SIGKILL (which can't be caught).
-    // Some areas I've seen this happen are:
-    // - When the OS updates an app.
-    // - In some circumstances for Watchdog Events.
-    // - Resource overuse (CPU, Disk, ...).
-    SIGTERM};
+static const int FIRCLSFatalSignals[FIRCLSSignalCount] = {SIGABRT, SIGBUS, SIGFPE, SIGILL,
+                                                          SIGSEGV, SIGSYS, SIGTRAP};
 
 #if CLS_USE_SIGALTSTACK
 static void FIRCLSSignalInstallAltStack(FIRCLSSignalReadContext *roContext);
@@ -246,9 +237,6 @@ void FIRCLSSignalNameLookup(int number, int code, const char **name, const char
     case SIGTRAP:
       *name = "SIGTRAP";
       break;
-    case SIGTERM:
-      *name = "SIGTERM";
-      break;
     default:
       *name = "UNKNOWN";
       break;

+ 1 - 2
Crashlytics/Crashlytics/Handlers/FIRCLSSignal.h

@@ -30,8 +30,7 @@
 #endif
 
 #if CLS_SIGNAL_SUPPORTED
-// keep in sync with the list in _FIRCLSFatalSignals_.
-#define FIRCLSSignalCount (8)
+#define FIRCLSSignalCount (7)
 
 typedef struct {
   const char* path;

+ 13 - 5
Crashlytics/UnitTests/FIRCLSRolloutsPersistenceManagerTests.m

@@ -30,6 +30,7 @@ NSString *reportId = @"1234567";
 
 @interface FIRCLSRolloutsPersistenceManagerTests : XCTestCase
 @property(nonatomic, strong) FIRCLSTempMockFileManager *fileManager;
+@property(nonatomic, strong) dispatch_queue_t loggingQueue;
 @property(nonatomic, strong) FIRCLSRolloutsPersistenceManager *rolloutsPersistenceManager;
 @end
 
@@ -41,8 +42,11 @@ NSString *reportId = @"1234567";
   [self.fileManager createReportDirectories];
   [self.fileManager setupNewPathForExecutionIdentifier:reportId];
 
+  self.loggingQueue =
+      dispatch_queue_create("com.google.firebase.FIRCLSRolloutsPersistence", DISPATCH_QUEUE_SERIAL);
   self.rolloutsPersistenceManager =
-      [[FIRCLSRolloutsPersistenceManager alloc] initWithFileManager:self.fileManager];
+      [[FIRCLSRolloutsPersistenceManager alloc] initWithFileManager:self.fileManager
+                                                           andQueue:self.loggingQueue];
 }
 
 - (void)tearDown {
@@ -65,18 +69,22 @@ NSString *reportId = @"1234567";
   NSString *rolloutsFilePath =
       [[[self.fileManager activePath] stringByAppendingPathComponent:reportId]
           stringByAppendingPathComponent:FIRCLSReportRolloutsFile];
-
   [self.rolloutsPersistenceManager updateRolloutsStateToPersistenceWithRollouts:data
                                                                        reportID:reportId];
-
+  XCTAssertNotNil(self.loggingQueue);
   // Wait for the logging queue to finish.
-  dispatch_async(FIRCLSGetLoggingQueue(), ^{
+  dispatch_async(self.loggingQueue, ^{
     [expectation fulfill];
   });
 
   [self waitForExpectations:@[ expectation ] timeout:3];
 
   XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:rolloutsFilePath]);
+
+  NSFileHandle *rolloutsFile = [NSFileHandle fileHandleForUpdatingAtPath:rolloutsFilePath];
+  NSData *fileData = [rolloutsFile readDataToEndOfFile];
+  NSString *fileString = [[NSString alloc] initWithData:fileData encoding:NSUTF8StringEncoding];
+  XCTAssertTrue([fileString isEqualToString:[encodedStateString stringByAppendingString:@"\n"]]);
 }
 
 - (void)testUpdateRolloutsStateToPersistenceEnsureNoHang {
@@ -93,7 +101,7 @@ NSString *reportId = @"1234567";
 
   // Clog up the queue with a long running operation. This sleep time
   // must be longer than the expectation timeout.
-  dispatch_async(FIRCLSGetLoggingQueue(), ^{
+  dispatch_async(self.loggingQueue, ^{
     sleep(10);
   });
 

+ 1 - 1
FirebaseAnalytics.podspec

@@ -13,7 +13,7 @@ Pod::Spec.new do |s|
     s.authors          = 'Google, Inc.'
 
     s.source           = {
-        :http => 'https://dl.google.com/firebase/ios/analytics/069659a8d012c35e/FirebaseAnalytics-10.27.0.tar.gz'
+        :http => 'https://dl.google.com/firebase/ios/analytics/83850700831975be/FirebaseAnalytics-10.28.0.tar.gz'
     }
 
     s.cocoapods_version = '>= 1.12.0'

+ 1 - 1
FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckInterop.h

@@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN
 NS_SWIFT_NAME(AppCheckTokenHandlerInterop)
 typedef void (^FIRAppCheckTokenHandlerInterop)(id<FIRAppCheckTokenResultInterop> tokenResult);
 
-NS_SWIFT_NAME(AppCheckInterop) @protocol FIRAppCheckInterop
+NS_SWIFT_NAME(AppCheckInterop) @protocol FIRAppCheckInterop<NSObject>
 
 /// Retrieve a cached or generate a new FAA Token. If forcingRefresh == YES always generates a new
 /// token and updates the cache.

+ 4 - 4
FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckProtocol.h

@@ -16,7 +16,7 @@
 
 #import <Foundation/Foundation.h>
 
-@class FIRAppCheckToken;
+@protocol FIRAppCheckTokenProtocol;
 
 NS_ASSUME_NONNULL_BEGIN
 
@@ -38,8 +38,8 @@ NS_SWIFT_NAME(AppCheckProtocol)
 /// @param handler The completion handler. Includes the app check token if the request succeeds,
 /// or an error if the request fails.
 - (void)tokenForcingRefresh:(BOOL)forcingRefresh
-                 completion:
-                     (void (^)(FIRAppCheckToken *_Nullable token, NSError *_Nullable error))handler
+                 completion:(void (^)(id<FIRAppCheckTokenProtocol> _Nullable token,
+                                      NSError *_Nullable error))handler
     NS_SWIFT_NAME(token(forcingRefresh:completion:));
 
 /// Requests a limited-use Firebase App Check token. This method should be used only if you need to
@@ -50,7 +50,7 @@ NS_SWIFT_NAME(AppCheckProtocol)
 /// Protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection).
 /// This method does not affect the token generation behavior of the
 /// ``tokenForcingRefresh()`` method.
-- (void)limitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token,
+- (void)limitedUseTokenWithCompletion:(void (^)(id<FIRAppCheckTokenProtocol> _Nullable token,
                                                 NSError *_Nullable error))handler;
 
 @end

+ 32 - 0
FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckTokenProtocol.h

@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+NS_SWIFT_NAME(AppCheckTokenProtocol)
+@protocol FIRAppCheckTokenProtocol <NSObject>
+
+/// A Firebase App Check token.
+@property(nonatomic, readonly) NSString *token;
+
+/// The App Check token's expiration date in the device's local time.
+@property(nonatomic, readonly) NSDate *expirationDate;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 1 - 0
FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FirebaseAppCheckInterop.h

@@ -16,4 +16,5 @@
 
 #import "FIRAppCheckInterop.h"
 #import "FIRAppCheckProtocol.h"
+#import "FIRAppCheckTokenProtocol.h"
 #import "FIRAppCheckTokenResultInterop.h"

+ 30 - 0
FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h

@@ -74,6 +74,36 @@ NS_SWIFT_NAME(AppCheck)
 /// set explicitly, the value will be persisted and used as a default value on next app launches.
 @property(nonatomic, assign) BOOL isTokenAutoRefreshEnabled;
 
+/// Requests Firebase app check token. This method should *only* be used if you need to authorize
+/// requests to a non-Firebase backend. Requests to Firebase backend are authorized automatically if
+/// configured.
+///
+/// If your non-Firebase backend exposes sensitive or expensive endpoints that have low traffic
+/// volume, consider protecting it with [Replay
+/// Protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection).
+/// In this case, use the ``limitedUseToken(completion:)`` instead to obtain a limited-use token.
+/// @param forcingRefresh If `YES`,  a new Firebase app check token is requested and the token
+/// cache is ignored. If `NO`, the cached token is used if it exists and has not expired yet. In
+/// most cases, `NO` should be used. `YES` should only be used if the server explicitly returns an
+/// error, indicating a revoked token.
+/// @param handler The completion handler. Includes the app check token if the request succeeds,
+/// or an error if the request fails.
+- (void)tokenForcingRefresh:(BOOL)forcingRefresh
+                 completion:
+                     (void (^)(FIRAppCheckToken *_Nullable token, NSError *_Nullable error))handler
+    NS_SWIFT_NAME(token(forcingRefresh:completion:));
+
+/// Requests a limited-use Firebase App Check token. This method should be used only if you need to
+/// authorize requests to a non-Firebase backend.
+///
+/// Returns limited-use tokens that are intended for use with your non-Firebase backend endpoints
+/// that are protected with [Replay
+/// Protection](https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection).
+/// This method does not affect the token generation behavior of the
+/// ``tokenForcingRefresh()`` method.
+- (void)limitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token,
+                                                NSError *_Nullable error))handler;
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 3 - 1
FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h

@@ -16,11 +16,13 @@
 
 #import <Foundation/Foundation.h>
 
+#import <FirebaseAppCheckInterop/FirebaseAppCheckInterop.h>
+
 NS_ASSUME_NONNULL_BEGIN
 
 /// An object representing a Firebase App Check token.
 NS_SWIFT_NAME(AppCheckToken)
-@interface FIRAppCheckToken : NSObject
+@interface FIRAppCheckToken : NSObject <FIRAppCheckTokenProtocol>
 
 /// A Firebase App Check token.
 @property(nonatomic, readonly) NSString *token;

+ 53 - 0
FirebaseAppCheck/Tests/Interop/ObjC/FIRAppCheckInteropAPITests.m

@@ -0,0 +1,53 @@
+// Copyright 2024 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.
+
+#import <Foundation/Foundation.h>
+
+#import <FirebaseAppCheckInterop/FirebaseAppCheckInterop.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRAppCheckInteropAPITests : NSObject
+@end
+
+@implementation FIRAppCheckInteropAPITests
+
+- (void)usage {
+  id<FIRAppCheckInterop> appCheckInterop;
+
+  [appCheckInterop getTokenForcingRefresh:NO
+                               completion:^(id<FIRAppCheckTokenResultInterop> tokenResult) {
+                                 NSString *__unused token = tokenResult.token;
+                                 NSError *__unused _Nullable error = tokenResult.error;
+                               }];
+
+  NSString *__unused tokenDidChangeNotificationName =
+      [appCheckInterop tokenDidChangeNotificationName];
+
+  NSString *__unused notificationTokenKey = [appCheckInterop notificationTokenKey];
+
+  NSString *__unused notificationAppNameKey = [appCheckInterop notificationAppNameKey];
+
+  if ([appCheckInterop respondsToSelector:@selector(getLimitedUseTokenWithCompletion:)]) {
+    [appCheckInterop
+        getLimitedUseTokenWithCompletion:^(id<FIRAppCheckTokenResultInterop> tokenResult) {
+          NSString *__unused token = tokenResult.token;
+          NSError *__unused _Nullable error = tokenResult.error;
+        }];
+  }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 51 - 0
FirebaseAppCheck/Tests/Interop/ObjC/FIRAppCheckProtocolAPITests.m

@@ -0,0 +1,51 @@
+// Copyright 2024 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.
+
+#import <Foundation/Foundation.h>
+
+#import <FirebaseAppCheckInterop/FirebaseAppCheckInterop.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRAppCheckProtocolAPITests : NSObject
+@end
+
+@implementation FIRAppCheckProtocolAPITests
+
+- (void)usage {
+  id<FIRAppCheckProtocol> appCheck;
+
+  [appCheck tokenForcingRefresh:NO
+                     completion:^(id<FIRAppCheckTokenProtocol> _Nullable token,
+                                  NSError *_Nullable error) {
+                       if (token) {
+                         NSString *__unused tokenValue = token.token;
+                         NSDate *__unused expirationDate = token.expirationDate;
+                       }
+                     }];
+
+  if ([appCheck respondsToSelector:@selector(limitedUseTokenWithCompletion:)]) {
+    [appCheck limitedUseTokenWithCompletion:^(id<FIRAppCheckTokenProtocol> _Nullable token,
+                                              NSError *_Nullable error) {
+      if (token) {
+        NSString *__unused tokenValue = token.token;
+        NSDate *__unused expirationDate = token.expirationDate;
+      }
+    }];
+  }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 64 - 0
FirebaseAppCheck/Tests/Interop/Swift/AppCheckInteropAPITests.swift

@@ -0,0 +1,64 @@
+// Copyright 2024 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.
+
+// MARK: This file is used to evaluate the AppCheckInterop API in Swift.
+
+import Foundation
+
+// MARK: Do not import `FirebaseAppCheck`, this file is for `FirebaseAppCheckInterop` only.
+
+import FirebaseAppCheckInterop
+
+final class AppCheckInteropAPITests {
+  let appCheckInterop: AppCheckInterop! = nil
+
+  func usage() {
+    let _: Void = appCheckInterop.getToken(forcingRefresh: false) { result in
+      let _: FIRAppCheckTokenResultInterop = result
+      let _: String = result.token
+      if let error = result.error {
+        let _: String = error.localizedDescription
+      }
+    }
+
+    let _: String = appCheckInterop.tokenDidChangeNotificationName()
+
+    let _: String = appCheckInterop.notificationTokenKey()
+
+    let _: String = appCheckInterop.notificationAppNameKey()
+
+    guard let getLimitedUseToken: (@escaping AppCheckTokenHandlerInterop) -> Void =
+      appCheckInterop.getLimitedUseToken else { return }
+    let _: Void = getLimitedUseToken { result in
+      let _: FIRAppCheckTokenResultInterop = result
+      let _: String = result.token
+      if let error = result.error {
+        let _: String = error.localizedDescription
+      }
+    }
+  }
+
+  @available(iOS 13, macOS 10.15, macCatalyst 13, tvOS 13, *)
+  func usage_async() async {
+    let result: FIRAppCheckTokenResultInterop =
+      await appCheckInterop.getToken(forcingRefresh: false)
+    let _: String = result.token
+    if let error = result.error {
+      let _: String = error.localizedDescription
+    }
+
+    // The following fails to compile with "Command SwiftCompile failed with a nonzero exit code".
+    // let _ = await appCheckInterop.getLimitedUseToken?()
+  }
+}

+ 58 - 0
FirebaseAppCheck/Tests/Interop/Swift/AppCheckProtocolAPITests.swift

@@ -0,0 +1,58 @@
+// Copyright 2024 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.
+
+// MARK: This file is used to evaluate the AppCheckProtocol API in Swift.
+
+import Foundation
+
+// MARK: Do not import `FirebaseAppCheck`, this file is for `FirebaseAppCheckInterop` only.
+
+import FirebaseAppCheckInterop
+
+final class AppCheckProtocolAPITests {
+  let appCheck: AppCheckProtocol! = nil
+
+  func usage() {
+    let _: Void = appCheck.token(forcingRefresh: false, completion: { token, error in
+      if let token: AppCheckTokenProtocol {
+        let _: String = token.token
+        let _: Date = token.expirationDate
+      }
+      if let error: Error {
+        let _: String = error.localizedDescription
+      }
+    })
+
+    let _: Void = appCheck.limitedUseToken { token, error in
+      if let token: AppCheckTokenProtocol {
+        let _: String = token.token
+        let _: Date = token.expirationDate
+      }
+      if let error: Error {
+        let _: String = error.localizedDescription
+      }
+    }
+  }
+
+  @available(iOS 13, macOS 10.15, macCatalyst 13, tvOS 13, *)
+  func usage_async() async {
+    do {
+      let token: AppCheckTokenProtocol = try await appCheck.token(forcingRefresh: false)
+      let _: String = token.token
+      let _: Date = token.expirationDate
+    } catch {
+      let _: String = error.localizedDescription
+    }
+  }
+}

+ 32 - 4
FirebaseAppCheckInterop.podspec

@@ -20,11 +20,39 @@ Pod::Spec.new do |s|
     :tag => 'CocoaPods-' + s.version.to_s
   }
   s.social_media_url = 'https://twitter.com/Firebase'
-  s.ios.deployment_target = '13.0'
-  s.osx.deployment_target = '10.15'
-  s.tvos.deployment_target = '13.0'
-  s.watchos.deployment_target = '7.0'
+
+  ios_deployment_target = '10.0'
+  osx_deployment_target = '10.13'
+  tvos_deployment_target = '12.0'
+  watchos_deployment_target = '6.0'
+
+  s.ios.deployment_target = ios_deployment_target
+  s.osx.deployment_target = osx_deployment_target
+  s.tvos.deployment_target = tvos_deployment_target
+  s.watchos.deployment_target = watchos_deployment_target
 
   s.source_files = 'FirebaseAppCheck/Interop/**/*.[hm]'
   s.public_header_files = 'FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/*.h'
+
+  s.test_spec 'objc-unit' do |unit_tests|
+    unit_tests.platforms = {
+      :ios => ios_deployment_target,
+      :osx => osx_deployment_target,
+      :tvos => tvos_deployment_target
+    }
+    unit_tests.source_files = [
+      'FirebaseAppCheck/Tests/Interop/ObjC/**/*.[hm]',
+    ]
+  end
+
+  s.test_spec 'swift-unit' do |swift_unit_tests|
+    swift_unit_tests.platforms = {
+      :ios => ios_deployment_target,
+      :osx => osx_deployment_target,
+      :tvos => tvos_deployment_target
+    }
+    swift_unit_tests.source_files = [
+      'FirebaseAppCheck/Tests/Interop/Swift/**/*.swift',
+    ]
+  end
 end

+ 0 - 1
FirebaseCore/Extension/FIRLogger.h

@@ -181,7 +181,6 @@ NS_SWIFT_NAME(FirebaseLogger)
 ///   three-character service identifier and a six digit integer message ID that is unique within
 ///   the service. An example of the message code is @"I-COR000001".
 ///   - message: Formatted string to be used as the log's message.
-///   - args: Arguments list obtained from calling `va_start`, used when message is a format string.
 + (void)logWithLevel:(FIRLoggerLevel)level
              service:(FIRLoggerService)service
                 code:(NSString *)code

+ 5 - 14
FirebaseMLModelDownloader/Sources/DeviceLogger.swift

@@ -13,11 +13,8 @@
 // limitations under the License.
 
 import Foundation
-#if SWIFT_PACKAGE
-  @_implementationOnly import GoogleUtilities_Logger
-#else
-  @_implementationOnly import GoogleUtilities
-#endif
+
+@_implementationOnly import FirebaseCoreExtension
 
 /// Enum of log messages.
 enum LoggerMessageCode: Int {
@@ -76,15 +73,9 @@ enum DeviceLogger {
   /// Log identifier.
   static let service = "[Firebase/MLModelDownloader]"
 
-  static func logEvent(level: GoogleLoggerLevel, message: String, messageCode: LoggerMessageCode) {
+  static func logEvent(level: FirebaseLoggerLevel, message: String,
+                       messageCode: LoggerMessageCode) {
     let code = String(format: "I-MLM%06d", messageCode.rawValue)
-    let args: [CVarArg] = []
-    GULLoggerWrapper.log(
-      with: level,
-      withService: DeviceLogger.service,
-      withCode: code,
-      withMessage: message,
-      withArgs: getVaList(args)
-    )
+    FirebaseLogger.log(level: level, service: DeviceLogger.service, code: code, message: message)
   }
 }

+ 0 - 1
FirebaseMLModelDownloader/Tests/Unit/ModelDownloaderUnitTests.swift

@@ -23,7 +23,6 @@
   @testable import FirebaseMLModelDownloader
   import XCTest
   #if SWIFT_PACKAGE
-    @_implementationOnly import GoogleUtilities_Logger
     @_implementationOnly import GoogleUtilities_UserDefaults
   #else
     @_implementationOnly import GoogleUtilities

+ 4 - 0
FirebaseMessaging/CHANGELOG.md

@@ -1,6 +1,10 @@
 # 11.0.0
 - [fixed] Completed Messaging's transition to NSSecureCoding (#12343).
 
+# 10.29.0
+- [fixed] Renamed "initWithFileName" internal method that was causing submission issues for some
+  users. (#13134).
+
 # 10.27.0
 - [fixed] Fixed bug preventing Messaging from working with a custom sqlite3
   dependency (#12900).

+ 1 - 1
FirebaseMessaging/Sources/Token/FIRMessagingBackupExcludedPlist.h

@@ -39,7 +39,7 @@
  *
  *  @return Helper which allows to read write data to a backup excluded plist.
  */
-- (instancetype)initWithFileName:(NSString *)fileName subDirectory:(NSString *)subDirectory;
+- (instancetype)initWithPlistFile:(NSString *)fileName subDirectory:(NSString *)subDirectory;
 
 /**
  *  Write dictionary data to the backup excluded plist file. If the file does not exist

+ 1 - 1
FirebaseMessaging/Sources/Token/FIRMessagingBackupExcludedPlist.m

@@ -28,7 +28,7 @@
 
 @implementation FIRMessagingBackupExcludedPlist
 
-- (instancetype)initWithFileName:(NSString *)fileName subDirectory:(NSString *)subDirectory {
+- (instancetype)initWithPlistFile:(NSString *)fileName subDirectory:(NSString *)subDirectory {
   self = [super init];
   if (self) {
     _fileName = [fileName copy];

+ 2 - 2
FirebaseMessaging/Sources/Token/FIRMessagingCheckinStore.m

@@ -48,8 +48,8 @@ NSString *const kFIRMessagingCheckinKeychainService = @"com.google.iid.checkin";
   self = [super init];
   if (self) {
     _plist = [[FIRMessagingBackupExcludedPlist alloc]
-        initWithFileName:kCheckinFileName
-            subDirectory:kFIRMessagingInstanceIDSubDirectoryName];
+        initWithPlistFile:kCheckinFileName
+             subDirectory:kFIRMessagingInstanceIDSubDirectoryName];
     _keychain =
         [[FIRMessagingAuthKeychain alloc] initWithIdentifier:kFIRMessagingCheckinKeychainGeneric];
   }

+ 2 - 2
FirebaseMessaging/Tests/UnitTests/FIRMessagingBackupExcludedPlistTest.m

@@ -44,8 +44,8 @@ static NSString *const kTestPlistFileName = @"com.google.test.IIDBackupExcludedP
 - (void)setUp {
   [super setUp];
   [FIRMessaging createSubDirectory:kSubDirectoryName];
-  self.plist = [[FIRMessagingBackupExcludedPlist alloc] initWithFileName:kTestPlistFileName
-                                                            subDirectory:kSubDirectoryName];
+  self.plist = [[FIRMessagingBackupExcludedPlist alloc] initWithPlistFile:kTestPlistFileName
+                                                             subDirectory:kSubDirectoryName];
 }
 
 - (void)tearDown {

+ 2 - 2
FirebaseMessaging/Tests/UnitTests/FIRMessagingCheckinStoreTest.m

@@ -67,8 +67,8 @@ static int64_t const kLastCheckinTimestamp = 123456;
   [super setUp];
   [FIRMessaging createSubDirectory:kSubDirectoryName];
   self.checkinStore = [[FIRMessagingCheckinStore alloc] init];
-  self.plist = [[FIRMessagingBackupExcludedPlist alloc] initWithFileName:kFakeCheckinPlistName
-                                                            subDirectory:kSubDirectoryName];
+  self.plist = [[FIRMessagingBackupExcludedPlist alloc] initWithPlistFile:kFakeCheckinPlistName
+                                                             subDirectory:kSubDirectoryName];
   self.checkinStore.plist = self.plist;
 }
 

+ 2 - 2
FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenStoreTest.m

@@ -76,8 +76,8 @@ static NSString *const kFakeCheckinPlistName = @"com.google.test.TestTokenStore"
   [FIRMessaging createSubDirectory:kSubDirectoryName];
 
   self.checkinPlist =
-      [[FIRMessagingBackupExcludedPlist alloc] initWithFileName:kFakeCheckinPlistName
-                                                   subDirectory:kSubDirectoryName];
+      [[FIRMessagingBackupExcludedPlist alloc] initWithPlistFile:kFakeCheckinPlistName
+                                                    subDirectory:kSubDirectoryName];
 
   // checkin store
   FIRMessagingFakeKeychain *fakeKeychain = [[FIRMessagingFakeKeychain alloc] init];

+ 2 - 1
FirebasePerformance/CHANGELOG.md

@@ -1,5 +1,6 @@
-# Unreleased
+# 10.28.0
 - Fix Crash from InstrumentUploadTaskWithStreamedRequest (#12983).
+- Replace SystemConfiguration with a more recent network monitoring API by Apple (#13079).
 
 # 10.25.0
 - [changed] Removed usages of user defaults API to eliminate required reason impact.

+ 4 - 0
FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h

@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#import "FirebasePerformance/Sources/Protogen/nanopb/perf_metric.nanopb.h"
 #import "FirebasePerformance/Sources/Public/FirebasePerformance/FIRTrace.h"
 
 FOUNDATION_EXTERN NSString *__nonnull const kFPRAppStartTraceName;
@@ -50,6 +51,9 @@ NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions."
 /** Current running state of the application. */
 @property(nonatomic, readonly) FPRApplicationState applicationState;
 
+/** Current network connection type of the application. */
+@property(nonatomic, readonly) firebase_perf_v1_NetworkConnectionInfo_NetworkType networkType;
+
 /** Accesses the singleton instance.
  *  @return Reference to the shared object if successful; <code>nil</code> if not.
  */

+ 49 - 3
FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.m

@@ -15,6 +15,7 @@
 #import "FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h"
 
 #import <Foundation/Foundation.h>
+#import <Network/Network.h>
 #import <UIKit/UIKit.h>
 
 #import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
@@ -55,6 +56,15 @@ NSString *const kFPRAppCounterNameActivePrewarm = @"_fsapc";
 /** Current running state of the application. */
 @property(nonatomic, readwrite) FPRApplicationState applicationState;
 
+/** Current network connection type of the application. */
+@property(nonatomic, readwrite) firebase_perf_v1_NetworkConnectionInfo_NetworkType networkType;
+
+/** Network monitor object to track network movements. */
+@property(nonatomic, readwrite) nw_path_monitor_t monitor;
+
+/** Queue used to track the network monitoring changes. */
+@property(nonatomic, readwrite) dispatch_queue_t monitorQueue;
+
 /** Trace to measure the app start performance. */
 @property(nonatomic) FIRTrace *appStartTrace;
 
@@ -122,9 +132,12 @@ NSString *const kFPRAppCounterNameActivePrewarm = @"_fsapc";
  */
 - (instancetype)initAppActivityTracker {
   self = [super init];
-  _applicationState = FPRApplicationStateUnknown;
-  _appStartGaugeMetricDispatched = NO;
-  _configurations = [FPRConfigurations sharedInstance];
+  if (self != nil) {
+    _applicationState = FPRApplicationStateUnknown;
+    _appStartGaugeMetricDispatched = NO;
+    _configurations = [FPRConfigurations sharedInstance];
+    [self startTrackingNetwork];
+  }
   return self;
 }
 
@@ -147,6 +160,35 @@ NSString *const kFPRAppCounterNameActivePrewarm = @"_fsapc";
   return self.backgroundSessionTrace;
 }
 
+- (void)startTrackingNetwork {
+  self.networkType = firebase_perf_v1_NetworkConnectionInfo_NetworkType_NONE;
+
+  if (@available(iOS 12, tvOS 12, *)) {
+    dispatch_queue_attr_t attrs = dispatch_queue_attr_make_with_qos_class(
+        DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, DISPATCH_QUEUE_PRIORITY_DEFAULT);
+    self.monitorQueue = dispatch_queue_create("com.google.firebase.perf.network.monitor", attrs);
+
+    self.monitor = nw_path_monitor_create();
+    nw_path_monitor_set_queue(self.monitor, self.monitorQueue);
+    __weak FPRAppActivityTracker *weakSelf = self;
+    nw_path_monitor_set_update_handler(self.monitor, ^(nw_path_t _Nonnull path) {
+      BOOL isWiFi = nw_path_uses_interface_type(path, nw_interface_type_wifi);
+      BOOL isCellular = nw_path_uses_interface_type(path, nw_interface_type_cellular);
+      BOOL isEthernet = nw_path_uses_interface_type(path, nw_interface_type_wired);
+
+      if (isWiFi) {
+        weakSelf.networkType = firebase_perf_v1_NetworkConnectionInfo_NetworkType_WIFI;
+      } else if (isCellular) {
+        weakSelf.networkType = firebase_perf_v1_NetworkConnectionInfo_NetworkType_MOBILE;
+      } else if (isEthernet) {
+        weakSelf.networkType = firebase_perf_v1_NetworkConnectionInfo_NetworkType_ETHERNET;
+      }
+    });
+
+    nw_path_monitor_start(self.monitor);
+  }
+}
+
 /**
  * Checks if the prewarming feature is available on the current device.
  *
@@ -286,6 +328,10 @@ NSString *const kFPRAppCounterNameActivePrewarm = @"_fsapc";
 }
 
 - (void)dealloc {
+  if (@available(iOS 12, tvOS 12, *)) {
+    nw_path_monitor_cancel(self.monitor);
+  }
+
   [[NSNotificationCenter defaultCenter] removeObserver:self
                                                   name:UIApplicationDidBecomeActiveNotification
                                                 object:[UIApplication sharedApplication]];

+ 3 - 33
FirebasePerformance/Sources/FPRNanoPbUtils.m

@@ -18,8 +18,8 @@
 #import <CoreTelephony/CTCarrier.h>
 #import <CoreTelephony/CTTelephonyNetworkInfo.h>
 #endif
-#import <SystemConfiguration/SystemConfiguration.h>
 
+#import "FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h"
 #import "FirebasePerformance/Sources/Common/FPRConstants.h"
 #import "FirebasePerformance/Sources/FIRPerformance+Internal.h"
 #import "FirebasePerformance/Sources/FPRDataUtils.h"
@@ -34,7 +34,6 @@
 
 static firebase_perf_v1_NetworkRequestMetric_HttpMethod FPRHTTPMethodForString(
     NSString *methodString);
-static firebase_perf_v1_NetworkConnectionInfo_NetworkType FPRNetworkConnectionInfoNetworkType(void);
 #ifdef TARGET_HAS_MOBILE_CONNECTIVITY
 static firebase_perf_v1_NetworkConnectionInfo_MobileSubtype FPRCellularNetworkType(void);
 #endif
@@ -72,36 +71,6 @@ static firebase_perf_v1_NetworkRequestMetric_HttpMethod FPRHTTPMethodForString(
   return HTTPMethod.intValue;
 }
 
-/** Get the current network connection type in firebase_perf_v1_NetworkConnectionInfo_NetworkType
- * format.
- *  @return Current network connection type.
- */
-static firebase_perf_v1_NetworkConnectionInfo_NetworkType FPRNetworkConnectionInfoNetworkType(
-    void) {
-  firebase_perf_v1_NetworkConnectionInfo_NetworkType networkType =
-      firebase_perf_v1_NetworkConnectionInfo_NetworkType_NONE;
-
-  static SCNetworkReachabilityRef reachabilityRef = 0;
-  static dispatch_once_t onceToken;
-  dispatch_once(&onceToken, ^{
-    reachabilityRef = SCNetworkReachabilityCreateWithName(kCFAllocatorSystemDefault, "google.com");
-  });
-
-  SCNetworkReachabilityFlags reachabilityFlags = 0;
-  SCNetworkReachabilityGetFlags(reachabilityRef, &reachabilityFlags);
-
-  // Parse the network flags to set the network type.
-  if (reachabilityFlags & kSCNetworkReachabilityFlagsReachable) {
-    if (reachabilityFlags & kSCNetworkReachabilityFlagsIsWWAN) {
-      networkType = firebase_perf_v1_NetworkConnectionInfo_NetworkType_MOBILE;
-    } else {
-      networkType = firebase_perf_v1_NetworkConnectionInfo_NetworkType_WIFI;
-    }
-  }
-
-  return networkType;
-}
-
 #ifdef TARGET_HAS_MOBILE_CONNECTIVITY
 /** Get the current cellular network connection type in
  * firebase_perf_v1_NetworkConnectionInfo_MobileSubtype format.
@@ -233,7 +202,8 @@ firebase_perf_v1_ApplicationInfo FPRGetApplicationInfoMessage(void) {
   iosAppInfo.bundle_short_version =
       FPREncodeString([mainBundle infoDictionary][@"CFBundleShortVersionString"]);
   iosAppInfo.sdk_version = FPREncodeString([NSString stringWithUTF8String:kFPRSDKVersion]);
-  iosAppInfo.network_connection_info.network_type = FPRNetworkConnectionInfoNetworkType();
+  iosAppInfo.network_connection_info.network_type =
+      [FPRAppActivityTracker sharedInstance].networkType;
   iosAppInfo.has_network_connection_info = true;
   iosAppInfo.network_connection_info.has_network_type = true;
 #ifdef TARGET_HAS_MOBILE_CONNECTIVITY

+ 8 - 0
FirebaseVertexAI/CHANGELOG.md

@@ -1,3 +1,11 @@
+# 10.28.0
+- [changed] Removed uses of the `gemini-1.5-flash-preview-0514` model in docs
+  and samples. Developers should now use the auto-updated versions,
+  `gemini-1.5-pro` or `gemini-1.5-flash`, or a specific stable version; see
+  [available model names](https://firebase.google.com/docs/vertex-ai/gemini-models#available-model-names)
+  for more details. (#13099)
+- [feature] Added community support for tvOS and visionOS. (#13090, #13092)
+
 # 10.27.0
 - [changed] Removed uses of the `gemini-1.5-pro-preview-0409` model in docs and
   samples. Developers should now use `gemini-1.5-pro-preview-0514` or

+ 1 - 1
FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift

@@ -36,7 +36,7 @@ class ConversationViewModel: ObservableObject {
   private var chatTask: Task<Void, Never>?
 
   init() {
-    model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash-preview-0514")
+    model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash")
     chat = model.startChat()
   }
 

+ 1 - 1
FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift

@@ -39,7 +39,7 @@ class FunctionCallingViewModel: ObservableObject {
 
   init() {
     model = VertexAI.vertexAI().generativeModel(
-      modelName: "gemini-1.5-flash-preview-0514",
+      modelName: "gemini-1.5-flash",
       tools: [Tool(functionDeclarations: [
         FunctionDeclaration(
           name: "get_exchange_rate",

+ 1 - 1
FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift

@@ -44,7 +44,7 @@ class PhotoReasoningViewModel: ObservableObject {
   private var model: GenerativeModel?
 
   init() {
-    model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash-preview-0514")
+    model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash")
   }
 
   func reason() async {

+ 1 - 1
FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift

@@ -32,7 +32,7 @@ class SummarizeViewModel: ObservableObject {
   private var model: GenerativeModel?
 
   init() {
-    model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash-preview-0514")
+    model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash")
   }
 
   func summarize(inputText: String) async {

+ 1 - 1
FirebaseVertexAI/Sources/Chat.swift

@@ -16,7 +16,7 @@ import Foundation
 
 /// An object that represents a back-and-forth chat with a model, capturing the history and saving
 /// the context in memory between each message sent.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public class Chat {
   private let model: GenerativeModel
 

+ 0 - 1
FirebaseVertexAI/Sources/Constants.swift

@@ -19,7 +19,6 @@ import Foundation
 #endif
 
 /// Constants associated with the Vertex AI for Firebase SDK.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
 enum Constants {
   /// The Vertex AI backend endpoint URL.
   static let baseURL = "https://firebaseml.googleapis.com"

+ 5 - 5
FirebaseVertexAI/Sources/CountTokensRequest.swift

@@ -14,14 +14,14 @@
 
 import Foundation
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 struct CountTokensRequest {
   let model: String
   let contents: [ModelContent]
   let options: RequestOptions
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension CountTokensRequest: GenerativeAIRequest {
   typealias Response = CountTokensResponse
 
@@ -31,7 +31,7 @@ extension CountTokensRequest: GenerativeAIRequest {
 }
 
 /// The model's response to a count tokens request.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct CountTokensResponse {
   /// The total number of tokens in the input given to the model as a prompt.
   public let totalTokens: Int
@@ -45,14 +45,14 @@ public struct CountTokensResponse {
 
 // MARK: - Codable Conformances
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension CountTokensRequest: Encodable {
   enum CodingKeys: CodingKey {
     case contents
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension CountTokensResponse: Decodable {
   enum CodingKeys: CodingKey {
     case totalTokens

+ 1 - 1
FirebaseVertexAI/Sources/GenerateContentError.swift

@@ -15,7 +15,7 @@
 import Foundation
 
 /// Errors that occur when generating content from a model.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public enum GenerateContentError: Error {
   /// An error occurred when constructing the prompt. Examine the related error for details.
   case promptImageContentError(underlying: ImageConversionError)

+ 3 - 3
FirebaseVertexAI/Sources/GenerateContentRequest.swift

@@ -14,7 +14,7 @@
 
 import Foundation
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 struct GenerateContentRequest {
   /// Model name.
   let model: String
@@ -28,7 +28,7 @@ struct GenerateContentRequest {
   let options: RequestOptions
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension GenerateContentRequest: Encodable {
   enum CodingKeys: String, CodingKey {
     case contents
@@ -40,7 +40,7 @@ extension GenerateContentRequest: Encodable {
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension GenerateContentRequest: GenerativeAIRequest {
   typealias Response = GenerateContentResponse
 

+ 14 - 14
FirebaseVertexAI/Sources/GenerateContentResponse.swift

@@ -15,7 +15,7 @@
 import Foundation
 
 /// The model's response to a generate content request.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct GenerateContentResponse {
   /// Token usage metadata for processing the generate content request.
   public struct UsageMetadata {
@@ -84,7 +84,7 @@ public struct GenerateContentResponse {
 
 /// A struct representing a possible reply to a content generation prompt. Each content generation
 /// prompt may produce multiple candidate responses.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct CandidateResponse {
   /// The response's content.
   public let content: ModelContent
@@ -110,14 +110,14 @@ public struct CandidateResponse {
 }
 
 /// A collection of source attributions for a piece of content.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct CitationMetadata {
   /// A list of individual cited sources and the parts of the content to which they apply.
   public let citationSources: [Citation]
 }
 
 /// A struct describing a source attribution.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct Citation {
   /// The inclusive beginning of a sequence in a model response that derives from a cited source.
   public let startIndex: Int
@@ -133,7 +133,7 @@ public struct Citation {
 }
 
 /// A value enumerating possible reasons for a model to terminate a content generation request.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public enum FinishReason: String {
   case unknown = "FINISH_REASON_UNKNOWN"
 
@@ -158,7 +158,7 @@ public enum FinishReason: String {
 }
 
 /// A metadata struct containing any feedback the model had on the prompt it was provided.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct PromptFeedback {
   /// A type describing possible reasons to block a prompt.
   public enum BlockReason: String {
@@ -190,7 +190,7 @@ public struct PromptFeedback {
 
 // MARK: - Codable Conformances
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension GenerateContentResponse: Decodable {
   enum CodingKeys: CodingKey {
     case candidates
@@ -224,7 +224,7 @@ extension GenerateContentResponse: Decodable {
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension GenerateContentResponse.UsageMetadata: Decodable {
   enum CodingKeys: CodingKey {
     case promptTokenCount
@@ -241,7 +241,7 @@ extension GenerateContentResponse.UsageMetadata: Decodable {
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension CandidateResponse: Decodable {
   enum CodingKeys: CodingKey {
     case content
@@ -290,14 +290,14 @@ extension CandidateResponse: Decodable {
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension CitationMetadata: Decodable {
   enum CodingKeys: String, CodingKey {
     case citationSources = "citations"
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension Citation: Decodable {
   enum CodingKeys: CodingKey {
     case startIndex
@@ -315,7 +315,7 @@ extension Citation: Decodable {
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension FinishReason: Decodable {
   public init(from decoder: Decoder) throws {
     let value = try decoder.singleValueContainer().decode(String.self)
@@ -330,7 +330,7 @@ extension FinishReason: Decodable {
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension PromptFeedback.BlockReason: Decodable {
   public init(from decoder: Decoder) throws {
     let value = try decoder.singleValueContainer().decode(String.self)
@@ -345,7 +345,7 @@ extension PromptFeedback.BlockReason: Decodable {
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension PromptFeedback: Decodable {
   enum CodingKeys: CodingKey {
     case blockReason

+ 2 - 2
FirebaseVertexAI/Sources/GenerationConfig.swift

@@ -16,7 +16,7 @@ import Foundation
 
 /// A struct defining model parameters to be used when sending generative AI
 /// requests to the backend model.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct GenerationConfig {
   /// A parameter controlling the degree of randomness in token selection. A
   /// temperature of zero is deterministic, always choosing the
@@ -95,5 +95,5 @@ public struct GenerationConfig {
 
 // MARK: - Codable Conformances
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension GenerationConfig: Encodable {}

+ 2 - 2
FirebaseVertexAI/Sources/GenerativeAIRequest.swift

@@ -14,7 +14,7 @@
 
 import Foundation
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 protocol GenerativeAIRequest: Encodable {
   associatedtype Response: Decodable
 
@@ -24,7 +24,7 @@ protocol GenerativeAIRequest: Encodable {
 }
 
 /// Configuration parameters for sending requests to the backend.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct RequestOptions {
   /// The request’s timeout interval in seconds; if not specified uses the default value for a
   /// `URLRequest`.

+ 1 - 1
FirebaseVertexAI/Sources/GenerativeAIService.swift

@@ -17,7 +17,7 @@ import FirebaseAuthInterop
 import FirebaseCore
 import Foundation
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 struct GenerativeAIService {
   /// The language of the SDK in the format `gl-<language>/<version>`.
   static let languageTag = "gl-swift/5"

+ 2 - 2
FirebaseVertexAI/Sources/GenerativeModel.swift

@@ -18,7 +18,7 @@ import Foundation
 
 /// A type that represents a remote multimodal model (like Gemini), with the ability to generate
 /// content based on various input types.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public final class GenerativeModel {
   // The prefix for a model resource in the Gemini API.
   private static let modelResourcePrefix = "models/"
@@ -318,7 +318,7 @@ public final class GenerativeModel {
 }
 
 /// An error thrown in `GenerativeModel.countTokens(_:)`.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public enum CountTokensError: Error {
   case internalError(underlying: Error)
 }

+ 1 - 1
FirebaseVertexAI/Sources/Logging.swift

@@ -15,7 +15,7 @@
 import Foundation
 import OSLog
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 struct Logging {
   /// Subsystem that should be used for all Loggers.
   static let subsystem = "com.google.firebase.vertex-ai"

+ 3 - 3
FirebaseVertexAI/Sources/ModelContent.swift

@@ -17,7 +17,7 @@ import Foundation
 /// A type describing data in media formats interpretable by an AI model. Each generative AI
 /// request or response contains an `Array` of ``ModelContent``s, and each ``ModelContent`` value
 /// may comprise multiple heterogeneous ``ModelContent/Part``s.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct ModelContent: Equatable {
   /// A discrete piece of data in a media format interpretable by an AI model. Within a single value
   /// of ``Part``, different data types may not mix.
@@ -116,10 +116,10 @@ public struct ModelContent: Equatable {
 
 // MARK: Codable Conformances
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension ModelContent: Codable {}
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension ModelContent.Part: Codable {
   enum CodingKeys: String, CodingKey {
     case text

+ 4 - 4
FirebaseVertexAI/Sources/PartsRepresentable+Image.swift

@@ -38,7 +38,7 @@ public enum ImageConversionError: Error {
 
 #if canImport(UIKit)
   /// Enables images to be representable as ``ThrowingPartsRepresentable``.
-  @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+  @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
   extension UIImage: ThrowingPartsRepresentable {
     public func tryPartsValue() throws -> [ModelContent.Part] {
       guard let data = jpegData(compressionQuality: imageCompressionQuality) else {
@@ -50,7 +50,7 @@ public enum ImageConversionError: Error {
 
 #elseif canImport(AppKit)
   /// Enables images to be representable as ``ThrowingPartsRepresentable``.
-  @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+  @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
   extension NSImage: ThrowingPartsRepresentable {
     public func tryPartsValue() throws -> [ModelContent.Part] {
       guard let cgImage = cgImage(forProposedRect: nil, context: nil, hints: nil) else {
@@ -67,7 +67,7 @@ public enum ImageConversionError: Error {
 #endif
 
 /// Enables `CGImages` to be representable as model content.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension CGImage: ThrowingPartsRepresentable {
   public func tryPartsValue() throws -> [ModelContent.Part] {
     let output = NSMutableData()
@@ -88,7 +88,7 @@ extension CGImage: ThrowingPartsRepresentable {
 }
 
 /// Enables `CIImages` to be representable as model content.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension CIImage: ThrowingPartsRepresentable {
   public func tryPartsValue() throws -> [ModelContent.Part] {
     let context = CIContext()

+ 6 - 6
FirebaseVertexAI/Sources/PartsRepresentable.swift

@@ -16,7 +16,7 @@ import Foundation
 
 /// A protocol describing any data that could be serialized to model-interpretable input data,
 /// where the serialization process might fail with an error.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public protocol ThrowingPartsRepresentable {
   func tryPartsValue() throws -> [ModelContent.Part]
 }
@@ -24,12 +24,12 @@ public protocol ThrowingPartsRepresentable {
 /// A protocol describing any data that could be serialized to model-interpretable input data,
 /// where the serialization process cannot fail with an error. For a failable conversion, see
 /// ``ThrowingPartsRepresentable``
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public protocol PartsRepresentable: ThrowingPartsRepresentable {
   var partsValue: [ModelContent.Part] { get }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public extension PartsRepresentable {
   func tryPartsValue() throws -> [ModelContent.Part] {
     return partsValue
@@ -37,7 +37,7 @@ public extension PartsRepresentable {
 }
 
 /// Enables a ``ModelContent.Part`` to be passed in as ``ThrowingPartsRepresentable``.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension ModelContent.Part: ThrowingPartsRepresentable {
   public typealias ErrorType = Never
   public func tryPartsValue() throws -> [ModelContent.Part] {
@@ -47,7 +47,7 @@ extension ModelContent.Part: ThrowingPartsRepresentable {
 
 /// Enable an `Array` of ``ThrowingPartsRepresentable`` values to be passed in as a single
 /// ``ThrowingPartsRepresentable``.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension [ThrowingPartsRepresentable]: ThrowingPartsRepresentable {
   public func tryPartsValue() throws -> [ModelContent.Part] {
     return try compactMap { element in
@@ -58,7 +58,7 @@ extension [ThrowingPartsRepresentable]: ThrowingPartsRepresentable {
 }
 
 /// Enables a `String` to be passed in as ``ThrowingPartsRepresentable``.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension String: PartsRepresentable {
   public var partsValue: [ModelContent.Part] {
     return [.text(self)]

+ 9 - 9
FirebaseVertexAI/Sources/Safety.swift

@@ -17,7 +17,7 @@ import Foundation
 /// A type defining potentially harmful media categories and their model-assigned ratings. A value
 /// of this type may be assigned to a category for every model-generated response, not just
 /// responses that exceed a certain threshold.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct SafetyRating: Equatable, Hashable {
   /// The category describing the potential harm a piece of content may pose. See
   /// ``SafetySetting/HarmCategory`` for a list of possible values.
@@ -60,7 +60,7 @@ public struct SafetyRating: Equatable, Hashable {
 }
 
 /// Safety feedback for an entire request.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct SafetyFeedback {
   /// Safety rating evaluated from content.
   public let rating: SafetyRating
@@ -77,7 +77,7 @@ public struct SafetyFeedback {
 
 /// A type used to specify a threshold for harmful content, beyond which the model will return a
 /// fallback response instead of generated content.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public struct SafetySetting {
   /// A type describing safety attributes, which include harmful categories and topics that can
   /// be considered sensitive.
@@ -142,7 +142,7 @@ public struct SafetySetting {
 
 // MARK: - Codable Conformances
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension SafetyRating.HarmProbability: Codable {
   public init(from decoder: Decoder) throws {
     let value = try decoder.singleValueContainer().decode(String.self)
@@ -157,13 +157,13 @@ extension SafetyRating.HarmProbability: Codable {
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension SafetyRating: Decodable {}
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension SafetyFeedback: Decodable {}
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension SafetySetting.HarmCategory: Codable {
   public init(from decoder: Decoder) throws {
     let value = try decoder.singleValueContainer().decode(String.self)
@@ -178,7 +178,7 @@ extension SafetySetting.HarmCategory: Codable {
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension SafetySetting.BlockThreshold: Codable {
   public init(from decoder: Decoder) throws {
     let value = try decoder.singleValueContainer().decode(String.self)
@@ -193,5 +193,5 @@ extension SafetySetting.BlockThreshold: Codable {
   }
 }
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 extension SafetySetting: Codable {}

+ 3 - 3
FirebaseVertexAI/Sources/VertexAI.swift

@@ -21,7 +21,7 @@ import Foundation
 @_implementationOnly import FirebaseCoreExtension
 
 /// The Vertex AI for Firebase SDK provides access to Gemini models directly from your app.
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 public class VertexAI: NSObject {
   // MARK: - Public APIs
 
@@ -69,8 +69,8 @@ public class VertexAI: NSObject {
   /// guidance on choosing an appropriate model for your use case.
   ///
   /// - Parameters:
-  ///   - modelName: The name of the model to use, for example `"gemini-1.5-flash-preview-0514"`;
-  ///     see [available model names
+  ///   - modelName: The name of the model to use, for example `"gemini-1.5-flash"`; see
+  ///     [available model names
   ///     ](https://firebase.google.com/docs/vertex-ai/gemini-models#available-model-names) for a
   ///     list of supported model names.
   ///   - generationConfig: The content generation parameters your model should use.

+ 74 - 0
FirebaseVertexAI/Tests/Integration/IntegrationTests.swift

@@ -0,0 +1,74 @@
+// Copyright 2024 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.
+
+import FirebaseCore
+import FirebaseVertexAI
+import XCTest
+
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, *)
+final class IntegrationTests: XCTestCase {
+  // Set temperature, topP and topK to lowest allowed values to make responses more deterministic.
+  let generationConfig = GenerationConfig(temperature: 0.0, topP: 0.0, topK: 1)
+
+  var vertex: VertexAI!
+  var model: GenerativeModel!
+
+  override func setUp() async throws {
+    try XCTSkipIf(ProcessInfo.processInfo.environment["VertexAIRunIntegrationTests"] == nil, """
+    Vertex AI integration tests skipped; to enable them, set the VertexAIRunIntegrationTests \
+    environment variable in Xcode or CI jobs.
+    """)
+
+    let plistPath = try XCTUnwrap(Bundle.module.path(
+      forResource: "GoogleService-Info",
+      ofType: "plist"
+    ))
+    let options = try XCTUnwrap(FirebaseOptions(contentsOfFile: plistPath))
+    FirebaseApp.configure(options: options)
+
+    vertex = VertexAI.vertexAI()
+    model = vertex.generativeModel(
+      modelName: "gemini-1.5-flash",
+      generationConfig: generationConfig
+    )
+  }
+
+  override func tearDown() async throws {
+    if let app = FirebaseApp.app() {
+      await app.delete()
+    }
+  }
+
+  // MARK: - Generate Content
+
+  func testGenerateContent() async throws {
+    let prompt = "Where is Google headquarters located? Answer with the city name only."
+
+    let response = try await model.generateContent(prompt)
+
+    let text = try XCTUnwrap(response.text).trimmingCharacters(in: .whitespacesAndNewlines)
+    XCTAssertEqual(text, "Mountain View")
+  }
+
+  // MARK: - Count Tokens
+
+  func testCountTokens() async throws {
+    let prompt = "Why is the sky blue?"
+
+    let response = try await model.countTokens(prompt)
+
+    XCTAssertEqual(response.totalTokens, 6)
+    XCTAssertEqual(response.totalBillableCharacters, 16)
+  }
+}

+ 0 - 0
FirebaseVertexAI/Tests/Integration/Resources/placeholder.txt


+ 2 - 2
FirebaseVertexAI/Tests/Unit/ChatTests.swift

@@ -17,7 +17,7 @@ import XCTest
 
 @testable import FirebaseVertexAI
 
-@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, *)
 final class ChatTests: XCTestCase {
   var urlSession: URLSession!
 
@@ -33,7 +33,7 @@ final class ChatTests: XCTestCase {
 
   func testMergingText() async throws {
     let fileURL = try XCTUnwrap(Bundle.module.url(
-      forResource: "streaming-success-basic-reply-long",
+      forResource: "streaming-success-basic-reply-parts",
       withExtension: "txt"
     ))
 

+ 1 - 1
FirebaseVertexAI/Tests/Unit/Fakes/AuthInteropFake.swift

@@ -15,7 +15,7 @@
 import FirebaseAuthInterop
 import Foundation
 
-@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, *)
 class AuthInteropFake: NSObject, AuthInterop {
   let token: String?
   let error: Error?

+ 0 - 2
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-failure-finish-reason-safety.txt

@@ -1,2 +0,0 @@
-data: {"candidates": [{"content": {"parts": [{"text": "No"}]},"finishReason": "SAFETY","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "HIGH"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}
-

+ 0 - 6
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-failure-recitation-no-content.txt

@@ -1,6 +0,0 @@
-data: {"candidates": [{"content": {"parts": [{"text": "Some information"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}
-
-data: {"candidates": [{"content": {"parts": [{"text": "Some information cited from an external source"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "LOW"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citationSources": [{"startIndex": 30,"endIndex": 179,"uri": "https://www.example.com/some-citation","license": ""}]}}]}
-
-data: {"candidates": [{"finishReason": "RECITATION","index": 0}]}
-

+ 0 - 1
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-success-basic-reply-short.txt

@@ -1 +0,0 @@
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": "Mountain View, California"}]},"finishReason": "STOP","safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.02854415,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.052424565},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.24926445,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.0996453},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.087096825,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.043123372},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.14402841,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.086169556}]}],"usageMetadata": {"promptTokenCount": 6,"candidatesTokenCount": 4,"totalTokenCount": 10}}

+ 0 - 7
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-success-citations.txt

@@ -1,7 +0,0 @@
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": "Some information"}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.043204036,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.082549304},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.046291895,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.071461484},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.100701615,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.06164962},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.13150747,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.040087357}]}]}
-
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": "  Some information cited from an external source"}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.18982129,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.1337543},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.13637818,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.021906368},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.25404602,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.09073549},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.24202643,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.06548521}],"citationMetadata": {"citations": [{"endIndex": 128,"uri": "https://www.example.com/citation-1"},{"startIndex": 130,"endIndex": 265,"uri": "https://www.example.com/citation-2"}]}}]}
-
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": " More information cited from an external source"}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.07850098,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.039416388},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.08035747,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.04885778},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.12273335,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.059646938},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.053206205,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.04099903}],"citationMetadata": {"citations": [{"startIndex": 272,"endIndex": 431,"uri": "https://www.example.com/citation-3","license": "mit"}]}}]}
-
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": " More information "}]},"finishReason": "STOP","safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.16013464,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.11716747},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.10818896,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.021990221},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.2158462,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.07682221},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.19636348,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.08021325}]}],"usageMetadata": {"promptTokenCount": 9,"candidatesTokenCount": 53,"totalTokenCount": 62}}

+ 0 - 53
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-finish-reason-safety.json

@@ -1,53 +0,0 @@
-{
-    "candidates": [
-        {
-            "content": {
-                "parts": [
-                    {
-                        "text": "No"
-                    }
-                ]
-            },
-            "finishReason": "SAFETY",
-            "index": 0,
-            "safetyRatings": [
-                {
-                    "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
-                    "probability": "NEGLIGIBLE"
-                },
-                {
-                    "category": "HARM_CATEGORY_HATE_SPEECH",
-                    "probability": "HIGH"
-                },
-                {
-                    "category": "HARM_CATEGORY_HARASSMENT",
-                    "probability": "NEGLIGIBLE"
-                },
-                {
-                    "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
-                    "probability": "NEGLIGIBLE"
-                }
-            ]
-        }
-    ],
-    "promptFeedback": {
-        "safetyRatings": [
-            {
-                "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
-                "probability": "NEGLIGIBLE"
-            },
-            {
-                "category": "HARM_CATEGORY_HATE_SPEECH",
-                "probability": "NEGLIGIBLE"
-            },
-            {
-                "category": "HARM_CATEGORY_HARASSMENT",
-                "probability": "NEGLIGIBLE"
-            },
-            {
-                "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
-                "probability": "NEGLIGIBLE"
-            }
-        ]
-    }
-}

+ 0 - 50
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-success-basic-reply-long.json

@@ -1,50 +0,0 @@
-{
-  "candidates": [
-    {
-      "content": {
-        "role": "model",
-        "parts": [
-          {
-            "text": "You can ask me a wide range of questions, including:\n\n* **General knowledge questions:** What is the capital of France? Who painted the Mona Lisa?\n* **Science questions:** What is the chemical formula for water? How does photosynthesis work?\n* **History questions:** When did World War II start? Who was the first president of the United States?\n* **Math questions:** What is the square root of 16? How do you solve for x in the equation x + 5 = 10?\n* **Current events questions:** What is happening in Ukraine? Who is the current president of the United States?\n* **Personal questions:** What are your hobbies? What are your favorite books?\n* **Hypothetical questions:** What would happen if all the ice caps melted? What if humans could live on Mars?\n* **Questions about me:** What is your name? How old are you? What are your capabilities?\n\n**Tips for asking questions:**\n\n* Be specific and clear in your questions.\n* Use correct grammar and spelling.\n* Try to ask open-ended questions that allow for multiple answers.\n* Be patient and wait for my response. I am still under development, so I may take a few seconds to process your question.\n\nPlease note that I am still under development and may not be able to answer all questions accurately. However, I will do my best to provide you with the most relevant information I have available."
-          }
-        ]
-      },
-      "finishReason": "STOP",
-      "safetyRatings": [
-        {
-          "category": "HARM_CATEGORY_HATE_SPEECH",
-          "probability": "NEGLIGIBLE",
-          "probabilityScore": 0.047869004,
-          "severity": "HARM_SEVERITY_NEGLIGIBLE",
-          "severityScore": 0.050705366
-        },
-        {
-          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
-          "probability": "NEGLIGIBLE",
-          "probabilityScore": 0.052134257,
-          "severity": "HARM_SEVERITY_NEGLIGIBLE",
-          "severityScore": 0.036288295
-        },
-        {
-          "category": "HARM_CATEGORY_HARASSMENT",
-          "probability": "NEGLIGIBLE",
-          "probabilityScore": 0.08464396,
-          "severity": "HARM_SEVERITY_NEGLIGIBLE",
-          "severityScore": 0.033907957
-        },
-        {
-          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
-          "probability": "NEGLIGIBLE",
-          "probabilityScore": 0.06290424,
-          "severity": "HARM_SEVERITY_NEGLIGIBLE",
-          "severityScore": 0.050611436
-        }
-      ]
-    }
-  ],
-  "usageMetadata": {
-    "promptTokenCount": 6,
-    "candidatesTokenCount": 303,
-    "totalTokenCount": 309
-  }
-}

+ 1 - 1
FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift

@@ -16,7 +16,7 @@ import FirebaseVertexAI
 import Foundation
 import XCTest
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 final class GenerationConfigTests: XCTestCase {
   let encoder = JSONEncoder()
 

+ 14 - 18
FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift

@@ -19,7 +19,7 @@ import XCTest
 
 @testable import FirebaseVertexAI
 
-@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, *)
 final class GenerativeModelTests: XCTestCase {
   let testPrompt = "What sorts of questions can I ask you?"
   let safetyRatingsNegligible: [SafetyRating] = [
@@ -71,7 +71,7 @@ final class GenerativeModelTests: XCTestCase {
     XCTAssertEqual(candidate.content.parts.count, 1)
     let part = try XCTUnwrap(candidate.content.parts.first)
     let partText = try XCTUnwrap(part.text)
-    XCTAssertTrue(partText.hasPrefix("You can ask me a wide range of questions"))
+    XCTAssertTrue(partText.hasPrefix("1. **Use Freshly Ground Coffee**:"))
     XCTAssertEqual(response.text, partText)
     XCTAssertEqual(response.functionCalls, [])
   }
@@ -92,7 +92,7 @@ final class GenerativeModelTests: XCTestCase {
     XCTAssertEqual(candidate.safetyRatings.sorted(), safetyRatingsNegligible)
     XCTAssertEqual(candidate.content.parts.count, 1)
     let part = try XCTUnwrap(candidate.content.parts.first)
-    XCTAssertEqual(part.text, "Mountain View, California, United States")
+    XCTAssertEqual(part.text, "Mountain View, California")
     XCTAssertEqual(response.text, part.text)
     XCTAssertEqual(response.functionCalls, [])
   }
@@ -506,7 +506,7 @@ final class GenerativeModelTests: XCTestCase {
       XCTFail("Should throw")
     } catch let GenerateContentError.responseStoppedEarly(reason, response) {
       XCTAssertEqual(reason, .safety)
-      XCTAssertEqual(response.text, "No")
+      XCTAssertEqual(response.text, "<redacted>")
     } catch {
       XCTFail("Should throw a responseStoppedEarly")
     }
@@ -888,7 +888,7 @@ final class GenerativeModelTests: XCTestCase {
       responses += 1
     }
 
-    XCTAssertEqual(responses, 8)
+    XCTAssertEqual(responses, 6)
   }
 
   func testGenerateContentStream_successBasicReplyShort() async throws {
@@ -949,18 +949,14 @@ final class GenerativeModelTests: XCTestCase {
 
     let lastCandidate = try XCTUnwrap(responses.last?.candidates.first)
     XCTAssertEqual(lastCandidate.finishReason, .stop)
-    XCTAssertEqual(citations.count, 3)
+    XCTAssertEqual(citations.count, 6)
     XCTAssertTrue(citations
       .contains(where: {
-        $0.startIndex == 0 && $0.endIndex == 128 && !$0.uri.isEmpty && $0.license == nil
+        $0.startIndex == 574 && $0.endIndex == 705 && !$0.uri.isEmpty && $0.license == ""
       }))
     XCTAssertTrue(citations
       .contains(where: {
-        $0.startIndex == 130 && $0.endIndex == 265 && !$0.uri.isEmpty && $0.license == nil
-      }))
-    XCTAssertTrue(citations
-      .contains(where: {
-        $0.startIndex == 272 && $0.endIndex == 431 && !$0.uri.isEmpty && $0.license == "mit"
+        $0.startIndex == 899 && $0.endIndex == 1026 && !$0.uri.isEmpty && $0.license == ""
       }))
   }
 
@@ -1159,7 +1155,7 @@ final class GenerativeModelTests: XCTestCase {
 
   func testCountTokens_succeeds() async throws {
     MockURLProtocol.requestHandler = try httpRequestHandler(
-      forResource: "success-total-tokens",
+      forResource: "unary-success-total-tokens",
       withExtension: "json"
     )
 
@@ -1171,7 +1167,7 @@ final class GenerativeModelTests: XCTestCase {
 
   func testCountTokens_succeeds_noBillableCharacters() async throws {
     MockURLProtocol.requestHandler = try httpRequestHandler(
-      forResource: "success-no-billable-characters",
+      forResource: "unary-success-no-billable-characters",
       withExtension: "json"
     )
 
@@ -1186,7 +1182,7 @@ final class GenerativeModelTests: XCTestCase {
 
   func testCountTokens_modelNotFound() async throws {
     MockURLProtocol.requestHandler = try httpRequestHandler(
-      forResource: "failure-model-not-found", withExtension: "json",
+      forResource: "unary-failure-model-not-found", withExtension: "json",
       statusCode: 404
     )
 
@@ -1207,7 +1203,7 @@ final class GenerativeModelTests: XCTestCase {
     let expectedTimeout = 150.0
     MockURLProtocol
       .requestHandler = try httpRequestHandler(
-        forResource: "success-total-tokens",
+        forResource: "unary-success-total-tokens",
         withExtension: "json",
         timeout: expectedTimeout
       )
@@ -1347,7 +1343,7 @@ private extension URLRequest {
   }
 }
 
-@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, *)
 class AppCheckInteropFake: NSObject, AppCheckInterop {
   /// The placeholder token value returned when an error occurs
   static let placeholderTokenValue = "placeholder-token"
@@ -1397,7 +1393,7 @@ class AppCheckInteropFake: NSObject, AppCheckInterop {
 
 struct AppCheckErrorFake: Error {}
 
-@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, *)
 extension SafetyRating: Comparable {
   public static func < (lhs: FirebaseVertexAI.SafetyRating,
                         rhs: FirebaseVertexAI.SafetyRating) -> Bool {

+ 1 - 1
FirebaseVertexAI/Tests/Unit/MockURLProtocol.swift

@@ -15,7 +15,7 @@
 import Foundation
 import XCTest
 
-@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, *)
 class MockURLProtocol: URLProtocol {
   static var requestHandler: ((URLRequest) throws -> (
     URLResponse,

+ 1 - 1
FirebaseVertexAI/Tests/Unit/ModelContentTests.swift

@@ -17,7 +17,7 @@ import XCTest
 
 @testable import FirebaseVertexAI
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 final class ModelContentTests: XCTestCase {
   let encoder = JSONEncoder()
 

+ 5 - 4
FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift

@@ -18,11 +18,11 @@ import FirebaseVertexAI
 import XCTest
 #if canImport(UIKit)
   import UIKit
-#else
+#elseif canImport(AppKit)
   import AppKit
 #endif
 
-@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
+@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *)
 final class PartsRepresentableTests: XCTestCase {
   func testModelContentFromCGImageIsNotEmpty() throws {
     // adapted from https://forums.swift.org/t/creating-a-cgimage-from-color-array/18634/2
@@ -73,7 +73,7 @@ final class PartsRepresentableTests: XCTestCase {
     XCTFail("Expected model content from invlaid image to error")
   }
 
-  #if canImport(UIKit)
+  #if canImport(UIKit) && !os(visionOS) // These tests are stalling in CI on visionOS.
     func testModelContentFromInvalidUIImageThrows() throws {
       let image = UIImage()
       do {
@@ -103,7 +103,8 @@ final class PartsRepresentableTests: XCTestCase {
       let modelContent = try image.tryPartsValue()
       XCTAssert(modelContent.count > 0, "Expected non-empty model content for UIImage: \(image)")
     }
-  #else
+
+  #elseif canImport(AppKit)
     func testModelContentFromNSImageIsNotEmpty() throws {
       let coreImage = CIImage(color: CIColor.red)
         .cropped(to: CGRect(origin: CGPointZero, size: CGSize(width: 16, height: 16)))

+ 1 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-failure-empty-content.txt → FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-empty-content.txt

@@ -1 +1,2 @@
 data: {"candidates": [{"content": {},"index": 0}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}
+

+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-failure-error-mid-stream.txt → FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-error-mid-stream.txt


+ 2 - 0
FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-finish-reason-safety.txt

@@ -0,0 +1,2 @@
+data: {"candidates": [{"content": {"parts": [{"text": "<redacted>"}],"role": "model"},"finishReason": "SAFETY","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "HIGH"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}
+

+ 1 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-failure-invalid-json.txt → FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-invalid-json.txt

@@ -1 +1,2 @@
 data: {"this": [{"is": {"not": [{"a": "valid"}]}, "response": {}}]}
+

+ 1 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-failure-malformed-content.txt → FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-malformed-content.txt

@@ -1 +1,2 @@
 data: {"candidates": [{"content": {"missing-parts": true},"index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_TOXICITY","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_VIOLENCE","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_TOXICITY","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_VIOLENCE","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}
+

+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-failure-prompt-blocked-safety.txt → FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-prompt-blocked-safety.txt


+ 6 - 0
FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-recitation-no-content.txt

@@ -0,0 +1,6 @@
+data: {"candidates": [{"content": {"parts": [{"text": "Copyrighted text goes here"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}
+
+data: {"candidates": [{"content": {"parts": [{"text": "More copyrighted text"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "LOW"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 30,"endIndex": 179,"uri": "https://www.example.com","license": ""}]}}]}
+
+data: {"candidates": [{"finishReason": "RECITATION","index": 0}]}
+

+ 1 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-failure-unknown-finish-enum.txt → FirebaseVertexAI/Tests/Unit/Responses/streaming-failure-unknown-finish-enum.txt

@@ -9,3 +9,4 @@ data: {"candidates": [{"content": {"parts": [{"text": ", flexible tails that the
 data: {"candidates": [{"content": {"parts": [{"text": "ls.\n\n- **Health and Care:**\n  - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\n  - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\n  - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\n  - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat's health and detecting any potential health issues early on.\n\n**Dogs:**\n\n- **Physical Characteristics:**\n  - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\n  - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\n  - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\n  - Ears: Dogs have floppy or erect ears that are sensitive to sound.\n  - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\n\n- **Behavior and Personality:**\n  - Loyal: Dogs are known for their loyalty and"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}
 
 data: {"candidates": [{"content": {"parts": [{"text": " devotion to their owners.\n  - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\n  - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\n  - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\n  - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\n\n- **Health and Care:**\n  - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\n  - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\n  - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\n  - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog's health and detecting any potential health issues early on."}]},"finishReason": "FAKE_ENUM","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT_NEW_ENUM","probability": "NEGLIGIBLE_UNKNOWN_ENUM"}]}]}
+

+ 12 - 0
FirebaseVertexAI/Tests/Unit/Responses/streaming-success-basic-reply-long.txt

@@ -0,0 +1,12 @@
+data: {"candidates": [{"content": {"parts": [{"text": "**Cats:**\n\n- **Physical Characteristics:**\n  - Size: Cats come"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}}
+
+data: {"candidates": [{"content": {"parts": [{"text": " in a wide range of sizes, from small breeds like the Singapura to large breeds like the Maine Coon.\n  - Fur: Cats have soft, furry coats"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}
+
+data: {"candidates": [{"content": {"parts": [{"text": " that can vary in length and texture depending on the breed.\n  - Eyes: Cats have large, expressive eyes that can be various colors, including green, blue, yellow, and hazel.\n  - Ears: Cats have pointed, erect ears that are sensitive to sound.\n  - Tail: Cats have long"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}
+
+data: {"candidates": [{"content": {"parts": [{"text": ", flexible tails that they use for balance and communication.\n\n- **Behavior and Personality:**\n  - Independent: Cats are often described as independent animals that enjoy spending time alone.\n  - Affectionate: Despite their independent nature, cats can be very affectionate and form strong bonds with their owners.\n  - Playful: Cats are naturally playful and enjoy engaging in activities such as chasing toys, climbing, and pouncing.\n  - Curious: Cats are curious creatures that love to explore their surroundings.\n  - Vocal: Cats communicate through a variety of vocalizations, including meows, purrs, hisses, and grow"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}
+
+data: {"candidates": [{"content": {"parts": [{"text": "ls.\n\n- **Health and Care:**\n  - Diet: Cats are obligate carnivores, meaning they require animal-based protein for optimal health.\n  - Grooming: Cats spend a significant amount of time grooming themselves to keep their fur clean and free of mats.\n  - Exercise: Cats need regular exercise to stay healthy and active. This can be achieved through play sessions or access to outdoor space.\n  - Veterinary Care: Regular veterinary checkups are essential for maintaining a cat's health and detecting any potential health issues early on.\n\n**Dogs:**\n\n- **Physical Characteristics:**\n  - Size: Dogs come in a wide range of sizes, from small breeds like the Chihuahua to giant breeds like the Great Dane.\n  - Fur: Dogs have fur coats that can vary in length, texture, and color depending on the breed.\n  - Eyes: Dogs have expressive eyes that can be various colors, including brown, blue, green, and hazel.\n  - Ears: Dogs have floppy or erect ears that are sensitive to sound.\n  - Tail: Dogs have long, wagging tails that they use for communication and expressing emotions.\n\n- **Behavior and Personality:**\n  - Loyal: Dogs are known for their loyalty and"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}
+
+data: {"candidates": [{"content": {"parts": [{"text": " devotion to their owners.\n  - Friendly: Dogs are generally friendly and outgoing animals that enjoy interacting with people and other animals.\n  - Playful: Dogs are playful and energetic creatures that love to engage in activities such as fetching, running, and playing with toys.\n  - Trainable: Dogs are highly trainable and can learn a variety of commands and tricks.\n  - Vocal: Dogs communicate through a variety of vocalizations, including barking, howling, whining, and growling.\n\n- **Health and Care:**\n  - Diet: Dogs are omnivores and can eat a variety of foods, including meat, vegetables, and grains.\n  - Grooming: Dogs require regular grooming to keep their fur clean and free of mats. The frequency of grooming depends on the breed and coat type.\n  - Exercise: Dogs need regular exercise to stay healthy and active. The amount of exercise required varies depending on the breed and age of the dog.\n  - Veterinary Care: Regular veterinary checkups are essential for maintaining a dog's health and detecting any potential health issues early on."}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}
+

+ 16 - 15
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-success-basic-reply-long.txt → FirebaseVertexAI/Tests/Unit/Responses/streaming-success-basic-reply-parts.txt

@@ -1,15 +1,16 @@
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": "1 "}]}}]}
-
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": "2 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.0394904,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.04468087},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.034553625,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.03890198},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.09401018,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.025809621},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.036562506,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.047691282}]}]}
-
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": "3 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.03507868,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.045183755},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.027742893,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.043528143},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.08803312,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.026105914},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.06681233,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.053899158}]}]}
-
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": "4 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.037750278,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05089372},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.040087357,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05888469},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.071202725,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.034100424},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.07613248,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.051749535}]}]}
-
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": "5 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.04672496,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.059210256},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.04977345,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05623635},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.083890386,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.03825006},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.08359067,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05975658}]}]}
-
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": "6 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.07779744,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.06052939},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.041930523,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.056756895},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.12787028,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05350215},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.09203286,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.048676573}]}]}
-
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": "7 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.071202725,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05291181},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.031439852,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.04509957},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.11417085,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.04922211},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.09451043,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.052716404}]}]}
-
-data: {"candidates": [{"content": {"role": "model","parts": [{"text": "8"}]},"finishReason": "STOP","safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.06221698,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.045777276},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.03085051,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.04560694},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.0992954,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.040769264},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.100701615,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.061424047}]}],"usageMetadata": {"promptTokenCount": 6,"candidatesTokenCount": 326,"totalTokenCount": 332}}
+data: {"candidates": [{"content": {"role": "model","parts": [{"text": "1 "}]}}]}
+
+data: {"candidates": [{"content": {"role": "model","parts": [{"text": "2 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.0394904,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.04468087},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.034553625,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.03890198},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.09401018,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.025809621},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.036562506,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.047691282}]}]}
+
+data: {"candidates": [{"content": {"role": "model","parts": [{"text": "3 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.03507868,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.045183755},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.027742893,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.043528143},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.08803312,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.026105914},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.06681233,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.053899158}]}]}
+
+data: {"candidates": [{"content": {"role": "model","parts": [{"text": "4 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.037750278,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05089372},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.040087357,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05888469},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.071202725,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.034100424},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.07613248,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.051749535}]}]}
+
+data: {"candidates": [{"content": {"role": "model","parts": [{"text": "5 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.04672496,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.059210256},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.04977345,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05623635},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.083890386,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.03825006},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.08359067,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05975658}]}]}
+
+data: {"candidates": [{"content": {"role": "model","parts": [{"text": "6 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.07779744,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.06052939},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.041930523,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.056756895},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.12787028,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05350215},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.09203286,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.048676573}]}]}
+
+data: {"candidates": [{"content": {"role": "model","parts": [{"text": "7 "}]},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.071202725,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.05291181},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.031439852,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.04509957},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.11417085,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.04922211},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.09451043,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.052716404}]}]}
+
+data: {"candidates": [{"content": {"role": "model","parts": [{"text": "8"}]},"finishReason": "STOP","safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.06221698,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.045777276},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.03085051,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.04560694},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.0992954,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.040769264},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.100701615,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.061424047}]}],"usageMetadata": {"promptTokenCount": 6,"candidatesTokenCount": 326,"totalTokenCount": 332}}
+

+ 2 - 0
FirebaseVertexAI/Tests/Unit/Responses/streaming-success-basic-reply-short.txt

@@ -0,0 +1,2 @@
+data: {"candidates": [{"content": {"role": "model","parts": [{"text": "Cheyenne"}]},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.02854415,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.052424565},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.24926445,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.0996453},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.087096825,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.043123372},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.14402841,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.086169556}]}],"promptFeedback": {"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]},"usageMetadata": {"promptTokenCount": 6,"candidatesTokenCount": 4,"totalTokenCount": 10}}
+

+ 12 - 0
FirebaseVertexAI/Tests/Unit/Responses/streaming-success-citations.txt

@@ -0,0 +1,12 @@
+data: {"candidates": [{"content": {"parts": [{"text": "1. **Definition:**\nQuantum mechanics is a fundamental theory in physics that provides"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}
+
+data: {"candidates": [{"content": {"parts": [{"text": " the foundation for understanding the physical properties of nature at the scale of atoms and subatomic particles. It is based on the idea that energy, momentum, angular momentum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}
+
+data: {"candidates": [{"content": {"parts": [{"text": ", and other quantities are quantized, meaning they can exist only in discrete values. \n\n2. **Key Concepts:**\n   - **Wave-particle Duality:** Particles such as electrons and photons can exhibit both wave-like and particle-like behaviors. \n   - **Uncertainty Principle:** Proposed by Werner"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}]}]}
+
+data: {"candidates": [{"content": {"parts": [{"text": " Heisenberg, it states that the more precisely the position of a particle is known, the less precisely its momentum can be known, and vice versa. \n   - **Superposition:** A quantum system can exist in multiple states simultaneously until it is observed or measured, at which point it \"collapses\" into a single state.\n   - **Quantum Entanglement:** Two or more particles can become correlated in such a way that the state of one particle cannot be described independently of the other, even when they are separated by large distances. \n\n3. **Applications:**\n   - **Quantum Computing:** It explores the use of quantum"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]}
+
+data: {"candidates": [{"content": {"parts": [{"text": "-mechanical phenomena to perform complex calculations and solve problems that are intractable for classical computers. \n   - **Quantum Cryptography:** Utilizes the principles of quantum mechanics to develop secure communication channels. \n   - **Quantum Imaging:** Employs quantum effects to enhance the resolution and sensitivity of imaging techniques in fields such as microscopy and medical imaging. \n   - **Quantum Sensors:** Leverages quantum properties to develop highly sensitive sensors for detecting magnetic fields, accelerations, and other physical quantities. \n\n4. **Learning Resources:**\n   - **Books:**\n     - \"Quantum Mechanics for Mathematicians\" by James Glimm and Arthur Jaffe\n     - \"Principles of Quantum Mechanics\" by R. Shankar\n     - \"Quantum Mechanics: Concepts and Applications\" by Nouredine Zettili\n   - **Online Courses:**\n     - \"Quantum Mechanics I\" by MIT OpenCourseWare\n     - \"Quantum Mechanics for Everyone\" by Coursera\n     - \"Quantum Mechanics\" by Stanford Online\n   - **Documentaries and Videos:**\n     - \"Quantum Mechanics: The Strangest Theory\" (BBC Documentary)\n     - \"Quantum Mechanics Explained Simply\" by Veritasium (YouTube Channel)\n     - \"What is Quantum Mechanics?\" by Minute"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]}
+
+data: {"candidates": [{"content": {"parts": [{"text": "Physics (YouTube Channel)"}],"role": "model"},"finishReason": "STOP","index": 0,"safetyRatings": [{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"}],"citationMetadata": {"citations": [{"startIndex": 574,"endIndex": 705,"uri": "https://www.example.com","license": ""},{"startIndex": 899,"endIndex": 1026,"uri": "https://www.example.com","license": ""}]}}]}
+

+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/streaming-success-unknown-safety-enum.txt → FirebaseVertexAI/Tests/Unit/Responses/streaming-success-unknown-safety-enum.txt


+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-api-key.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-api-key.json


+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-empty-content.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-empty-content.json


+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-finish-reason-safety-no-content.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-finish-reason-safety-no-content.json


+ 54 - 0
FirebaseVertexAI/Tests/Unit/Responses/unary-failure-finish-reason-safety.json

@@ -0,0 +1,54 @@
+{
+  "candidates": [
+    {
+      "content": {
+        "parts": [
+          {
+            "text": "<redacted>"
+          }
+        ],
+        "role": "model"
+      },
+      "finishReason": "SAFETY",
+      "index": 0,
+      "safetyRatings": [
+        {
+          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
+          "probability": "NEGLIGIBLE"
+        },
+        {
+          "category": "HARM_CATEGORY_HATE_SPEECH",
+          "probability": "HIGH"
+        },
+        {
+          "category": "HARM_CATEGORY_HARASSMENT",
+          "probability": "NEGLIGIBLE"
+        },
+        {
+          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
+          "probability": "NEGLIGIBLE"
+        }
+      ]
+    }
+  ],
+  "promptFeedback": {
+    "safetyRatings": [
+      {
+        "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
+        "probability": "NEGLIGIBLE"
+      },
+      {
+        "category": "HARM_CATEGORY_HATE_SPEECH",
+        "probability": "NEGLIGIBLE"
+      },
+      {
+        "category": "HARM_CATEGORY_HARASSMENT",
+        "probability": "NEGLIGIBLE"
+      },
+      {
+        "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
+        "probability": "NEGLIGIBLE"
+      }
+    ]
+  }
+}

+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-firebaseml-api-not-enabled.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-firebaseml-api-not-enabled.json


+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-image-rejected.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-image-rejected.json


+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-invalid-response.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-invalid-response.json


+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-malformed-content.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-malformed-content.json


+ 0 - 0
FirebaseVertexAI/Tests/Unit/CountTokenResponses/failure-model-not-found.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-model-not-found.json


+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-prompt-blocked-safety.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-prompt-blocked-safety.json


+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-unknown-enum-finish-reason.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-unknown-enum-finish-reason.json


+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-unknown-enum-prompt-blocked.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-unknown-enum-prompt-blocked.json


+ 0 - 0
FirebaseVertexAI/Tests/Unit/GenerateContentResponses/unary-failure-unknown-model.json → FirebaseVertexAI/Tests/Unit/Responses/unary-failure-unknown-model.json


File diff suppressed because it is too large
+ 7 - 0
FirebaseVertexAI/Tests/Unit/Responses/unary-success-basic-reply-long.json


Some files were not shown because too many files changed in this diff