Skip to content
Draft
12 changes: 7 additions & 5 deletions .github/workflows/sdk.ai.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,20 @@ jobs:
target: iOS
xcode: Xcode_26.1
runs-on: ${{ matrix.os }}
needs: spm
# Temporarily run immediately to check build failures
# needs: spm
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: .build
key: ${{ needs.spm.outputs.cache_key }}
# Temporarily disable cache restore
# - uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
# with:
# path: .build
# key: ${{ needs.spm.outputs.cache_key }}
- name: Run integration tests
run: scripts/repo.sh tests run --secrets ./scripts/secrets/AI.json --platforms ${{ matrix.target }} --xcode ${{ matrix.xcode }} AI
- name: Upload xcodebuild logs
Expand Down
2 changes: 1 addition & 1 deletion FirebaseAI/Sources/GenerativeModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ public final class GenerativeModel: Sendable {
)
try await context.yield(
GenerativeModel.ResponseStream<Content>.Snapshot(
content: Content.Partial(firebaseGeneratedContent),
content: Content.PartiallyGenerated(firebaseGeneratedContent),
rawContent: firebaseGeneratedContent,
rawResponse: response
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#if canImport(FoundationModels)
public import protocol FoundationModels.ConvertibleFromGeneratedContent
public import struct FoundationModels.GeneratedContent
#endif // canImport(FoundationModels)

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public protocol ConvertibleFromFirebaseGeneratedContent {
init(_ content: FirebaseGeneratedContent) throws
}

#if canImport(FoundationModels)
@available(iOS 26.0, macOS 26.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
public extension ConvertibleFromFirebaseGeneratedContent
where Self: ConvertibleFromGeneratedContent {
init(_ content: FirebaseGeneratedContent) throws {
try self.init(content.generatedContent)
}
}
#endif // canImport(FoundationModels)
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#if canImport(FoundationModels)
public import protocol FoundationModels.ConvertibleToGeneratedContent
public import struct FoundationModels.GeneratedContent
#endif // canImport(FoundationModels)

#if compiler(>=6.2)
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public protocol ConvertibleToFirebaseGeneratedContent: SendableMetatype {
Expand All @@ -28,14 +23,3 @@
var firebaseGeneratedContent: FirebaseGeneratedContent { get }
}
#endif // compiler(>=6.2)

#if canImport(FoundationModels)
@available(iOS 26.0, macOS 26.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
public extension ConvertibleToFirebaseGeneratedContent where Self: ConvertibleToGeneratedContent {
var firebaseGeneratedContent: FirebaseGeneratedContent {
generatedContent.firebaseGeneratedContent
}
}
#endif // canImport(FoundationModels)
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ import Foundation
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public protocol FirebaseGenerable: ConvertibleFromFirebaseGeneratedContent,
ConvertibleToFirebaseGeneratedContent {
associatedtype Partial: ConvertibleFromFirebaseGeneratedContent = Self
associatedtype PartiallyGenerated: ConvertibleFromFirebaseGeneratedContent

static var firebaseGenerationSchema: FirebaseGenerationSchema { get }
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public extension FirebaseGenerable {
typealias Partial = Self
typealias PartiallyGenerated = Self
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public extension Optional where Wrapped: FirebaseGenerable {
typealias Partial = Wrapped.Partial
typealias PartiallyGenerated = Wrapped.PartiallyGenerated
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
Expand All @@ -56,6 +56,8 @@ extension Optional: ConvertibleFromFirebaseGeneratedContent

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Bool: FirebaseGenerable {
public typealias PartiallyGenerated = Self

public static var firebaseGenerationSchema: FirebaseGenerationSchema {
FirebaseGenerationSchema(type: Bool.self, kind: .boolean)
}
Expand All @@ -74,6 +76,8 @@ extension Bool: FirebaseGenerable {

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension String: FirebaseGenerable {
public typealias PartiallyGenerated = Self

public static var firebaseGenerationSchema: FirebaseGenerationSchema {
FirebaseGenerationSchema(type: String.self, kind: .string(guides: StringGuides()))
}
Expand All @@ -92,6 +96,8 @@ extension String: FirebaseGenerable {

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Int: FirebaseGenerable {
public typealias PartiallyGenerated = Self

public static var firebaseGenerationSchema: FirebaseGenerationSchema {
FirebaseGenerationSchema(type: Int.self, kind: .integer(guides: IntegerGuides()))
}
Expand All @@ -110,6 +116,8 @@ extension Int: FirebaseGenerable {

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Float: FirebaseGenerable {
public typealias PartiallyGenerated = Self

public static var firebaseGenerationSchema: FirebaseGenerationSchema {
FirebaseGenerationSchema(type: Float.self, kind: .double(guides: DoubleGuides()))
}
Expand All @@ -128,6 +136,8 @@ extension Float: FirebaseGenerable {

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Double: FirebaseGenerable {
public typealias PartiallyGenerated = Self

public static var firebaseGenerationSchema: FirebaseGenerationSchema {
FirebaseGenerationSchema(type: Double.self, kind: .double(guides: DoubleGuides()))
}
Expand All @@ -146,6 +156,8 @@ extension Double: FirebaseGenerable {

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Decimal: FirebaseGenerable {
public typealias PartiallyGenerated = Self

public static var firebaseGenerationSchema: FirebaseGenerationSchema {
FirebaseGenerationSchema(type: Decimal.self, kind: .double(guides: DoubleGuides()))
}
Expand All @@ -164,6 +176,8 @@ extension Decimal: FirebaseGenerable {

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Array: FirebaseGenerable where Element: FirebaseGenerable {
public typealias PartiallyGenerated = [Element.PartiallyGenerated]

public static var firebaseGenerationSchema: FirebaseGenerationSchema {
FirebaseGenerationSchema(
type: Self.self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@

import Foundation
#if canImport(FoundationModels)
public import protocol FoundationModels.ConvertibleToGeneratedContent
public import struct FoundationModels.GenerationID
public import struct FoundationModels.GeneratedContent
private import FoundationModels
#endif // canImport(FoundationModels)

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public struct FirebaseGeneratedContent: Sendable, CustomDebugStringConvertible, FirebaseGenerable,
Equatable {
public typealias PartiallyGenerated = Self

public let kind: Kind

public static var firebaseGenerationSchema: FirebaseGenerationSchema {
Expand Down Expand Up @@ -134,10 +134,9 @@ public struct FirebaseGeneratedContent: Sendable, CustomDebugStringConvertible,
if #available(iOS 26.0, macOS 26.0, visionOS 26.0, *) {
do {
let generatedContent = try GeneratedContent(json: json)
firebaseGeneratedContent = generatedContent.firebaseGeneratedContent
firebaseGeneratedContent.id = id

self = firebaseGeneratedContent
self = FirebaseGeneratedContent.from(
generatedContent: generatedContent, id: id
)

return
} catch {
Expand Down Expand Up @@ -215,6 +214,47 @@ public struct FirebaseGeneratedContent: Sendable, CustomDebugStringConvertible,

return try Value(value)
}

#if canImport(FoundationModels)
@available(iOS 26.0, macOS 26.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
private static func from(generatedContent: GeneratedContent,
id: ResponseID?) -> FirebaseGeneratedContent {
switch generatedContent.kind {
case .null:
return FirebaseGeneratedContent(kind: .null, id: id)
case let .bool(value):
return FirebaseGeneratedContent(kind: .bool(value), id: id)
case let .number(value):
return FirebaseGeneratedContent(kind: .number(value), id: id)
case let .string(value):
return FirebaseGeneratedContent(kind: .string(value), id: id)
case let .array(values):
return FirebaseGeneratedContent(
kind: .array(values.map {
from(generatedContent: $0, id: nil)
}),
id: id
)
case let .structure(properties: properties, orderedKeys: orderedKeys):
return FirebaseGeneratedContent(
kind: .structure(
properties: properties.mapValues {
from(generatedContent: $0, id: nil)
},
orderedKeys: orderedKeys
),
id: id
)
@unknown default:
assertionFailure(
"Unknown `FoundationModels.GeneratedContent` kind: \(generatedContent.kind)"
)
return FirebaseGeneratedContent(kind: .null, id: id)
}
}
#endif // canImport(FoundationModels)
}

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
Expand Down Expand Up @@ -262,73 +302,3 @@ public extension FirebaseGeneratedContent {
}
}
}

#if canImport(FoundationModels)
@available(iOS 26.0, macOS 26.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
extension FirebaseGeneratedContent: ConvertibleToGeneratedContent {
public var generatedContent: GeneratedContent {
let generationID = id?.generationID

switch kind {
case .null:
return GeneratedContent(kind: .null, id: generationID)
case let .bool(value):
return GeneratedContent(kind: .bool(value), id: generationID)
case let .number(value):
return GeneratedContent(kind: .number(value), id: generationID)
case let .string(value):
return GeneratedContent(kind: .string(value), id: generationID)
case let .array(values):
return GeneratedContent(kind: .array(values.map { $0.generatedContent }), id: generationID)
case let .structure(properties: properties, orderedKeys: orderedKeys):
return GeneratedContent(
kind: .structure(
properties: properties.mapValues { $0.generatedContent }, orderedKeys: orderedKeys
),
id: generationID
)
}
}
}

@available(iOS 26.0, macOS 26.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
extension GeneratedContent: ConvertibleToFirebaseGeneratedContent {
public var firebaseGeneratedContent: FirebaseGeneratedContent {
let responseID = id.map { ResponseID(generationID: $0) }
return toFirebaseGeneratedContent(id: responseID)
}

private func toFirebaseGeneratedContent(id: ResponseID?) -> FirebaseGeneratedContent {
switch kind {
case .null:
return FirebaseGeneratedContent(kind: .null, id: id)
case let .bool(value):
return FirebaseGeneratedContent(kind: .bool(value), id: id)
case let .number(value):
return FirebaseGeneratedContent(kind: .number(value), id: id)
case let .string(value):
return FirebaseGeneratedContent(kind: .string(value), id: id)
case let .array(values):
return FirebaseGeneratedContent(
kind: .array(values.map { $0.toFirebaseGeneratedContent(id: nil) }),
id: id
)
case let .structure(properties: properties, orderedKeys: orderedKeys):
return FirebaseGeneratedContent(
kind: .structure(
properties: properties.mapValues { $0.toFirebaseGeneratedContent(id: nil) },
orderedKeys: orderedKeys
),
id: id
)
@unknown default:
assertionFailure("Unknown `FoundationModels.GeneratedContent` kind: \(kind)")
return FirebaseGeneratedContent(kind: .null, id: id)
}
}
}
#endif // canImport(FoundationModels)
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public extension GenerativeModel {
struct ResponseStream<Content>: AsyncSequence, Sendable
where Content: FirebaseGenerable & Sendable, Content.Partial: Sendable {
where Content: FirebaseGenerable & Sendable, Content.PartiallyGenerated: Sendable {
public typealias Element = Snapshot
public typealias AsyncIterator = AsyncThrowingStream<Snapshot, Error>.Iterator

private let _stream: AsyncThrowingStream<Snapshot, Error>
private let _context: StreamContext

public struct Snapshot: Sendable {
public let content: Content.Partial
public let content: Content.PartiallyGenerated
public let rawContent: FirebaseGeneratedContent
public let rawResponse: GenerateContentResponse
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import Foundation
#if canImport(FoundationModels)
internal import struct FoundationModels.GenerationID
private import FoundationModels
#endif // canImport(FoundationModels)

protocol GenerationIDProtocol: Sendable, Hashable {}
Expand Down Expand Up @@ -43,18 +43,22 @@ public struct ResponseID: Sendable, Hashable {
identifier = .value(responseID)
}

init(generationID: any GenerationIDProtocol) {
identifier = .generationID(generationID)
}

#if canImport(FoundationModels)
@available(iOS 26.0, macOS 26.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
init(generationID: GenerationID) {
private init(generationID: GenerationID) {
identifier = .generationID(generationID)
}

@available(iOS 26.0, macOS 26.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
var generationID: GenerationID? {
private var generationID: GenerationID? {
guard case let .generationID(id as GenerationID) = identifier else {
return nil
}
Expand Down
Loading
Loading