Skip to content

Commit 85a152f

Browse files
andrewheardpaulb777
authored andcommitted
Add URLContextMetadata to Candidate
1 parent 0b97b88 commit 85a152f

File tree

6 files changed

+151
-9
lines changed

6 files changed

+151
-9
lines changed

FirebaseAI/Sources/AILog.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ enum AILog {
6666
case codeExecutionResultUnrecognizedOutcome = 3015
6767
case executableCodeUnrecognizedLanguage = 3016
6868
case fallbackValueUsed = 3017
69+
case urlMetadataUnrecognizedURLRetrievalStatus = 3018
6970

7071
// SDK State Errors
7172
case generateContentResponseNoCandidates = 4000

FirebaseAI/Sources/GenerateContentResponse.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,19 @@ public struct Candidate: Sendable {
154154

155155
public let groundingMetadata: GroundingMetadata?
156156

157+
/// Metadata related to the ``URLContext`` tool.
158+
public let urlContextMetadata: URLContextMetadata?
159+
157160
/// Initializer for SwiftUI previews or tests.
158161
public init(content: ModelContent, safetyRatings: [SafetyRating], finishReason: FinishReason?,
159-
citationMetadata: CitationMetadata?, groundingMetadata: GroundingMetadata? = nil) {
162+
citationMetadata: CitationMetadata?, groundingMetadata: GroundingMetadata? = nil,
163+
urlContextMetadata: URLContextMetadata? = nil) {
160164
self.content = content
161165
self.safetyRatings = safetyRatings
162166
self.finishReason = finishReason
163167
self.citationMetadata = citationMetadata
164168
self.groundingMetadata = groundingMetadata
169+
self.urlContextMetadata = urlContextMetadata
165170
}
166171

167172
// Returns `true` if the candidate contains no information that a developer could use.
@@ -499,6 +504,7 @@ extension Candidate: Decodable {
499504
case finishReason
500505
case citationMetadata
501506
case groundingMetadata
507+
case urlContextMetadata
502508
}
503509

504510
/// Initializes a response from a decoder. Used for decoding server responses; not for public
@@ -540,6 +546,14 @@ extension Candidate: Decodable {
540546
GroundingMetadata.self,
541547
forKey: .groundingMetadata
542548
)
549+
550+
if let urlContextMetadata =
551+
try container.decodeIfPresent(URLContextMetadata.self, forKey: .urlContextMetadata),
552+
!urlContextMetadata.urlMetadata.isEmpty {
553+
self.urlContextMetadata = urlContextMetadata
554+
} else {
555+
urlContextMetadata = nil
556+
}
543557
}
544558
}
545559

FirebaseAI/Sources/Tool.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,6 @@ public struct GoogleSearch: Sendable {
6363
public init() {}
6464
}
6565

66-
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
67-
public struct URLContext: Sendable {
68-
public init() {}
69-
}
70-
7166
/// A helper tool that the model may use when generating responses.
7267
///
7368
/// A `Tool` is a piece of code that enables the system to interact with external systems to perform
@@ -235,8 +230,5 @@ extension FunctionCallingConfig.Mode: Encodable {}
235230
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
236231
extension GoogleSearch: Encodable {}
237232

238-
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
239-
extension URLContext: Encodable {}
240-
241233
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
242234
extension ToolConfig: Encodable {}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2025 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+
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
16+
public struct URLContext: Sendable, Encodable {
17+
init() {}
18+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2025 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+
/// Metadata related to the ``URLContext`` tool.
16+
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
17+
public struct URLContextMetadata: Sendable, Hashable {
18+
/// List of URL context.
19+
public let urlMetadata: [URLMetadata]
20+
}
21+
22+
// MARK: - Codable Conformances
23+
24+
extension URLContextMetadata: Decodable {
25+
enum CodingKeys: CodingKey {
26+
case urlMetadata
27+
}
28+
29+
public init(from decoder: any Decoder) throws {
30+
let container = try decoder.container(keyedBy: CodingKeys.self)
31+
urlMetadata = try container.decodeIfPresent([URLMetadata].self, forKey: .urlMetadata) ?? []
32+
}
33+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2025 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+
/// Context of a single URL retrieval.
18+
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
19+
public struct URLMetadata: Sendable, Hashable {
20+
/// Status of the URL retrieval.
21+
public struct URLRetrievalStatus: DecodableProtoEnum, Hashable {
22+
enum Kind: String {
23+
case unspecified = "URL_RETRIEVAL_STATUS_UNSPECIFIED"
24+
case success = "URL_RETRIEVAL_STATUS_SUCCESS"
25+
case error = "URL_RETRIEVAL_STATUS_ERROR"
26+
case paywall = "URL_RETRIEVAL_STATUS_PAYWALL"
27+
case unsafe = "URL_RETRIEVAL_STATUS_UNSAFE"
28+
}
29+
30+
/// Internal only - default value.
31+
static let unspecified = URLRetrievalStatus(kind: .unspecified)
32+
33+
/// URL retrieval succeeded.
34+
public static let success = URLRetrievalStatus(kind: .success)
35+
36+
/// URL retrieval failed due to an error.
37+
public static let error = URLRetrievalStatus(kind: .error)
38+
39+
// URL retrieval failed failed because the content is behind paywall.
40+
public static let paywall = URLRetrievalStatus(kind: .paywall)
41+
42+
// URL retrieval failed because the content is unsafe.
43+
public static let unsafe = URLRetrievalStatus(kind: .unsafe)
44+
45+
/// Returns the raw string representation of the `URLRetrievalStatus` value.
46+
public let rawValue: String
47+
48+
static let unrecognizedValueMessageCode =
49+
AILog.MessageCode.urlMetadataUnrecognizedURLRetrievalStatus
50+
}
51+
52+
/// The URL retrieved by the ``URLContext`` tool.
53+
public let retrievedURL: URL?
54+
55+
/// The status of the URL retrieval.
56+
public let retrievalStatus: URLRetrievalStatus
57+
}
58+
59+
// MARK: - Codable Conformances
60+
61+
extension URLMetadata: Decodable {
62+
enum CodingKeys: String, CodingKey {
63+
case retrievedURL = "retrievedUrl"
64+
case retrievalStatus = "urlRetrievalStatus"
65+
}
66+
67+
public init(from decoder: any Decoder) throws {
68+
let container = try decoder.container(keyedBy: CodingKeys.self)
69+
70+
if let retrievedURLString = try container.decodeIfPresent(String.self, forKey: .retrievedURL),
71+
let retrievedURL = URL(string: retrievedURLString) {
72+
self.retrievedURL = retrievedURL
73+
} else {
74+
retrievedURL = nil
75+
}
76+
let retrievalStatus = try container.decodeIfPresent(
77+
URLMetadata.URLRetrievalStatus.self, forKey: .retrievalStatus
78+
)
79+
80+
self.retrievalStatus = AILog.safeUnwrap(
81+
retrievalStatus, fallback: URLMetadata.URLRetrievalStatus(kind: .unspecified)
82+
)
83+
}
84+
}

0 commit comments

Comments
 (0)