Skip to content

Commit 5cc7f5a

Browse files
authored
Merge branch 'main' into origin/cj/ai-live-session-resumption
2 parents c35386b + 150698e commit 5cc7f5a

File tree

185 files changed

+8735
-5610
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

185 files changed

+8735
-5610
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
ReleaseTooling/ @firebase/firebase-ios-sdk-admin
22
FirebaseCore/ @firebase/firebase-ios-sdk-admin
3-
Package.swift @firebase/firebase-ios-sdk-admin
3+
/Package.swift @firebase/firebase-ios-sdk-admin
44
Dangerfile @firebase/firebase-ios-sdk-admin
55
**/Public @firebase/firebase-ios-sdk-admin
66
*.podspec @firebase/firebase-ios-sdk-admin

Crashlytics/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Unreleased
1+
# 12.11.0
22
- [fixed] Fixed an issue where Crashlytics API calls were silently dropped if invoked immediately after Firebase initialization.
33

44
# 12.10.0

FirebaseAI.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ Build AI-powered apps and features with the Gemini API using the Firebase AI SDK
1919
s.social_media_url = 'https://twitter.com/Firebase'
2020

2121
ios_deployment_target = '15.0'
22-
osx_deployment_target = '12.0'
22+
osx_deployment_target = '10.15'
2323
tvos_deployment_target = '15.0'
24-
watchos_deployment_target = '8.0'
24+
watchos_deployment_target = '7.0'
2525

2626
s.ios.deployment_target = ios_deployment_target
2727
s.osx.deployment_target = osx_deployment_target

FirebaseAI/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
[`ContextWindowCompressionConfig`](https://firebase.google.com/docs/reference/swift/firebaseai/api/reference/Classes/ContextWindowCompressionConfig).
77
(#15904)
88

9+
# 12.11.0
10+
- [feature] **Public Preview**: Introduces `GenerativeModelSession` providing
11+
APIs for generating structured data from Gemini via the same `@Generable` and
12+
`@Guide` macros that are used with Foundation Models.
13+
914
# 12.9.0
1015
- [changed] The URL context tool APIs are now GA.
1116
- [feature] Added support for implicit caching (context caching) metadata in `GenerateContentResponse`.

FirebaseAI/Sources/AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ This directory contains the source code for the FirebaseAI library.
2424
- **`GenerativeAIRequest.swift`**: Defines the `GenerativeAIRequest` protocol for requests sent to the generative AI backend. It also defines `RequestOptions`.
2525
- **`GenerativeAIService.swift`**: Defines the `GenerativeAIService` struct, which is responsible for making requests to the generative AI backend. It handles things like authentication, URL construction, and response parsing.
2626
- **`GenerativeModel.swift`**: Defines the `GenerativeModel` class, which represents a remote multimodal model. It provides methods for generating content, counting tokens, and starting a chat via `startChat(history:)`, which returns a `Chat` instance.
27+
- **`GenerativeModelSession.swift`**: Defines the `GenerativeModelSession` class, which provides a simplified interface for single-turn interactions with a generative model. It's particularly useful for generating typed objects from a model's response using the `@Generable` macro, without the conversational turn-based structure of a `Chat`.
2728
- **`History.swift`**: Defines the `History` class, a thread-safe class for managing the chat history, used by the `Chat` class.
2829
- **`JSONValue.swift`**: Defines the `JSONValue` enum and `JSONObject` typealias for representing JSON values.
2930
- **`ModalityTokenCount.swift`**: Defines the `ModalityTokenCount` and `ContentModality` structs for representing token counting information for a single modality.

FirebaseAI/Sources/AILog.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,6 @@ enum AILog {
139139
log(level: .debug, code: code, message)
140140
}
141141

142-
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
143142
static func makeInternalError(message: String, code: MessageCode) -> GenerateContentError {
144143
let error = GenerateContentError.internalError(underlying: NSError(
145144
domain: "\(Constants.baseErrorDomain).Internal",

FirebaseAI/Sources/Chat.swift

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import Foundation
1616

1717
/// An object that represents a back-and-forth chat with a model, capturing the history and saving
1818
/// the context in memory between each message sent.
19-
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
2019
public final class Chat: Sendable {
2120
private let model: GenerativeModel
2221
private let _history: History
@@ -37,6 +36,8 @@ public final class Chat: Sendable {
3736
}
3837
}
3938

39+
var generationConfig: GenerationConfig? { model.generationConfig }
40+
4041
/// Sends a message using the existing history of this chat as context. If successful, the message
4142
/// and response will be added to the history. If unsuccessful, history will remain unchanged.
4243
/// - Parameter parts: The new content to send as a single chat message.
@@ -52,14 +53,40 @@ public final class Chat: Sendable {
5253
/// - Parameter content: The new content to send as a single chat message.
5354
/// - Returns: The model's response if no error occurred.
5455
/// - Throws: A ``GenerateContentError`` if an error occurred.
55-
public func sendMessage(_ content: [ModelContent]) async throws
56-
-> GenerateContentResponse {
56+
public func sendMessage(_ content: [ModelContent]) async throws -> GenerateContentResponse {
57+
return try await sendMessage(content, generationConfig: generationConfig)
58+
}
59+
60+
/// Sends a message using the existing history of this chat as context. If successful, the message
61+
/// and response will be added to the history. If unsuccessful, history will remain unchanged.
62+
/// - Parameter parts: The new content to send as a single chat message.
63+
/// - Returns: A stream containing the model's response or an error if an error occurred.
64+
@available(macOS 12.0, watchOS 8.0, *)
65+
public func sendMessageStream(_ parts: any PartsRepresentable...) throws
66+
-> AsyncThrowingStream<GenerateContentResponse, Error> {
67+
return try sendMessageStream([ModelContent(parts: parts)])
68+
}
69+
70+
/// Sends a message using the existing history of this chat as context. If successful, the message
71+
/// and response will be added to the history. If unsuccessful, history will remain unchanged.
72+
/// - Parameter content: The new content to send as a single chat message.
73+
/// - Returns: A stream containing the model's response or an error if an error occurred.
74+
@available(macOS 12.0, watchOS 8.0, *)
75+
public func sendMessageStream(_ content: [ModelContent]) throws
76+
-> AsyncThrowingStream<GenerateContentResponse, Error> {
77+
return try sendMessageStream(content, generationConfig: generationConfig)
78+
}
79+
80+
// MARK: - Internal
81+
82+
func sendMessage(_ content: [ModelContent],
83+
generationConfig: GenerationConfig?) async throws -> GenerateContentResponse {
5784
// Ensure that the new content has the role set.
5885
let newContent = content.map(populateContentRole(_:))
5986

6087
// Send the history alongside the new message as context.
6188
let request = history + newContent
62-
let result = try await model.generateContent(request)
89+
let result = try await model.generateContent(request, generationConfig: generationConfig)
6390
guard let reply = result.candidates.first?.content else {
6491
let error = NSError(domain: "com.google.generative-ai",
6592
code: -1,
@@ -78,29 +105,15 @@ public final class Chat: Sendable {
78105
return result
79106
}
80107

81-
/// Sends a message using the existing history of this chat as context. If successful, the message
82-
/// and response will be added to the history. If unsuccessful, history will remain unchanged.
83-
/// - Parameter parts: The new content to send as a single chat message.
84-
/// - Returns: A stream containing the model's response or an error if an error occurred.
85-
@available(macOS 12.0, *)
86-
public func sendMessageStream(_ parts: any PartsRepresentable...) throws
87-
-> AsyncThrowingStream<GenerateContentResponse, Error> {
88-
return try sendMessageStream([ModelContent(parts: parts)])
89-
}
90-
91-
/// Sends a message using the existing history of this chat as context. If successful, the message
92-
/// and response will be added to the history. If unsuccessful, history will remain unchanged.
93-
/// - Parameter content: The new content to send as a single chat message.
94-
/// - Returns: A stream containing the model's response or an error if an error occurred.
95-
@available(macOS 12.0, *)
96-
public func sendMessageStream(_ content: [ModelContent]) throws
108+
@available(macOS 12.0, watchOS 8.0, *)
109+
func sendMessageStream(_ content: [ModelContent], generationConfig: GenerationConfig?) throws
97110
-> AsyncThrowingStream<GenerateContentResponse, Error> {
98111
// Ensure that the new content has the role set.
99112
let newContent: [ModelContent] = content.map(populateContentRole(_:))
100113

101114
// Send the history alongside the new message as context.
102115
let request = history + newContent
103-
let stream = try model.generateContentStream(request)
116+
let stream = try model.generateContentStream(request, generationConfig: generationConfig)
104117
return AsyncThrowingStream { continuation in
105118
Task {
106119
var aggregatedContent: [ModelContent] = []
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Internal Extensions
2+
3+
This directory contains internal extensions to data models and other types. These extensions provide functionality that is specific to the internal workings of the Firebase AI SDK and are not part of the public API.
4+
5+
## Files
6+
7+
- **`GenerationSchema+Gemini.swift`**: This file extends `GenerationSchema` to provide a `toGeminiJSONSchema()` method. This method transforms the schema into a format that is compatible with the Gemini backend, including renaming properties like `x-order` to `propertyOrdering`. This file is conditionally compiled and is only available when `FoundationModels` can be imported.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#if compiler(>=6.2) && canImport(FoundationModels)
16+
import FoundationModels
17+
18+
@available(iOS 26.0, macOS 26.0, *)
19+
@available(tvOS, unavailable)
20+
@available(watchOS, unavailable)
21+
public extension FoundationModels.ConvertibleFromGeneratedContent {
22+
/// Initializes an instance from `FirebaseAI.GeneratedContent`.
23+
///
24+
/// **Public Preview**: This API is a public preview and may be subject to change.
25+
///
26+
/// - Parameters:
27+
/// - content: The `FirebaseAI.GeneratedContent` from which to initialize.
28+
/// - Throws: An error if initialization fails.
29+
init(_ content: FirebaseAI.GeneratedContent) throws {
30+
try self.init(content.generatedContent)
31+
}
32+
}
33+
#endif // compiler(>=6.2) && canImport(FoundationModels)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
17+
extension FirebaseAI.GenerationSchema {
18+
/// Returns a Gemini-compatible JSON Schema of this `GenerationSchema`.
19+
func toGeminiJSONSchema() throws -> JSONObject {
20+
let encoder = JSONEncoder()
21+
encoder.keyEncodingStrategy = .custom { keys in
22+
guard let lastKey = keys.last else {
23+
assertionFailure("Unexpected empty coding path.")
24+
return SchemaCodingKey(stringValue: "")
25+
}
26+
if lastKey.stringValue == "x-order" {
27+
return SchemaCodingKey(stringValue: "propertyOrdering")
28+
}
29+
return lastKey
30+
}
31+
32+
#if canImport(FoundationModels)
33+
if #available(iOS 26.0, macOS 26.0, visionOS 26.0, *) {
34+
let generationSchemaData = try encoder.encode(self)
35+
let jsonSchema = try JSONDecoder().decode(JSONObject.self, from: generationSchemaData)
36+
37+
return jsonSchema
38+
}
39+
#endif // canImport(FoundationModels)
40+
41+
// TODO: Implement FirebaseAI.GenerationSchema encoding for iOS < 26.
42+
throw EncodingError.invalidValue(self, .init(codingPath: [], debugDescription: """
43+
\(Self.self).#\(#function): `GenerationSchema` encoding is not yet implemented for iOS < 26.
44+
"""))
45+
}
46+
47+
private struct SchemaCodingKey: CodingKey {
48+
let stringValue: String
49+
let intValue: Int? = nil
50+
51+
init(stringValue: String) {
52+
self.stringValue = stringValue
53+
}
54+
55+
init?(intValue: Int) {
56+
assertionFailure("Unexpected \(Self.self) with integer value: \(intValue)")
57+
return nil
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)