فهرست منبع

[Firebase AI] Add initial project structure (#14737)

# Conflicts:
#	FirebaseAI/Tests/Unit/Types/GenerateContentResponseTests.swift
#	FirebaseVertexAI/Sources/VertexAI.swift
Andrew Heard 11 ماه پیش
والد
کامیت
8a854c732b
100فایلهای تغییر یافته به همراه1393 افزوده شده و 83 حذف شده
  1. 164 0
      .github/workflows/firebaseai.yml
  2. 1 39
      .github/workflows/vertexai.yml
  3. 70 0
      FirebaseAI.podspec
  4. 0 0
      FirebaseAI/CHANGELOG.md
  5. 0 0
      FirebaseAI/README.md
  6. 0 0
      FirebaseAI/Sample/README.md
  7. 0 0
      FirebaseAI/Sources/Chat.swift
  8. 0 0
      FirebaseAI/Sources/Constants.swift
  9. 0 0
      FirebaseAI/Sources/Errors.swift
  10. 260 0
      FirebaseAI/Sources/FirebaseAI.swift
  11. 0 0
      FirebaseAI/Sources/FirebaseInfo.swift
  12. 0 0
      FirebaseAI/Sources/FunctionCalling.swift
  13. 0 0
      FirebaseAI/Sources/GenAIURLSession.swift
  14. 0 0
      FirebaseAI/Sources/GenerateContentError.swift
  15. 0 0
      FirebaseAI/Sources/GenerateContentRequest.swift
  16. 0 0
      FirebaseAI/Sources/GenerateContentResponse.swift
  17. 0 0
      FirebaseAI/Sources/GenerationConfig.swift
  18. 0 0
      FirebaseAI/Sources/GenerativeAIRequest.swift
  19. 0 0
      FirebaseAI/Sources/GenerativeAIService.swift
  20. 0 0
      FirebaseAI/Sources/GenerativeModel.swift
  21. 0 0
      FirebaseAI/Sources/JSONValue.swift
  22. 0 0
      FirebaseAI/Sources/ModalityTokenCount.swift
  23. 0 0
      FirebaseAI/Sources/ModelContent.swift
  24. 0 0
      FirebaseAI/Sources/PartsRepresentable+Image.swift
  25. 0 0
      FirebaseAI/Sources/PartsRepresentable.swift
  26. 0 0
      FirebaseAI/Sources/Protocols/Internal/CodableProtoEnum.swift
  27. 0 0
      FirebaseAI/Sources/Safety.swift
  28. 0 0
      FirebaseAI/Sources/Types/Internal/APIConfig.swift
  29. 0 0
      FirebaseAI/Sources/Types/Internal/DataType.swift
  30. 0 0
      FirebaseAI/Sources/Types/Internal/Errors/BackendError.swift
  31. 0 0
      FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationInstance.swift
  32. 0 0
      FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationOutputOptions.swift
  33. 0 0
      FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift
  34. 0 0
      FirebaseAI/Sources/Types/Internal/Imagen/ImagenConstants.swift
  35. 0 0
      FirebaseAI/Sources/Types/Internal/Imagen/ImagenGCSImage.swift
  36. 0 0
      FirebaseAI/Sources/Types/Internal/Imagen/ImagenGenerationRequest.swift
  37. 0 0
      FirebaseAI/Sources/Types/Internal/Imagen/ImagenImageRepresentable.swift
  38. 0 0
      FirebaseAI/Sources/Types/Internal/Imagen/InternalImagenImage.swift
  39. 0 0
      FirebaseAI/Sources/Types/Internal/Imagen/RAIFilteredReason.swift
  40. 0 0
      FirebaseAI/Sources/Types/Internal/InternalPart.swift
  41. 0 0
      FirebaseAI/Sources/Types/Internal/ProtoDate.swift
  42. 0 0
      FirebaseAI/Sources/Types/Internal/Requests/CountTokensRequest.swift
  43. 0 0
      FirebaseAI/Sources/Types/Public/Imagen/ImagenAspectRatio.swift
  44. 0 0
      FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationConfig.swift
  45. 0 0
      FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift
  46. 0 0
      FirebaseAI/Sources/Types/Public/Imagen/ImagenImageFormat.swift
  47. 0 0
      FirebaseAI/Sources/Types/Public/Imagen/ImagenImagesBlockedError.swift
  48. 0 0
      FirebaseAI/Sources/Types/Public/Imagen/ImagenInlineImage.swift
  49. 0 0
      FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift
  50. 0 0
      FirebaseAI/Sources/Types/Public/Imagen/ImagenPersonFilterLevel.swift
  51. 0 0
      FirebaseAI/Sources/Types/Public/Imagen/ImagenSafetyFilterLevel.swift
  52. 0 0
      FirebaseAI/Sources/Types/Public/Imagen/ImagenSafetySettings.swift
  53. 0 0
      FirebaseAI/Sources/Types/Public/Part.swift
  54. 0 0
      FirebaseAI/Sources/Types/Public/ResponseModality.swift
  55. 0 0
      FirebaseAI/Sources/Types/Public/Schema.swift
  56. 0 0
      FirebaseAI/Sources/VertexLog.swift
  57. 0 0
      FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
  58. 0 0
      FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
  59. 0 0
      FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/Contents.json
  60. 0 0
      FirebaseAI/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json
  61. 0 0
      FirebaseAI/Tests/TestApp/Resources/TestApp.entitlements
  62. 0 0
      FirebaseAI/Tests/TestApp/Sources/Constants.swift
  63. 0 0
      FirebaseAI/Tests/TestApp/Sources/ContentView.swift
  64. 0 0
      FirebaseAI/Tests/TestApp/Sources/FirebaseAppUtils.swift
  65. 0 0
      FirebaseAI/Tests/TestApp/Sources/TestApp.swift
  66. 0 0
      FirebaseAI/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift
  67. 6 6
      FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift
  68. 7 7
      FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift
  69. 4 4
      FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift
  70. 4 4
      FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift
  71. 6 6
      FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift
  72. 0 0
      FirebaseAI/Tests/TestApp/Tests/Integration/TestHelpers.swift
  73. 6 6
      FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift
  74. 0 0
      FirebaseAI/Tests/TestApp/Tests/Utilities/IntegrationTestUtils.swift
  75. 0 0
      FirebaseAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj
  76. 213 0
      FirebaseAI/Tests/Unit/APITests.swift
  77. 3 3
      FirebaseAI/Tests/Unit/ChatTests.swift
  78. 0 0
      FirebaseAI/Tests/Unit/Fakes/AuthInteropFake.swift
  79. 1 1
      FirebaseAI/Tests/Unit/GenerationConfigTests.swift
  80. 2 2
      FirebaseAI/Tests/Unit/GenerativeModelTests.swift
  81. 1 1
      FirebaseAI/Tests/Unit/JSONValueTests.swift
  82. 0 0
      FirebaseAI/Tests/Unit/MockURLProtocol.swift
  83. 1 1
      FirebaseAI/Tests/Unit/PartTests.swift
  84. 1 1
      FirebaseAI/Tests/Unit/PartsRepresentableTests.swift
  85. 0 0
      FirebaseAI/Tests/Unit/README.md
  86. 1 1
      FirebaseAI/Tests/Unit/RequestOptionsTest.swift
  87. BIN
      FirebaseAI/Tests/Unit/Resources/animals.mp4
  88. BIN
      FirebaseAI/Tests/Unit/Resources/blue.png
  89. BIN
      FirebaseAI/Tests/Unit/Resources/gemini-report.pdf
  90. BIN
      FirebaseAI/Tests/Unit/Resources/hello-world.mp3
  91. 67 0
      FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift
  92. 1 1
      FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift
  93. 57 0
      FirebaseAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift
  94. 110 0
      FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift
  95. 215 0
      FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift
  96. 10 0
      FirebaseAI/Tests/Unit/Snippets/README.md
  97. 96 0
      FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift
  98. 55 0
      FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift
  99. 31 0
      FirebaseAI/Tests/Unit/TestUtilities/BundleTestUtil.swift
  100. 0 0
      FirebaseAI/Tests/Unit/Types/GenerateContentResponseTests.swift

+ 164 - 0
.github/workflows/firebaseai.yml

@@ -0,0 +1,164 @@
+name: firebaseai
+
+on:
+  pull_request:
+    paths:
+    - 'FirebaseAI**'
+    - '.github/workflows/firebaseai.yml'
+    - 'Gemfile*'
+  schedule:
+    # Run every day at 11pm (PST) - cron uses UTC times
+    - cron:  '0 7 * * *'
+  workflow_dispatch:
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read  # Needed for actions/checkout
+  actions: write # Needed for actions/cache (save and restore)
+
+jobs:
+  spm-package-resolved:
+    runs-on: macos-14
+    outputs:
+      cache_key: ${{ steps.generate_cache_key.outputs.cache_key }}
+    env:
+      FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
+    steps:
+      - uses: actions/checkout@v4
+      - name: Generate Swift Package.resolved
+        id: swift_package_resolve
+        run: |
+          swift package resolve
+      - name: Generate cache key
+        id: generate_cache_key
+        run: |
+          cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}"
+          echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT"
+      - uses: actions/cache/save@v4
+        id: cache
+        with:
+          path: .build
+          key: ${{ steps.generate_cache_key.outputs.cache_key }}
+
+  spm-unit:
+    strategy:
+      matrix:
+        include:
+          - os: macos-14
+            xcode: Xcode_16.2
+            target: iOS
+          - os: macos-15
+            xcode: Xcode_16.3
+            target: iOS
+          - os: macos-15
+            xcode: Xcode_16.3
+            target: tvOS
+          - os: macos-15
+            xcode: Xcode_16.3
+            target: macOS
+          - os: macos-15
+            xcode: Xcode_16.3
+            target: watchOS
+          - os: macos-15
+            xcode: Xcode_16.3
+            target: catalyst
+          - os: macos-15
+            xcode: Xcode_16.3
+            target: visionOS
+    runs-on: ${{ matrix.os }}
+    needs: spm-package-resolved
+    env:
+      FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
+    steps:
+    - uses: actions/checkout@v4
+    - uses: actions/cache/restore@v4
+      with:
+        path: .build
+        key: ${{needs.spm-package-resolved.outputs.cache_key}}
+    - name: Clone mock responses
+      run: scripts/update_vertexai_responses.sh
+    - name: Xcode
+      run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer
+    - name: Install visionOS, if needed.
+      if: matrix.target == 'visionOS'
+      run: xcodebuild -downloadPlatform visionOS
+    - name: Initialize xcodebuild
+      run: scripts/setup_spm_tests.sh
+    - uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3
+      with:
+        timeout_minutes: 120
+        max_attempts: 3
+        retry_on: error
+        retry_wait_seconds: 120
+        command: scripts/build.sh FirebaseAIUnit ${{ matrix.target }} spm
+
+  testapp-integration:
+    strategy:
+      matrix:
+        target: [iOS]
+        os: [macos-15]
+        include:
+          - os: macos-15
+            xcode: Xcode_16.3
+    runs-on: ${{ matrix.os }}
+    needs: spm-package-resolved
+    env:
+      TEST_RUNNER_FIRAAppCheckDebugToken: ${{ secrets.VERTEXAI_INTEGRATION_FAC_DEBUG_TOKEN }}
+      TEST_RUNNER_VTXIntegrationImagen: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
+      FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
+      secrets_passphrase: ${{ secrets.GHASecretsGPGPassphrase1 }}
+    steps:
+    - uses: actions/checkout@v4
+    - uses: actions/cache/restore@v4
+      with:
+        path: .build
+        key: ${{needs.spm-package-resolved.outputs.cache_key}}
+    - name: Install Secret GoogleService-Info.plist
+      run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-GoogleService-Info.plist.gpg \
+        FirebaseAI/Tests/TestApp/Resources/GoogleService-Info.plist "$secrets_passphrase"
+    - name: Install Secret GoogleService-Info-Spark.plist
+      run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-GoogleService-Info-Spark.plist.gpg \
+        FirebaseAI/Tests/TestApp/Resources/GoogleService-Info-Spark.plist "$secrets_passphrase"
+    - name: Install Secret Credentials.swift
+      run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-Credentials.swift.gpg \
+        FirebaseAI/Tests/TestApp/Tests/Integration/Credentials.swift "$secrets_passphrase"
+    - name: Xcode
+      run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer
+    - name: Run IntegrationTests
+      run: scripts/build.sh FirebaseAIIntegration ${{ matrix.target }}
+
+  pod-lib-lint:
+    # Don't run on private repo unless it is a PR.
+    if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request'
+    strategy:
+      matrix:
+        include:
+          - os: macos-14
+            xcode: Xcode_16.2
+            swift_version: 5.9
+            warnings:
+          - os: macos-15
+            xcode: Xcode_16.3
+            swift_version: 5.9
+            warnings:
+          - os: macos-15
+            xcode: Xcode_16.3
+            swift_version: 6.0
+            warnings:
+    runs-on: ${{ matrix.os }}
+    steps:
+    - uses: actions/checkout@v4
+    - name: Clone mock responses
+      run: scripts/update_vertexai_responses.sh
+    - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1
+    - name: Setup Bundler
+      run: scripts/setup_bundler.sh
+    - name: Xcode
+      run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer
+    - name: Set Swift swift_version
+      run: sed -i "" "s#s.swift_version = '5.9'#s.swift_version = '${{ matrix.swift_version}}'#" FirebaseAI.podspec
+    - name: Build and test
+      run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseAI.podspec --platforms=${{ matrix.target }} ${{ matrix.warnings }}

+ 1 - 39
.github/workflows/vertexai.yml

@@ -3,6 +3,7 @@ name: vertexai
 on:
   pull_request:
     paths:
+    - 'FirebaseAI**'
     - 'FirebaseVertexAI**'
     - '.github/workflows/vertexai.yml'
     - 'Gemfile*'
@@ -74,8 +75,6 @@ jobs:
       with:
         path: .build
         key: ${{needs.spm-package-resolved.outputs.cache_key}}
-    - name: Clone mock responses
-      run: scripts/update_vertexai_responses.sh
     - name: Xcode
       run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer
     - name: Install visionOS, if needed.
@@ -91,41 +90,6 @@ jobs:
         retry_wait_seconds: 120
         command: scripts/build.sh FirebaseVertexAIUnit ${{ matrix.target }} spm
 
-  testapp-integration:
-    strategy:
-      matrix:
-        target: [iOS]
-        os: [macos-15]
-        include:
-          - os: macos-15
-            xcode: Xcode_16.3
-    runs-on: ${{ matrix.os }}
-    needs: spm-package-resolved
-    env:
-      TEST_RUNNER_FIRAAppCheckDebugToken: ${{ secrets.VERTEXAI_INTEGRATION_FAC_DEBUG_TOKEN }}
-      TEST_RUNNER_VTXIntegrationImagen: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
-      FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
-      secrets_passphrase: ${{ secrets.GHASecretsGPGPassphrase1 }}
-    steps:
-    - uses: actions/checkout@v4
-    - uses: actions/cache/restore@v4
-      with:
-        path: .build
-        key: ${{needs.spm-package-resolved.outputs.cache_key}}
-    - name: Install Secret GoogleService-Info.plist
-      run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-GoogleService-Info.plist.gpg \
-        FirebaseVertexAI/Tests/TestApp/Resources/GoogleService-Info.plist "$secrets_passphrase"
-    - name: Install Secret GoogleService-Info-Spark.plist
-      run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-GoogleService-Info-Spark.plist.gpg \
-        FirebaseVertexAI/Tests/TestApp/Resources/GoogleService-Info-Spark.plist "$secrets_passphrase"
-    - name: Install Secret Credentials.swift
-      run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-Credentials.swift.gpg \
-        FirebaseVertexAI/Tests/TestApp/Tests/Integration/Credentials.swift "$secrets_passphrase"
-    - name: Xcode
-      run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer
-    - name: Run IntegrationTests
-      run: scripts/build.sh VertexIntegration ${{ matrix.target }}
-
   pod-lib-lint:
     # Don't run on private repo unless it is a PR.
     if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request'
@@ -147,8 +111,6 @@ jobs:
     runs-on: ${{ matrix.os }}
     steps:
     - uses: actions/checkout@v4
-    - name: Clone mock responses
-      run: scripts/update_vertexai_responses.sh
     - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1
     - name: Setup Bundler
       run: scripts/setup_bundler.sh

+ 70 - 0
FirebaseAI.podspec

@@ -0,0 +1,70 @@
+Pod::Spec.new do |s|
+  s.name             = 'FirebaseAI'
+  s.version          = '11.13.0'
+  s.summary          = 'Firebase AI SDK'
+
+  s.description      = <<-DESC
+Build AI-powered apps and features with the Gemini API using the Firebase AI SDK.
+                       DESC
+
+  s.homepage         = 'https://firebase.google.com'
+  s.license          = { :type => 'Apache-2.0', :file => 'LICENSE' }
+  s.authors          = 'Google, Inc.'
+
+  s.source           = {
+    :git => 'https://github.com/firebase/firebase-ios-sdk.git',
+    :tag => 'CocoaPods-' + s.version.to_s
+  }
+
+  s.social_media_url = 'https://twitter.com/Firebase'
+
+  ios_deployment_target = '15.0'
+  osx_deployment_target = '12.0'
+  tvos_deployment_target = '15.0'
+  watchos_deployment_target = '8.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.cocoapods_version = '>= 1.12.0'
+  s.prefix_header_file = false
+
+  s.source_files = [
+    'FirebaseAI/Sources/**/*.swift',
+  ]
+
+  s.swift_version = '5.9'
+
+  s.framework = 'Foundation'
+  s.ios.framework = 'UIKit'
+  s.osx.framework = 'AppKit'
+  s.tvos.framework = 'UIKit'
+  s.watchos.framework = 'WatchKit'
+
+  s.dependency 'FirebaseAppCheckInterop', '~> 11.4'
+  s.dependency 'FirebaseAuthInterop', '~> 11.4'
+  s.dependency 'FirebaseCore', '~> 11.13.0'
+  s.dependency 'FirebaseCoreExtension', '~> 11.13.0'
+
+  s.test_spec 'unit' do |unit_tests|
+    unit_tests_dir = 'FirebaseAI/Tests/Unit/'
+    unit_tests.scheme = { :code_coverage => true }
+    unit_tests.platforms = {
+      :ios => ios_deployment_target,
+      :osx => osx_deployment_target,
+      :tvos => tvos_deployment_target
+    }
+    unit_tests.source_files = [
+      unit_tests_dir + '**/*.swift',
+    ]
+    unit_tests.exclude_files = [
+      unit_tests_dir + 'Snippets/**/*.swift',
+    ]
+    unit_tests.resources = [
+      unit_tests_dir + 'vertexai-sdk-test-data/mock-responses/vertexai',
+      unit_tests_dir + 'Resources/**/*',
+    ]
+  end
+end

+ 0 - 0
FirebaseVertexAI/CHANGELOG.md → FirebaseAI/CHANGELOG.md


+ 0 - 0
FirebaseVertexAI/README.md → FirebaseAI/README.md


+ 0 - 0
FirebaseVertexAI/Sample/README.md → FirebaseAI/Sample/README.md


+ 0 - 0
FirebaseVertexAI/Sources/Chat.swift → FirebaseAI/Sources/Chat.swift


+ 0 - 0
FirebaseVertexAI/Sources/Constants.swift → FirebaseAI/Sources/Constants.swift


+ 0 - 0
FirebaseVertexAI/Sources/Errors.swift → FirebaseAI/Sources/Errors.swift


+ 260 - 0
FirebaseAI/Sources/FirebaseAI.swift

@@ -0,0 +1,260 @@
+// 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 FirebaseAppCheckInterop
+import FirebaseAuthInterop
+import FirebaseCore
+import Foundation
+
+// Avoids exposing internal FirebaseCore APIs to Swift users.
+internal import FirebaseCoreExtension
+
+/// The Vertex AI for Firebase SDK provides access to Gemini models directly from your app.
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
+public final class FirebaseAI: Sendable {
+  // MARK: - Public APIs
+
+  /// Creates an instance of `VertexAI`.
+  ///
+  ///  - Parameters:
+  ///   - app: A custom `FirebaseApp` used for initialization; if not specified, uses the default
+  ///     ``FirebaseApp``.
+  ///   - location: The region identifier, defaulting to `us-central1`; see
+  ///     [Vertex AI locations]
+  ///     (https://firebase.google.com/docs/vertex-ai/locations?platform=ios#available-locations)
+  ///     for a list of supported locations.
+  /// - Returns: A `VertexAI` instance, configured with the custom `FirebaseApp`.
+  public static func vertexAI(app: FirebaseApp? = nil,
+                              location: String = "us-central1") -> FirebaseAI {
+    let vertexInstance = vertexAI(app: app, location: location, apiConfig: defaultVertexAIAPIConfig)
+    // Verify that the `VertexAI` instance is always configured with the production endpoint since
+    // this is the public API surface for creating an instance.
+    assert(vertexInstance.apiConfig.service == .vertexAI(endpoint: .firebaseVertexAIProd))
+    assert(vertexInstance.apiConfig.service.endpoint == .firebaseVertexAIProd)
+    assert(vertexInstance.apiConfig.version == .v1beta)
+
+    return vertexInstance
+  }
+
+  /// Initializes a generative model with the given parameters.
+  ///
+  /// - Note: Refer to [Gemini models](https://firebase.google.com/docs/vertex-ai/gemini-models) for
+  /// 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"`; 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.
+  ///   - safetySettings: A value describing what types of harmful content your model should allow.
+  ///   - tools: A list of ``Tool`` objects that the model may use to generate the next response.
+  ///   - toolConfig: Tool configuration for any `Tool` specified in the request.
+  ///   - systemInstruction: Instructions that direct the model to behave a certain way; currently
+  ///     only text content is supported.
+  ///   - requestOptions: Configuration parameters for sending requests to the backend.
+  public func generativeModel(modelName: String,
+                              generationConfig: GenerationConfig? = nil,
+                              safetySettings: [SafetySetting]? = nil,
+                              tools: [Tool]? = nil,
+                              toolConfig: ToolConfig? = nil,
+                              systemInstruction: ModelContent? = nil,
+                              requestOptions: RequestOptions = RequestOptions())
+    -> GenerativeModel {
+    if !modelName.starts(with: GenerativeModel.geminiModelNamePrefix) {
+      VertexLog.warning(code: .unsupportedGeminiModel, """
+      Unsupported Gemini model "\(modelName)"; see \
+      https://firebase.google.com/docs/vertex-ai/models for a list supported Gemini model names.
+      """)
+    }
+
+    return GenerativeModel(
+      modelName: modelName,
+      modelResourceName: modelResourceName(modelName: modelName),
+      firebaseInfo: firebaseInfo,
+      apiConfig: apiConfig,
+      generationConfig: generationConfig,
+      safetySettings: safetySettings,
+      tools: tools,
+      toolConfig: toolConfig,
+      systemInstruction: systemInstruction,
+      requestOptions: requestOptions
+    )
+  }
+
+  /// **[Public Preview]** Initializes an ``ImagenModel`` with the given parameters.
+  ///
+  /// > Warning: For Vertex AI in Firebase, image generation using Imagen 3 models is in Public
+  /// Preview, which means that the feature is not subject to any SLA or deprecation policy and
+  /// could change in backwards-incompatible ways.
+  ///
+  /// > Important: Only Imagen 3 models (named `imagen-3.0-*`) are supported.
+  ///
+  /// - Parameters:
+  ///   - modelName: The name of the Imagen 3 model to use, for example `"imagen-3.0-generate-002"`;
+  ///     see [model versions](https://firebase.google.com/docs/vertex-ai/models) for a list of
+  ///     supported Imagen 3 models.
+  ///   - generationConfig: Configuration options for generating images with Imagen.
+  ///   - safetySettings: Settings describing what types of potentially harmful content your model
+  ///     should allow.
+  ///   - requestOptions: Configuration parameters for sending requests to the backend.
+  public func imagenModel(modelName: String, generationConfig: ImagenGenerationConfig? = nil,
+                          safetySettings: ImagenSafetySettings? = nil,
+                          requestOptions: RequestOptions = RequestOptions()) -> ImagenModel {
+    if !modelName.starts(with: ImagenModel.imagenModelNamePrefix) {
+      VertexLog.warning(code: .unsupportedImagenModel, """
+      Unsupported Imagen model "\(modelName)"; see \
+      https://firebase.google.com/docs/vertex-ai/models for a list supported Imagen model names.
+      """)
+    }
+
+    return ImagenModel(
+      modelResourceName: modelResourceName(modelName: modelName),
+      firebaseInfo: firebaseInfo,
+      apiConfig: apiConfig,
+      generationConfig: generationConfig,
+      safetySettings: safetySettings,
+      requestOptions: requestOptions
+    )
+  }
+
+  /// Class to enable VertexAI to register via the Objective-C based Firebase component system
+  /// to include VertexAI in the userAgent.
+  @objc(FIRVertexAIComponent) class FirebaseVertexAIComponent: NSObject {}
+
+  // MARK: - Private
+
+  /// Firebase data relevant to Vertex AI.
+  let firebaseInfo: FirebaseInfo
+
+  let apiConfig: APIConfig
+
+  #if compiler(>=6)
+    /// A map of active  `VertexAI` instances keyed by the `FirebaseApp` name and the `location`, in
+    /// the format `appName:location`.
+    private nonisolated(unsafe) static var instances: [InstanceKey: FirebaseAI] = [:]
+
+    /// Lock to manage access to the `instances` array to avoid race conditions.
+    private nonisolated(unsafe) static var instancesLock: os_unfair_lock = .init()
+  #else
+    /// A map of active  `VertexAI` instances keyed by the `FirebaseApp` name and the `location`, in
+    /// the format `appName:location`.
+    private static var instances: [InstanceKey: VertexAI] = [:]
+
+    /// Lock to manage access to the `instances` array to avoid race conditions.
+    private static var instancesLock: os_unfair_lock = .init()
+  #endif
+
+  let location: String?
+
+  static let defaultVertexAIAPIConfig = APIConfig(
+    service: .vertexAI(endpoint: .firebaseVertexAIProd),
+    version: .v1beta
+  )
+
+  static func vertexAI(app: FirebaseApp?, location: String?, apiConfig: APIConfig) -> FirebaseAI {
+    guard let app = app ?? FirebaseApp.app() else {
+      fatalError("No instance of the default Firebase app was found.")
+    }
+
+    os_unfair_lock_lock(&instancesLock)
+
+    // Unlock before the function returns.
+    defer { os_unfair_lock_unlock(&instancesLock) }
+
+    let instanceKey = InstanceKey(appName: app.name, location: location, apiConfig: apiConfig)
+    if let instance = instances[instanceKey] {
+      return instance
+    }
+    let newInstance = FirebaseAI(app: app, location: location, apiConfig: apiConfig)
+    instances[instanceKey] = newInstance
+    return newInstance
+  }
+
+  init(app: FirebaseApp, location: String?, apiConfig: APIConfig) {
+    guard let projectID = app.options.projectID else {
+      fatalError("The Firebase app named \"\(app.name)\" has no project ID in its configuration.")
+    }
+    guard let apiKey = app.options.apiKey else {
+      fatalError("The Firebase app named \"\(app.name)\" has no API key in its configuration.")
+    }
+    firebaseInfo = FirebaseInfo(
+      appCheck: ComponentType<AppCheckInterop>.instance(
+        for: AppCheckInterop.self,
+        in: app.container
+      ),
+      auth: ComponentType<AuthInterop>.instance(for: AuthInterop.self, in: app.container),
+      projectID: projectID,
+      apiKey: apiKey,
+      firebaseAppID: app.options.googleAppID,
+      firebaseApp: app
+    )
+    self.apiConfig = apiConfig
+    self.location = location
+  }
+
+  func modelResourceName(modelName: String) -> String {
+    guard !modelName.isEmpty && modelName
+      .allSatisfy({ !$0.isWhitespace && !$0.isNewline && $0 != "/" }) else {
+      fatalError("""
+      Invalid model name "\(modelName)" specified; see \
+      https://firebase.google.com/docs/vertex-ai/gemini-model#available-models for a list of \
+      available models.
+      """)
+    }
+
+    switch apiConfig.service {
+    case .vertexAI:
+      return vertexAIModelResourceName(modelName: modelName)
+    case .developer:
+      return developerModelResourceName(modelName: modelName)
+    }
+  }
+
+  private func vertexAIModelResourceName(modelName: String) -> String {
+    guard let location else {
+      fatalError("Location must be specified for the Vertex AI service.")
+    }
+    guard !location.isEmpty && location
+      .allSatisfy({ !$0.isWhitespace && !$0.isNewline && $0 != "/" }) else {
+      fatalError("""
+      Invalid location "\(location)" specified; see \
+      https://firebase.google.com/docs/vertex-ai/locations?platform=ios#available-locations \
+      for a list of available locations.
+      """)
+    }
+
+    let projectID = firebaseInfo.projectID
+    return "projects/\(projectID)/locations/\(location)/publishers/google/models/\(modelName)"
+  }
+
+  private func developerModelResourceName(modelName: String) -> String {
+    switch apiConfig.service.endpoint {
+    case .firebaseVertexAIStaging, .firebaseVertexAIProd:
+      let projectID = firebaseInfo.projectID
+      return "projects/\(projectID)/models/\(modelName)"
+    case .generativeLanguage:
+      return "models/\(modelName)"
+    }
+  }
+
+  /// Identifier for a unique instance of ``VertexAI``.
+  ///
+  /// This type is `Hashable` so that it can be used as a key in the `instances` dictionary.
+  private struct InstanceKey: Sendable, Hashable {
+    let appName: String
+    let location: String?
+    let apiConfig: APIConfig
+  }
+}

+ 0 - 0
FirebaseVertexAI/Sources/FirebaseInfo.swift → FirebaseAI/Sources/FirebaseInfo.swift


+ 0 - 0
FirebaseVertexAI/Sources/FunctionCalling.swift → FirebaseAI/Sources/FunctionCalling.swift


+ 0 - 0
FirebaseVertexAI/Sources/GenAIURLSession.swift → FirebaseAI/Sources/GenAIURLSession.swift


+ 0 - 0
FirebaseVertexAI/Sources/GenerateContentError.swift → FirebaseAI/Sources/GenerateContentError.swift


+ 0 - 0
FirebaseVertexAI/Sources/GenerateContentRequest.swift → FirebaseAI/Sources/GenerateContentRequest.swift


+ 0 - 0
FirebaseVertexAI/Sources/GenerateContentResponse.swift → FirebaseAI/Sources/GenerateContentResponse.swift


+ 0 - 0
FirebaseVertexAI/Sources/GenerationConfig.swift → FirebaseAI/Sources/GenerationConfig.swift


+ 0 - 0
FirebaseVertexAI/Sources/GenerativeAIRequest.swift → FirebaseAI/Sources/GenerativeAIRequest.swift


+ 0 - 0
FirebaseVertexAI/Sources/GenerativeAIService.swift → FirebaseAI/Sources/GenerativeAIService.swift


+ 0 - 0
FirebaseVertexAI/Sources/GenerativeModel.swift → FirebaseAI/Sources/GenerativeModel.swift


+ 0 - 0
FirebaseVertexAI/Sources/JSONValue.swift → FirebaseAI/Sources/JSONValue.swift


+ 0 - 0
FirebaseVertexAI/Sources/ModalityTokenCount.swift → FirebaseAI/Sources/ModalityTokenCount.swift


+ 0 - 0
FirebaseVertexAI/Sources/ModelContent.swift → FirebaseAI/Sources/ModelContent.swift


+ 0 - 0
FirebaseVertexAI/Sources/PartsRepresentable+Image.swift → FirebaseAI/Sources/PartsRepresentable+Image.swift


+ 0 - 0
FirebaseVertexAI/Sources/PartsRepresentable.swift → FirebaseAI/Sources/PartsRepresentable.swift


+ 0 - 0
FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift → FirebaseAI/Sources/Protocols/Internal/CodableProtoEnum.swift


+ 0 - 0
FirebaseVertexAI/Sources/Safety.swift → FirebaseAI/Sources/Safety.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/APIConfig.swift → FirebaseAI/Sources/Types/Internal/APIConfig.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/DataType.swift → FirebaseAI/Sources/Types/Internal/DataType.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/Errors/BackendError.swift → FirebaseAI/Sources/Types/Internal/Errors/BackendError.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/Imagen/ImageGenerationInstance.swift → FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationInstance.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/Imagen/ImageGenerationOutputOptions.swift → FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationOutputOptions.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift → FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenConstants.swift → FirebaseAI/Sources/Types/Internal/Imagen/ImagenConstants.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenGCSImage.swift → FirebaseAI/Sources/Types/Internal/Imagen/ImagenGCSImage.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenGenerationRequest.swift → FirebaseAI/Sources/Types/Internal/Imagen/ImagenGenerationRequest.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenImageRepresentable.swift → FirebaseAI/Sources/Types/Internal/Imagen/ImagenImageRepresentable.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/Imagen/InternalImagenImage.swift → FirebaseAI/Sources/Types/Internal/Imagen/InternalImagenImage.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/Imagen/RAIFilteredReason.swift → FirebaseAI/Sources/Types/Internal/Imagen/RAIFilteredReason.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift → FirebaseAI/Sources/Types/Internal/InternalPart.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/ProtoDate.swift → FirebaseAI/Sources/Types/Internal/ProtoDate.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Internal/Requests/CountTokensRequest.swift → FirebaseAI/Sources/Types/Internal/Requests/CountTokensRequest.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenAspectRatio.swift → FirebaseAI/Sources/Types/Public/Imagen/ImagenAspectRatio.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenGenerationConfig.swift → FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationConfig.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift → FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenImageFormat.swift → FirebaseAI/Sources/Types/Public/Imagen/ImagenImageFormat.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenImagesBlockedError.swift → FirebaseAI/Sources/Types/Public/Imagen/ImagenImagesBlockedError.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenInlineImage.swift → FirebaseAI/Sources/Types/Public/Imagen/ImagenInlineImage.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift → FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenPersonFilterLevel.swift → FirebaseAI/Sources/Types/Public/Imagen/ImagenPersonFilterLevel.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenSafetyFilterLevel.swift → FirebaseAI/Sources/Types/Public/Imagen/ImagenSafetyFilterLevel.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenSafetySettings.swift → FirebaseAI/Sources/Types/Public/Imagen/ImagenSafetySettings.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Part.swift → FirebaseAI/Sources/Types/Public/Part.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/ResponseModality.swift → FirebaseAI/Sources/Types/Public/ResponseModality.swift


+ 0 - 0
FirebaseVertexAI/Sources/Types/Public/Schema.swift → FirebaseAI/Sources/Types/Public/Schema.swift


+ 0 - 0
FirebaseVertexAI/Sources/VertexLog.swift → FirebaseAI/Sources/VertexLog.swift


+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json → FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json


+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json → FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json


+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/Contents.json → FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/Contents.json


+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json → FirebaseAI/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json


+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Resources/TestApp.entitlements → FirebaseAI/Tests/TestApp/Resources/TestApp.entitlements


+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Sources/Constants.swift → FirebaseAI/Tests/TestApp/Sources/Constants.swift


+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Sources/ContentView.swift → FirebaseAI/Tests/TestApp/Sources/ContentView.swift


+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Sources/FirebaseAppUtils.swift → FirebaseAI/Tests/TestApp/Sources/FirebaseAppUtils.swift


+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift → FirebaseAI/Tests/TestApp/Sources/TestApp.swift


+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift → FirebaseAI/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift


+ 6 - 6
FirebaseVertexAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift → FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift

@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import FirebaseAI
 import FirebaseAuth
 import FirebaseCore
 import FirebaseStorage
-import FirebaseVertexAI
 import Testing
 import VertexAITestApp
 
-@testable import struct FirebaseVertexAI.APIConfig
+@testable import struct FirebaseAI.APIConfig
 
 @Suite(.serialized)
 struct CountTokensIntegrationTests {
@@ -48,7 +48,7 @@ struct CountTokensIntegrationTests {
   @Test(arguments: InstanceConfig.allConfigs)
   func countTokens_text(_ config: InstanceConfig) async throws {
     let prompt = "Why is the sky blue?"
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2Flash,
       generationConfig: generationConfig,
       safetySettings: safetySettings
@@ -74,7 +74,7 @@ struct CountTokensIntegrationTests {
     arguments: InstanceConfig.allConfigsExceptDeveloperV1
   )
   func countTokens_text_systemInstruction(_ config: InstanceConfig) async throws {
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2Flash,
       generationConfig: generationConfig,
       safetySettings: safetySettings,
@@ -101,7 +101,7 @@ struct CountTokensIntegrationTests {
     InstanceConfig.developerV1Spark,
   ])
   func countTokens_text_systemInstruction_unsupported(_ config: InstanceConfig) async throws {
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2Flash,
       systemInstruction: systemInstruction // Not supported on the v1 Developer API
     )
@@ -123,7 +123,7 @@ struct CountTokensIntegrationTests {
     arguments: InstanceConfig.allConfigsExceptDeveloperV1
   )
   func countTokens_jsonSchema(_ config: InstanceConfig) async throws {
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2Flash,
       generationConfig: GenerationConfig(
         responseMIMEType: "application/json",

+ 7 - 7
FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift → FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift

@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import FirebaseAI
 import FirebaseAuth
 import FirebaseCore
 import FirebaseStorage
-import FirebaseVertexAI
 import Testing
 import VertexAITestApp
 
@@ -23,7 +23,7 @@ import VertexAITestApp
   import UIKit
 #endif // canImport(UIKit)
 
-@testable import struct FirebaseVertexAI.BackendError
+@testable import struct FirebaseAI.BackendError
 
 @Suite(.serialized)
 struct GenerateContentIntegrationTests {
@@ -49,7 +49,7 @@ struct GenerateContentIntegrationTests {
 
   @Test(arguments: InstanceConfig.allConfigs)
   func generateContent(_ config: InstanceConfig) async throws {
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2FlashLite,
       generationConfig: generationConfig,
       safetySettings: safetySettings
@@ -81,7 +81,7 @@ struct GenerateContentIntegrationTests {
     arguments: InstanceConfig.allConfigsExceptDeveloperV1
   )
   func generateContentEnum(_ config: InstanceConfig) async throws {
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2FlashLite,
       generationConfig: GenerationConfig(
         responseMIMEType: "text/x.enum", // Not supported on the v1 Developer API
@@ -128,7 +128,7 @@ struct GenerateContentIntegrationTests {
       topK: 1,
       responseModalities: [.text, .image]
     )
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2FlashExperimental,
       generationConfig: generationConfig,
       safetySettings: safetySettings
@@ -184,7 +184,7 @@ struct GenerateContentIntegrationTests {
     What are the names of the planets in the solar system, ordered from closest to furthest from
     the sun? Answer with a Markdown numbered list of the names and no other text.
     """
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2FlashLite,
       generationConfig: generationConfig,
       safetySettings: safetySettings
@@ -222,7 +222,7 @@ struct GenerateContentIntegrationTests {
     // bypasses the Vertex AI in Firebase proxy.
   ])
   func generateContent_appCheckNotConfigured_shouldFail(_ config: InstanceConfig) async throws {
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2Flash
     )
     let prompt = "Where is Google headquarters located? Answer with the city name only."

+ 4 - 4
FirebaseVertexAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift → FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift

@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import FirebaseAI
 import FirebaseAuth
 import FirebaseCore
 import FirebaseStorage
-import FirebaseVertexAI
 import Testing
 import VertexAITestApp
 
@@ -24,7 +24,7 @@ import VertexAITestApp
 #endif // canImport(UIKit)
 
 // TODO(#14452): Remove `@testable import` when `generateImages(prompt:gcsURI:)` is public.
-@testable import class FirebaseVertexAI.ImagenModel
+@testable import class FirebaseAI.ImagenModel
 
 @Suite(
   .enabled(
@@ -34,13 +34,13 @@ import VertexAITestApp
   .serialized
 )
 struct ImagenIntegrationTests {
-  var vertex: VertexAI
+  var vertex: FirebaseAI
   var storage: Storage
   var userID1: String
 
   init() async throws {
     userID1 = try await TestHelpers.getUserID()
-    vertex = VertexAI.vertexAI()
+    vertex = FirebaseAI.vertexAI()
     storage = Storage.storage()
   }
 

+ 4 - 4
FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift → FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift

@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import FirebaseAI
 import FirebaseAuth
 import FirebaseCore
 import FirebaseStorage
-import FirebaseVertexAI
 import VertexAITestApp
 import XCTest
 
@@ -42,14 +42,14 @@ final class IntegrationTests: XCTestCase {
   // Candidates and total token counts may differ slightly between runs due to whitespace tokens.
   let tokenCountAccuracy = 1
 
-  var vertex: VertexAI!
+  var vertex: FirebaseAI!
   var model: GenerativeModel!
   var storage: Storage!
   var userID1 = ""
 
   override func setUp() async throws {
     userID1 = try await TestHelpers.getUserID()
-    vertex = VertexAI.vertexAI()
+    vertex = FirebaseAI.vertexAI()
     model = vertex.generativeModel(
       modelName: "gemini-2.0-flash",
       generationConfig: generationConfig,
@@ -200,7 +200,7 @@ final class IntegrationTests: XCTestCase {
 
   func testCountTokens_appCheckNotConfigured_shouldFail() async throws {
     let app = try XCTUnwrap(FirebaseApp.app(name: FirebaseAppNames.appCheckNotConfigured))
-    let vertex = VertexAI.vertexAI(app: app)
+    let vertex = FirebaseAI.vertexAI(app: app)
     let model = vertex.generativeModel(modelName: "gemini-2.0-flash")
     let prompt = "Why is the sky blue?"
 

+ 6 - 6
FirebaseVertexAI/Tests/TestApp/Tests/Integration/SchemaTests.swift → FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift

@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import FirebaseAI
 import FirebaseAuth
 import FirebaseCore
 import FirebaseStorage
-import FirebaseVertexAI
 import Testing
 import VertexAITestApp
 
@@ -23,7 +23,7 @@ import VertexAITestApp
   import UIKit
 #endif // canImport(UIKit)
 
-@testable import struct FirebaseVertexAI.BackendError
+@testable import struct FirebaseAI.BackendError
 
 @Suite(.serialized)
 /// Test the schema fields.
@@ -50,7 +50,7 @@ struct SchemaTests {
 
   @Test(arguments: InstanceConfig.allConfigsExceptDeveloperV1)
   func generateContentSchemaItems(_ config: InstanceConfig) async throws {
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2FlashLite,
       generationConfig: GenerationConfig(
         responseMIMEType: "application/json",
@@ -75,7 +75,7 @@ struct SchemaTests {
 
   @Test(arguments: InstanceConfig.allConfigsExceptDeveloperV1)
   func generateContentSchemaNumberRange(_ config: InstanceConfig) async throws {
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2FlashLite,
       generationConfig: GenerationConfig(
         responseMIMEType: "application/json",
@@ -104,7 +104,7 @@ struct SchemaTests {
       let price: Double // Will correspond to .double in schema
       let salePrice: Float // Will correspond to .float in schema
     }
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2FlashLite,
       generationConfig: GenerationConfig(
         responseMIMEType: "application/json",
@@ -195,7 +195,7 @@ struct SchemaTests {
       ],
       description: "A U.S. mailing address"
     )
-    let model = VertexAI.componentInstance(config).generativeModel(
+    let model = FirebaseAI.componentInstance(config).generativeModel(
       modelName: ModelNames.gemini2Flash,
       generationConfig: GenerationConfig(
         temperature: 0.0,

+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Tests/Integration/TestHelpers.swift → FirebaseAI/Tests/TestApp/Tests/Integration/TestHelpers.swift


+ 6 - 6
FirebaseVertexAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift → FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift

@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import FirebaseAI
 import FirebaseCore
 import Testing
 import VertexAITestApp
 
-@testable import struct FirebaseVertexAI.APIConfig
-@testable import class FirebaseVertexAI.VertexAI
+@testable import struct FirebaseAI.APIConfig
 
 struct InstanceConfig {
   static let vertexV1 = InstanceConfig(
@@ -122,12 +122,12 @@ extension InstanceConfig: CustomTestStringConvertible {
   }
 }
 
-extension VertexAI {
-  static func componentInstance(_ instanceConfig: InstanceConfig) -> VertexAI {
+extension FirebaseAI {
+  static func componentInstance(_ instanceConfig: InstanceConfig) -> FirebaseAI {
     switch instanceConfig.apiConfig.service {
     case .vertexAI:
       let location = instanceConfig.location ?? "us-central1"
-      return VertexAI.vertexAI(
+      return FirebaseAI.vertexAI(
         app: instanceConfig.app,
         location: location,
         apiConfig: instanceConfig.apiConfig
@@ -137,7 +137,7 @@ extension VertexAI {
         instanceConfig.location == nil,
         "The Developer API is global and does not support `location`."
       )
-      return VertexAI.vertexAI(
+      return FirebaseAI.vertexAI(
         app: instanceConfig.app,
         location: nil,
         apiConfig: instanceConfig.apiConfig

+ 0 - 0
FirebaseVertexAI/Tests/TestApp/Tests/Utilities/IntegrationTestUtils.swift → FirebaseAI/Tests/TestApp/Tests/Utilities/IntegrationTestUtils.swift


+ 0 - 0
FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj → FirebaseAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj


+ 213 - 0
FirebaseAI/Tests/Unit/APITests.swift

@@ -0,0 +1,213 @@
+// Copyright 2023 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 FirebaseAI
+import FirebaseCore
+import XCTest
+#if canImport(AppKit)
+  import AppKit // For NSImage extensions.
+#elseif canImport(UIKit)
+  import UIKit // For UIImage extensions.
+#endif
+
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
+final class APITests: XCTestCase {
+  func codeSamples() async throws {
+    let app = FirebaseApp.app()
+    let config = GenerationConfig(temperature: 0.2,
+                                  topP: 0.1,
+                                  topK: 16,
+                                  candidateCount: 4,
+                                  maxOutputTokens: 256,
+                                  stopSequences: ["..."],
+                                  responseMIMEType: "text/plain")
+    let filters = [SafetySetting(harmCategory: .dangerousContent, threshold: .blockOnlyHigh)]
+    let systemInstruction = ModelContent(
+      role: "system",
+      parts: TextPart("Talk like a pirate.")
+    )
+
+    let requestOptions = RequestOptions()
+    let _ = RequestOptions(timeout: 30.0)
+
+    // Instantiate Vertex AI SDK - Default App
+    let vertexAI = FirebaseAI.vertexAI()
+    let _ = FirebaseAI.vertexAI(location: "my-location")
+
+    // Instantiate Vertex AI SDK - Custom App
+    let _ = FirebaseAI.vertexAI(app: app!)
+    let _ = FirebaseAI.vertexAI(app: app!, location: "my-location")
+
+    // Permutations without optional arguments.
+
+    let _ = vertexAI.generativeModel(modelName: "gemini-1.0-pro")
+
+    let _ = vertexAI.generativeModel(
+      modelName: "gemini-1.0-pro",
+      safetySettings: filters
+    )
+
+    let _ = vertexAI.generativeModel(
+      modelName: "gemini-1.0-pro",
+      generationConfig: config
+    )
+
+    let _ = vertexAI.generativeModel(
+      modelName: "gemini-1.0-pro",
+      systemInstruction: systemInstruction
+    )
+
+    // All arguments passed.
+    let genAI = vertexAI.generativeModel(
+      modelName: "gemini-1.0-pro",
+      generationConfig: config, // Optional
+      safetySettings: filters, // Optional
+      systemInstruction: systemInstruction, // Optional
+      requestOptions: requestOptions // Optional
+    )
+
+    // Full Typed Usage
+    let pngData = Data() // ....
+    let contents = [ModelContent(
+      role: "user",
+      parts: [
+        TextPart("Is it a cat?"),
+        InlineDataPart(data: pngData, mimeType: "image/png"),
+      ]
+    )]
+
+    do {
+      let response = try await genAI.generateContent(contents)
+      print(response.text ?? "Couldn't get text... check status")
+    } catch {
+      print("Error generating content: \(error)")
+    }
+
+    // Content input combinations.
+    let _ = try await genAI.generateContent("Constant String")
+    let str = "String Variable"
+    let _ = try await genAI.generateContent(str)
+    let _ = try await genAI.generateContent([str])
+    let _ = try await genAI.generateContent(str, "abc", "def")
+    let _ = try await genAI.generateContent(
+      str,
+      FileDataPart(uri: "gs://test-bucket/image.jpg", mimeType: "image/jpeg")
+    )
+    #if canImport(UIKit)
+      _ = try await genAI.generateContent(UIImage())
+      _ = try await genAI.generateContent([UIImage()])
+      _ = try await genAI.generateContent([str, UIImage(), TextPart(str)])
+      _ = try await genAI.generateContent(str, UIImage(), "def", UIImage())
+      _ = try await genAI.generateContent([str, UIImage(), "def", UIImage()])
+      _ = try await genAI.generateContent([ModelContent(parts: "def", UIImage()),
+                                           ModelContent(parts: "def", UIImage())])
+    #elseif canImport(AppKit)
+      _ = try await genAI.generateContent(NSImage())
+      _ = try await genAI.generateContent([NSImage()])
+      _ = try await genAI.generateContent(str, NSImage(), "def", NSImage())
+      _ = try await genAI.generateContent([str, NSImage(), "def", NSImage()])
+    #endif
+
+    // PartsRepresentable combinations.
+    let _ = ModelContent(parts: [TextPart(str)])
+    let _ = ModelContent(role: "model", parts: [TextPart(str)])
+    let _ = ModelContent(parts: "Constant String")
+    let _ = ModelContent(parts: str)
+    let _ = ModelContent(parts: [str])
+    let _ = ModelContent(parts: [str, InlineDataPart(data: Data(), mimeType: "foo")])
+    #if canImport(UIKit)
+      _ = ModelContent(role: "user", parts: UIImage())
+      _ = ModelContent(role: "user", parts: [UIImage()])
+      _ = ModelContent(parts: [str, UIImage()])
+      // Note: without explicitly specifying`: [any PartsRepresentable]` this will fail to compile
+      // below with "Cannot convert value of type `[Any]` to expected type `[any Part]`.
+      let representable2: [any PartsRepresentable] = [str, UIImage()]
+      _ = ModelContent(parts: representable2)
+      _ = ModelContent(parts: [str, UIImage(), TextPart(str)])
+    #elseif canImport(AppKit)
+      _ = ModelContent(role: "user", parts: NSImage())
+      _ = ModelContent(role: "user", parts: [NSImage()])
+      _ = ModelContent(parts: [str, NSImage()])
+      // Note: without explicitly specifying`: [any PartsRepresentable]` this will fail to compile
+      // below with "Cannot convert value of type `[Any]` to expected type `[any Part]`.
+      let representable2: [any PartsRepresentable] = [str, NSImage()]
+      _ = ModelContent(parts: representable2)
+      _ = ModelContent(parts: [str, NSImage(), TextPart(str)])
+    #endif
+
+    // countTokens API
+    let _: CountTokensResponse = try await genAI.countTokens("What color is the Sky?")
+    #if canImport(UIKit)
+      let _: CountTokensResponse = try await genAI.countTokens("What color is the Sky?",
+                                                               UIImage())
+      let _: CountTokensResponse = try await genAI.countTokens([
+        ModelContent(parts: "What color is the Sky?", UIImage()),
+        ModelContent(parts: UIImage(), "What color is the Sky?", UIImage()),
+      ])
+    #endif
+
+    // Chat
+    _ = genAI.startChat()
+    _ = genAI.startChat(history: [ModelContent(parts: "abc")])
+  }
+
+  // Public API tests for GenerateContentResponse.
+  func generateContentResponseAPI() {
+    let response = GenerateContentResponse(candidates: [])
+
+    let _: [Candidate] = response.candidates
+    let _: PromptFeedback? = response.promptFeedback
+
+    // Usage Metadata
+    guard let usageMetadata = response.usageMetadata else { fatalError() }
+    let _: Int = usageMetadata.promptTokenCount
+    let _: Int = usageMetadata.candidatesTokenCount
+    let _: Int = usageMetadata.totalTokenCount
+
+    // Computed Properties
+    let _: String? = response.text
+    let _: [FunctionCallPart] = response.functionCalls
+  }
+
+  // Result builder alternative
+
+  /*
+   let pngData = Data() // ....
+   let contents = [GenAIContent(role: "user",
+                                parts: [
+                                 .text("Is it a cat?"),
+                                 .png(pngData)
+                                ])]
+
+   // Turns into...
+
+   let contents = GenAIContent {
+     Role("user") {
+       Text("Is this a cat?")
+       Image(png: pngData)
+     }
+   }
+
+   GenAIContent {
+     ForEach(myInput) { input in
+       Role(input.role) {
+         input.contents
+       }
+     }
+   }
+
+   // Thoughts: this looks great from a code demo, but since I assume most content will be
+   // user generated, the result builder may not be the best API.
+   */
+}

+ 3 - 3
FirebaseVertexAI/Tests/Unit/ChatTests.swift → FirebaseAI/Tests/Unit/ChatTests.swift

@@ -12,11 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import FirebaseCore
 import Foundation
 import XCTest
 
-import FirebaseCore
-@testable import FirebaseVertexAI
+@testable import FirebaseAI
 
 @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
 final class ChatTests: XCTestCase {
@@ -70,7 +70,7 @@ final class ChatTests: XCTestCase {
         firebaseAppID: "My app ID",
         firebaseApp: app
       ),
-      apiConfig: VertexAI.defaultVertexAIAPIConfig,
+      apiConfig: FirebaseAI.defaultVertexAIAPIConfig,
       tools: nil,
       requestOptions: RequestOptions(),
       urlSession: urlSession

+ 0 - 0
FirebaseVertexAI/Tests/Unit/Fakes/AuthInteropFake.swift → FirebaseAI/Tests/Unit/Fakes/AuthInteropFake.swift


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

@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import FirebaseVertexAI
+import FirebaseAI
 import Foundation
 import XCTest
 

+ 2 - 2
FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift → FirebaseAI/Tests/Unit/GenerativeModelTests.swift

@@ -17,7 +17,7 @@ import FirebaseAuthInterop
 import FirebaseCore
 import XCTest
 
-@testable public import FirebaseVertexAI
+@testable import FirebaseAI
 
 @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
 final class GenerativeModelTests: XCTestCase {
@@ -59,7 +59,7 @@ final class GenerativeModelTests: XCTestCase {
   let testModelName = "test-model"
   let testModelResourceName =
     "projects/test-project-id/locations/test-location/publishers/google/models/test-model"
-  let apiConfig = VertexAI.defaultVertexAIAPIConfig
+  let apiConfig = FirebaseAI.defaultVertexAIAPIConfig
 
   let vertexSubdirectory = "vertexai"
 

+ 1 - 1
FirebaseVertexAI/Tests/Unit/JSONValueTests.swift → FirebaseAI/Tests/Unit/JSONValueTests.swift

@@ -13,7 +13,7 @@
 // limitations under the License.
 import XCTest
 
-@testable import FirebaseVertexAI
+@testable import FirebaseAI
 
 @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
 final class JSONValueTests: XCTestCase {

+ 0 - 0
FirebaseVertexAI/Tests/Unit/MockURLProtocol.swift → FirebaseAI/Tests/Unit/MockURLProtocol.swift


+ 1 - 1
FirebaseVertexAI/Tests/Unit/PartTests.swift → FirebaseAI/Tests/Unit/PartTests.swift

@@ -15,7 +15,7 @@
 import Foundation
 import XCTest
 
-@testable import FirebaseVertexAI
+@testable import FirebaseAI
 
 @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
 final class PartTests: XCTestCase {

+ 1 - 1
FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift → FirebaseAI/Tests/Unit/PartsRepresentableTests.swift

@@ -23,7 +23,7 @@ import XCTest
   import CoreImage
 #endif // canImport(CoreImage)
 
-@testable import FirebaseVertexAI
+@testable import FirebaseAI
 
 @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
 final class PartsRepresentableTests: XCTestCase {

+ 0 - 0
FirebaseVertexAI/Tests/Unit/README.md → FirebaseAI/Tests/Unit/README.md


+ 1 - 1
FirebaseVertexAI/Tests/Unit/RequestOptionsTest.swift → FirebaseAI/Tests/Unit/RequestOptionsTest.swift

@@ -14,7 +14,7 @@
 
 import XCTest
 
-@testable import FirebaseVertexAI
+@testable import FirebaseAI
 
 @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
 final class RequestOptionsTests: XCTestCase {

BIN
FirebaseAI/Tests/Unit/Resources/animals.mp4


BIN
FirebaseAI/Tests/Unit/Resources/blue.png


BIN
FirebaseAI/Tests/Unit/Resources/gemini-report.pdf


BIN
FirebaseAI/Tests/Unit/Resources/hello-world.mp3


+ 67 - 0
FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift

@@ -0,0 +1,67 @@
+// 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 FirebaseAI
+import FirebaseCore
+import XCTest
+
+// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory
+// for instructions on running them manually.
+
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
+final class ChatSnippets: XCTestCase {
+  lazy var model = FirebaseAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash")
+
+  override func setUpWithError() throws {
+    try FirebaseApp.configureDefaultAppForSnippets()
+  }
+
+  override func tearDown() async throws {
+    await FirebaseApp.deleteDefaultAppForSnippets()
+  }
+
+  func testChatNonStreaming() async throws {
+    // Optionally specify existing chat history
+    let history = [
+      ModelContent(role: "user", parts: "Hello, I have 2 dogs in my house."),
+      ModelContent(role: "model", parts: "Great to meet you. What would you like to know?"),
+    ]
+
+    // Initialize the chat with optional chat history
+    let chat = model.startChat(history: history)
+
+    // To generate text output, call sendMessage and pass in the message
+    let response = try await chat.sendMessage("How many paws are in my house?")
+    print(response.text ?? "No text in response.")
+  }
+
+  func testChatStreaming() async throws {
+    // Optionally specify existing chat history
+    let history = [
+      ModelContent(role: "user", parts: "Hello, I have 2 dogs in my house."),
+      ModelContent(role: "model", parts: "Great to meet you. What would you like to know?"),
+    ]
+
+    // Initialize the chat with optional chat history
+    let chat = model.startChat(history: history)
+
+    // To stream generated text output, call sendMessageStream and pass in the message
+    let contentStream = try chat.sendMessageStream("How many paws are in my house?")
+    for try await chunk in contentStream {
+      if let text = chunk.text {
+        print(text)
+      }
+    }
+  }
+}

+ 1 - 1
FirebaseVertexAI/Tests/Unit/Snippets/CloudStorageSnippets.swift → FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift

@@ -14,9 +14,9 @@
 
 #if SWIFT_PACKAGE // The FirebaseStorage dependency has only been added in Package.swift.
 
+  import FirebaseAI
   import FirebaseCore
   import FirebaseStorage
-  import FirebaseVertexAI
 
   // These CloudStorageSnippets are not currently runnable due to the GCS upload paths but are used
   // as compilation tests.

+ 57 - 0
FirebaseAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift

@@ -0,0 +1,57 @@
+// 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 Foundation
+import XCTest
+
+extension FirebaseApp {
+  /// Configures the default `FirebaseApp` for use in snippets tests.
+  ///
+  /// Uses a `GoogleService-Info.plist` file from the
+  /// [`Resources`](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseVertexAI/Tests/Unit/Resources)
+  /// directory.
+  ///
+  /// > Note: This is typically called in a snippet test's set up; overriding
+  /// > `setUpWithError() throws` works well since it supports throwing errors.
+  static func configureDefaultAppForSnippets() throws {
+    guard let plistPath = BundleTestUtil.bundle().path(
+      forResource: "GoogleService-Info",
+      ofType: "plist"
+    ) else {
+      throw XCTSkip("No GoogleService-Info.plist found in FirebaseVertexAI/Tests/Unit/Resources.")
+    }
+
+    let options = try XCTUnwrap(FirebaseOptions(contentsOfFile: plistPath))
+    FirebaseApp.configure(options: options)
+
+    guard FirebaseApp.isDefaultAppConfigured() else {
+      XCTFail("Default Firebase app not configured.")
+      return
+    }
+  }
+
+  /// Deletes the default `FirebaseApp` if configured.
+  ///
+  /// > Note: This is typically called in a snippet test's tear down; overriding
+  /// > `tearDown() async throws` works well since deletion is asynchronous.
+  static func deleteDefaultAppForSnippets() async {
+    // Checking if `isDefaultAppConfigured()` before calling `FirebaseApp.app()` suppresses a log
+    // message that "The default Firebase app has not yet been configured." during `tearDown` when
+    // the tests are skipped. This reduces extraneous noise in the test logs.
+    if FirebaseApp.isDefaultAppConfigured(), let app = FirebaseApp.app() {
+      await app.delete()
+    }
+  }
+}

+ 110 - 0
FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift

@@ -0,0 +1,110 @@
+// 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 FirebaseAI
+import FirebaseCore
+import XCTest
+
+// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory
+// for instructions on running them manually.
+
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
+final class FunctionCallingSnippets: XCTestCase {
+  override func setUpWithError() throws {
+    try FirebaseApp.configureDefaultAppForSnippets()
+  }
+
+  override func tearDown() async throws {
+    await FirebaseApp.deleteDefaultAppForSnippets()
+  }
+
+  func testFunctionCalling() async throws {
+    // This function calls a hypothetical external API that returns
+    // a collection of weather information for a given location on a given date.
+    func fetchWeather(city: String, state: String, date: String) -> JSONObject {
+      // TODO(developer): Write a standard function that would call an external weather API.
+
+      // For demo purposes, this hypothetical response is hardcoded here in the expected format.
+      return [
+        "temperature": .number(38),
+        "chancePrecipitation": .string("56%"),
+        "cloudConditions": .string("partlyCloudy"),
+      ]
+    }
+
+    let fetchWeatherTool = FunctionDeclaration(
+      name: "fetchWeather",
+      description: "Get the weather conditions for a specific city on a specific date.",
+      parameters: [
+        "location": .object(
+          properties: [
+            "city": .string(description: "The city of the location."),
+            "state": .string(description: "The US state of the location."),
+          ],
+          description: """
+          The name of the city and its state for which to get the weather. Only cities in the
+          USA are supported.
+          """
+        ),
+        "date": .string(
+          description: """
+          The date for which to get the weather. Date must be in the format: YYYY-MM-DD.
+          """
+        ),
+      ]
+    )
+
+    // Initialize the Vertex AI service and the generative model.
+    // Use a model that supports function calling, like a Gemini 1.5 model.
+    let model = FirebaseAI.vertexAI().generativeModel(
+      modelName: "gemini-1.5-flash",
+      // Provide the function declaration to the model.
+      tools: [.functionDeclarations([fetchWeatherTool])]
+    )
+
+    let chat = model.startChat()
+    let prompt = "What was the weather in Boston on October 17, 2024?"
+
+    // Send the user's question (the prompt) to the model using multi-turn chat.
+    let response = try await chat.sendMessage(prompt)
+
+    var functionResponses = [FunctionResponsePart]()
+
+    // When the model responds with one or more function calls, invoke the function(s).
+    for functionCall in response.functionCalls {
+      if functionCall.name == "fetchWeather" {
+        // TODO(developer): Handle invalid arguments.
+        guard case let .object(location) = functionCall.args["location"] else { fatalError() }
+        guard case let .string(city) = location["city"] else { fatalError() }
+        guard case let .string(state) = location["state"] else { fatalError() }
+        guard case let .string(date) = functionCall.args["date"] else { fatalError() }
+
+        functionResponses.append(FunctionResponsePart(
+          name: functionCall.name,
+          response: fetchWeather(city: city, state: state, date: date)
+        ))
+      }
+      // TODO(developer): Handle other potential function calls, if any.
+    }
+
+    // Send the response(s) from the function back to the model so that the model can use it
+    // to generate its final response.
+    let finalResponse = try await chat.sendMessage(
+      [ModelContent(role: "function", parts: functionResponses)]
+    )
+
+    // Log the text response.
+    print(finalResponse.text ?? "No text in response.")
+  }
+}

+ 215 - 0
FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift

@@ -0,0 +1,215 @@
+// 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 FirebaseAI
+import FirebaseCore
+import XCTest
+
+#if canImport(UIKit)
+  import UIKit
+#endif // canImport(UIKit)
+
+// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory
+// for instructions on running them manually.
+
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
+final class MultimodalSnippets: XCTestCase {
+  let bundle = BundleTestUtil.bundle()
+  lazy var model = FirebaseAI.vertexAI().generativeModel(modelName: "gemini-2.0-flash")
+  lazy var videoURL = {
+    guard let url = bundle.url(forResource: "animals", withExtension: "mp4") else {
+      fatalError("Video file animals.mp4 not found in Resources.")
+    }
+    return url
+  }()
+
+  lazy var audioURL = {
+    guard let url = bundle.url(forResource: "hello-world", withExtension: "mp3") else {
+      fatalError("Audio file hello-world.mp3 not found in Resources.")
+    }
+    return url
+  }()
+
+  lazy var pdfURL = {
+    guard let url = bundle.url(forResource: "gemini-report", withExtension: "pdf") else {
+      fatalError("PDF file gemini-report.pdf not found in Resources.")
+    }
+    return url
+  }()
+
+  override func setUpWithError() throws {
+    try FirebaseApp.configureDefaultAppForSnippets()
+  }
+
+  override func tearDown() async throws {
+    await FirebaseApp.deleteDefaultAppForSnippets()
+  }
+
+  // MARK: - Image Input
+
+  #if canImport(UIKit)
+    func testMultimodalOneImageNonStreaming() async throws {
+      guard let image = UIImage(systemName: "bicycle") else { fatalError() }
+
+      // Provide a text prompt to include with the image
+      let prompt = "What's in this picture?"
+
+      // To generate text output, call generateContent and pass in the prompt
+      let response = try await model.generateContent(image, prompt)
+      print(response.text ?? "No text in response.")
+    }
+
+    func testMultimodalOneImageStreaming() async throws {
+      guard let image = UIImage(systemName: "bicycle") else { fatalError() }
+
+      // Provide a text prompt to include with the image
+      let prompt = "What's in this picture?"
+
+      // To stream generated text output, call generateContentStream and pass in the prompt
+      let contentStream = try model.generateContentStream(image, prompt)
+      for try await chunk in contentStream {
+        if let text = chunk.text {
+          print(text)
+        }
+      }
+    }
+
+    func testMultimodalMultiImagesNonStreaming() async throws {
+      guard let image1 = UIImage(systemName: "car") else { fatalError() }
+      guard let image2 = UIImage(systemName: "car.2") else { fatalError() }
+
+      // Provide a text prompt to include with the images
+      let prompt = "What's different between these pictures?"
+
+      // To generate text output, call generateContent and pass in the prompt
+      let response = try await model.generateContent(image1, image2, prompt)
+      print(response.text ?? "No text in response.")
+    }
+
+    func testMultimodalMultiImagesStreaming() async throws {
+      guard let image1 = UIImage(systemName: "car") else { fatalError() }
+      guard let image2 = UIImage(systemName: "car.2") else { fatalError() }
+
+      // Provide a text prompt to include with the images
+      let prompt = "What's different between these pictures?"
+
+      // To stream generated text output, call generateContentStream and pass in the prompt
+      let contentStream = try model.generateContentStream(image1, image2, prompt)
+      for try await chunk in contentStream {
+        if let text = chunk.text {
+          print(text)
+        }
+      }
+    }
+  #endif // canImport(UIKit)
+
+  // MARK: - Video Input
+
+  func testMultimodalVideoNonStreaming() async throws {
+    // Provide the video as `Data` with the appropriate MIME type
+    let video = try InlineDataPart(data: Data(contentsOf: videoURL), mimeType: "video/mp4")
+
+    // Provide a text prompt to include with the video
+    let prompt = "What is in the video?"
+
+    // To generate text output, call generateContent with the text and video
+    let response = try await model.generateContent(video, prompt)
+    print(response.text ?? "No text in response.")
+  }
+
+  func testMultimodalVideoStreaming() async throws {
+    // Provide the video as `Data` with the appropriate MIME type
+    let video = try InlineDataPart(data: Data(contentsOf: videoURL), mimeType: "video/mp4")
+
+    // Provide a text prompt to include with the video
+    let prompt = "What is in the video?"
+
+    // To stream generated text output, call generateContentStream with the text and video
+    let contentStream = try model.generateContentStream(video, prompt)
+    for try await chunk in contentStream {
+      if let text = chunk.text {
+        print(text)
+      }
+    }
+  }
+
+  // MARK: - Audio Input
+
+  func testMultiModalAudioNonStreaming() async throws {
+    // Provide the audio as `Data` with the appropriate MIME type
+    let audio = try InlineDataPart(data: Data(contentsOf: audioURL), mimeType: "audio/mpeg")
+
+    // Provide a text prompt to include with the audio
+    let prompt = "Transcribe what's said in this audio recording."
+
+    // To generate text output, call `generateContent` with the audio and text prompt
+    let response = try await model.generateContent(audio, prompt)
+
+    // Print the generated text, handling the case where it might be nil
+    print(response.text ?? "No text in response.")
+  }
+
+  func testMultiModalAudioStreaming() async throws {
+    // Provide the audio as `Data` with the appropriate MIME type
+    let audio = try InlineDataPart(data: Data(contentsOf: audioURL), mimeType: "audio/mpeg")
+
+    // Provide a text prompt to include with the audio
+    let prompt = "Transcribe what's said in this audio recording."
+
+    // To stream generated text output, call `generateContentStream` with the audio and text prompt
+    let contentStream = try model.generateContentStream(audio, prompt)
+
+    // Print the generated text, handling the case where it might be nil
+    for try await chunk in contentStream {
+      if let text = chunk.text {
+        print(text)
+      }
+    }
+  }
+
+  // MARK: - Document Input
+
+  func testMultiModalPDFStreaming() async throws {
+    // Provide the PDF as `Data` with the appropriate MIME type
+    let pdf = try InlineDataPart(data: Data(contentsOf: pdfURL), mimeType: "application/pdf")
+
+    // Provide a text prompt to include with the PDF file
+    let prompt = "Summarize the important results in this report."
+
+    // To stream generated text output, call `generateContentStream` with the PDF file and text
+    // prompt
+    let contentStream = try model.generateContentStream(pdf, prompt)
+
+    // Print the generated text, handling the case where it might be nil
+    for try await chunk in contentStream {
+      if let text = chunk.text {
+        print(text)
+      }
+    }
+  }
+
+  func testMultiModalPDFNonStreaming() async throws {
+    // Provide the PDF as `Data` with the appropriate MIME type
+    let pdf = try InlineDataPart(data: Data(contentsOf: pdfURL), mimeType: "application/pdf")
+
+    // Provide a text prompt to include with the PDF file
+    let prompt = "Summarize the important results in this report."
+
+    // To generate text output, call `generateContent` with the PDF file and text prompt
+    let response = try await model.generateContent(pdf, prompt)
+
+    // Print the generated text, handling the case where it might be nil
+    print(response.text ?? "No text in response.")
+  }
+}

+ 10 - 0
FirebaseAI/Tests/Unit/Snippets/README.md

@@ -0,0 +1,10 @@
+# Vertex AI in Firebase Code Snippet Tests
+
+These "tests" are for verifying that the code snippets provided in our
+documentation continue to compile. They are intentionally skipped in CI but can
+be manually run to verify expected behavior / outputs.
+
+To run the tests, place a valid `GoogleService-Info.plist` file in the
+[`FirebaseVertexAI/Tests/Unit/Resources`](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseVertexAI/Tests/Unit/Resources)
+folder. They may then be invoked individually or alongside the rest of the unit
+tests in Xcode.

+ 96 - 0
FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift

@@ -0,0 +1,96 @@
+// 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 FirebaseAI
+import FirebaseCore
+import XCTest
+
+// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory
+// for instructions on running them manually.
+
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
+final class StructuredOutputSnippets: XCTestCase {
+  override func setUpWithError() throws {
+    try FirebaseApp.configureDefaultAppForSnippets()
+  }
+
+  override func tearDown() async throws {
+    await FirebaseApp.deleteDefaultAppForSnippets()
+  }
+
+  func testStructuredOutputJSONBasic() async throws {
+    // Provide a JSON schema object using a standard format.
+    // Later, pass this schema object into `responseSchema` in the generation config.
+    let jsonSchema = Schema.object(
+      properties: [
+        "characters": Schema.array(
+          items: .object(
+            properties: [
+              "name": .string(),
+              "age": .integer(),
+              "species": .string(),
+              "accessory": .enumeration(values: ["hat", "belt", "shoes"]),
+            ],
+            optionalProperties: ["accessory"]
+          )
+        ),
+      ]
+    )
+
+    // Initialize the Vertex AI service and the generative model.
+    // Use a model that supports `responseSchema`, like one of the Gemini 1.5 models.
+    let model = FirebaseAI.vertexAI().generativeModel(
+      modelName: "gemini-1.5-flash",
+      // In the generation config, set the `responseMimeType` to `application/json`
+      // and pass the JSON schema object into `responseSchema`.
+      generationConfig: GenerationConfig(
+        responseMIMEType: "application/json",
+        responseSchema: jsonSchema
+      )
+    )
+
+    let prompt = "For use in a children's card game, generate 10 animal-based characters."
+
+    let response = try await model.generateContent(prompt)
+    print(response.text ?? "No text in response.")
+  }
+
+  func testStructuredOutputEnumBasic() async throws {
+    // Provide an enum schema object using a standard format.
+    // Later, pass this schema object into `responseSchema` in the generation config.
+    let enumSchema = Schema.enumeration(values: ["drama", "comedy", "documentary"])
+
+    // Initialize the Vertex AI service and the generative model.
+    // Use a model that supports `responseSchema`, like one of the Gemini 1.5 models.
+    let model = FirebaseAI.vertexAI().generativeModel(
+      modelName: "gemini-1.5-flash",
+      // In the generation config, set the `responseMimeType` to `text/x.enum`
+      // and pass the enum schema object into `responseSchema`.
+      generationConfig: GenerationConfig(
+        responseMIMEType: "text/x.enum",
+        responseSchema: enumSchema
+      )
+    )
+
+    let prompt = """
+    The film aims to educate and inform viewers about real-life subjects, events, or people.
+    It offers a factual record of a particular topic by combining interviews, historical footage,
+    and narration. The primary purpose of a film is to present information and provide insights
+    into various aspects of reality.
+    """
+
+    let response = try await model.generateContent(prompt)
+    print(response.text ?? "No text in response.")
+  }
+}

+ 55 - 0
FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift

@@ -0,0 +1,55 @@
+// 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 FirebaseAI
+import FirebaseCore
+import XCTest
+
+// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory
+// for instructions on running them manually.
+
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
+final class TextSnippets: XCTestCase {
+  lazy var model = FirebaseAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash")
+
+  override func setUpWithError() throws {
+    try FirebaseApp.configureDefaultAppForSnippets()
+  }
+
+  override func tearDown() async throws {
+    await FirebaseApp.deleteDefaultAppForSnippets()
+  }
+
+  func testTextOnlyNonStreaming() async throws {
+    // Provide a prompt that contains text
+    let prompt = "Write a story about a magic backpack."
+
+    // To generate text output, call generateContent with the text input
+    let response = try await model.generateContent(prompt)
+    print(response.text ?? "No text in response.")
+  }
+
+  func testTextOnlyStreaming() async throws {
+    // Provide a prompt that contains text
+    let prompt = "Write a story about a magic backpack."
+
+    // To stream generated text output, call generateContentStream with the text input
+    let contentStream = try model.generateContentStream(prompt)
+    for try await chunk in contentStream {
+      if let text = chunk.text {
+        print(text)
+      }
+    }
+  }
+}

+ 31 - 0
FirebaseAI/Tests/Unit/TestUtilities/BundleTestUtil.swift

@@ -0,0 +1,31 @@
+// 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
+
+/// `Bundle` test utilities.
+final class BundleTestUtil {
+  /// Returns the `Bundle` for the test module or target containing the file.
+  ///
+  /// This abstracts away the `Bundle` differences between SPM and CocoaPods tests.
+  static func bundle() -> Bundle {
+    #if SWIFT_PACKAGE
+      return Bundle.module
+    #else // SWIFT_PACKAGE
+      return Bundle(for: Self.self)
+    #endif // SWIFT_PACKAGE
+  }
+
+  private init() {}
+}

+ 0 - 0
FirebaseVertexAI/Tests/Unit/Types/GenerateContentResponseTests.swift → FirebaseAI/Tests/Unit/Types/GenerateContentResponseTests.swift


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است