Skip to content

Commit f425c72

Browse files
committed
Merge branch 'main' into firebaseai/developer_bidi
2 parents fda3d3e + 5199edb commit f425c72

File tree

12 files changed

+195
-26
lines changed

12 files changed

+195
-26
lines changed

.github/workflows/all_plugins.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ jobs:
124124
"flutter build web"
125125
swift-integration:
126126
runs-on: macos-15
127-
timeout-minutes: 30
127+
timeout-minutes: 45
128128
env:
129129
FLUTTER_DEPENDENCIES: "cloud_firestore firebase_remote_config cloud_functions firebase_database firebase_auth firebase_storage firebase_analytics firebase_messaging firebase_app_check firebase_in_app_messaging firebase_performance firebase_crashlytics firebase_ml_model_downloader firebase_app_installations"
130130
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}

packages/firebase_ai/firebase_ai/lib/firebase_ai.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export 'src/api.dart'
2222
FinishReason,
2323
GenerateContentResponse,
2424
GenerationConfig,
25+
ThinkingConfig,
2526
HarmBlockThreshold,
2627
HarmCategory,
2728
HarmProbability,

packages/firebase_ai/firebase_ai/lib/src/developer/api.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
import 'dart:convert';
16+
1517
import '../api.dart'
1618
show
1719
BlockReason,
@@ -31,7 +33,8 @@ import '../api.dart'
3133
SerializationStrategy,
3234
UsageMetadata,
3335
createUsageMetadata;
34-
import '../content.dart' show Content, FunctionCall, Part, TextPart;
36+
import '../content.dart'
37+
show Content, FunctionCall, InlineDataPart, Part, TextPart;
3538
import '../error.dart';
3639
import '../tool.dart' show Tool, ToolConfig;
3740

@@ -322,8 +325,8 @@ Part _parsePart(Object? jsonObject) {
322325
'functionResponse': {'name': String _, 'response': Map<String, Object?> _}
323326
} =>
324327
throw UnimplementedError('FunctionResponse part not yet supported'),
325-
{'inlineData': {'mimeType': String _, 'data': String _}} =>
326-
throw UnimplementedError('inlineData content part not yet supported'),
328+
{'inlineData': {'mimeType': String mimeType, 'data': String bytes}} =>
329+
InlineDataPart(mimeType, base64Decode(bytes)),
327330
_ => throw unhandledFormat('Part', jsonObject),
328331
};
329332
}

packages/firebase_ai/firebase_ai/test/developer_api_test.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14+
import 'dart:convert';
15+
import 'dart:typed_data';
16+
17+
import 'package:firebase_ai/src/content.dart';
1418
import 'package:firebase_ai/src/developer/api.dart';
1519
import 'package:flutter_test/flutter_test.dart';
1620

@@ -121,6 +125,34 @@ void main() {
121125
DeveloperSerialization().parseGenerateContentResponse(jsonResponse);
122126
expect(response.usageMetadata, isNull);
123127
});
128+
129+
test('parses inlineData part correctly', () {
130+
final inlineData = Uint8List.fromList([1, 2, 3, 4]);
131+
final jsonResponse = {
132+
'candidates': [
133+
{
134+
'content': {
135+
'role': 'model',
136+
'parts': [
137+
{
138+
'inlineData': {
139+
'mimeType': 'application/octet-stream',
140+
'data': base64Encode(inlineData),
141+
}
142+
}
143+
]
144+
},
145+
'finishReason': 'STOP',
146+
}
147+
],
148+
};
149+
final response =
150+
DeveloperSerialization().parseGenerateContentResponse(jsonResponse);
151+
final part = response.candidates.first.content.parts.first;
152+
expect(part, isA<InlineDataPart>());
153+
expect((part as InlineDataPart).mimeType, 'application/octet-stream');
154+
expect(part.bytes, inlineData);
155+
});
124156
});
125157
});
126158
}

packages/firebase_auth/firebase_auth_platform_interface/lib/src/id_token_result.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,15 @@ class IdTokenResult {
4646
/// custom, phone, password, etc). Note, this does not map to provider IDs.
4747
String? get signInProvider => _data.signInProvider;
4848

49+
/// The type of second factor associated with this session, provided the user
50+
/// was multi-factor authenticated (for example, phone, etc.).
51+
String? get signInSecondFactor => _data.signInSecondFactor;
52+
4953
/// The Firebase Auth ID token JWT string.
5054
String? get token => _data.token;
5155

5256
@override
5357
String toString() {
54-
return '$IdTokenResult(authTime: $authTime, claims: $claims, expirationTime: $expirationTime, issuedAtTime: $issuedAtTime, signInProvider: $signInProvider, token: $token)';
58+
return '$IdTokenResult(authTime: $authTime, claims: $claims, expirationTime: $expirationTime, issuedAtTime: $issuedAtTime, signInProvider: $signInProvider, signInSecondFactor: $signInSecondFactor, token: $token)';
5559
}
5660
}

packages/firebase_auth/firebase_auth_platform_interface/test/id_token_result_test.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:flutter_test/flutter_test.dart';
99

1010
void main() {
1111
const String kMockSignInProvider = 'password';
12+
const String kMockSignInSecondFactor = 'phone';
1213
const String kMockToken = 'test-token';
1314
const int kMockExpirationTimestamp = 1234566;
1415
const int kMockAuthTimestamp = 1234567;
@@ -23,6 +24,7 @@ void main() {
2324
authTimestamp: kMockAuthTimestamp,
2425
expirationTimestamp: kMockExpirationTimestamp,
2526
signInProvider: kMockSignInProvider,
27+
signInSecondFactor: kMockSignInSecondFactor,
2628
token: kMockToken);
2729

2830
group('$IdTokenResult', () {
@@ -38,6 +40,8 @@ void main() {
3840
expect(idTokenResult.issuedAtTime!.millisecondsSinceEpoch,
3941
equals(kMockIssuedAtTimestamp));
4042
expect(idTokenResult.signInProvider, equals(kMockSignInProvider));
43+
expect(
44+
idTokenResult.signInSecondFactor, equals(kMockSignInSecondFactor));
4145
expect(idTokenResult.token, equals(kMockToken));
4246
});
4347
});
@@ -53,6 +57,7 @@ void main() {
5357
authTimestamp: kMockAuthTimestamp,
5458
expirationTimestamp: kMockExpirationTimestamp,
5559
signInProvider: kMockSignInProvider,
60+
signInSecondFactor: kMockSignInSecondFactor,
5661
token: kMockToken);
5762

5863
final testIdTokenResult = IdTokenResult(kMockData);
@@ -62,7 +67,7 @@ void main() {
6267

6368
test('toString()', () {
6469
expect(idTokenResult.toString(),
65-
'$IdTokenResult(authTime: ${idTokenResult.authTime}, claims: $kMockClaims, expirationTime: ${idTokenResult.expirationTime}, issuedAtTime: ${idTokenResult.issuedAtTime}, signInProvider: $kMockSignInProvider, token: $kMockToken)');
70+
'$IdTokenResult(authTime: ${idTokenResult.authTime}, claims: $kMockClaims, expirationTime: ${idTokenResult.expirationTime}, issuedAtTime: ${idTokenResult.issuedAtTime}, signInProvider: $kMockSignInProvider, signInSecondFactor: $kMockSignInSecondFactor, token: $kMockToken)');
6671
});
6772
});
6873
}

packages/firebase_storage/firebase_storage/example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@
157157
97C146EC1CF9000F007C117D /* Resources */,
158158
9705A1C41CF9048500538489 /* Embed Frameworks */,
159159
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
160+
545E69528F1478FA4BB537F8 /* [CP] Embed Pods Frameworks */,
160161
);
161162
buildRules = (
162163
);
@@ -240,6 +241,40 @@
240241
shellPath = /bin/sh;
241242
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
242243
};
244+
545E69528F1478FA4BB537F8 /* [CP] Embed Pods Frameworks */ = {
245+
isa = PBXShellScriptBuildPhase;
246+
buildActionMask = 2147483647;
247+
files = (
248+
);
249+
inputPaths = (
250+
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
251+
"${BUILT_PRODUCTS_DIR}/FirebaseAppCheckInterop/FirebaseAppCheckInterop.framework",
252+
"${BUILT_PRODUCTS_DIR}/FirebaseAuthInterop/FirebaseAuthInterop.framework",
253+
"${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework",
254+
"${BUILT_PRODUCTS_DIR}/FirebaseCoreExtension/FirebaseCoreExtension.framework",
255+
"${BUILT_PRODUCTS_DIR}/FirebaseCoreInternal/FirebaseCoreInternal.framework",
256+
"${BUILT_PRODUCTS_DIR}/FirebaseStorage/FirebaseStorage.framework",
257+
"${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
258+
"${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework",
259+
"${BUILT_PRODUCTS_DIR}/image_picker_ios/image_picker_ios.framework",
260+
);
261+
name = "[CP] Embed Pods Frameworks";
262+
outputPaths = (
263+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseAppCheckInterop.framework",
264+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseAuthInterop.framework",
265+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework",
266+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreExtension.framework",
267+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreInternal.framework",
268+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseStorage.framework",
269+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
270+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework",
271+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker_ios.framework",
272+
);
273+
runOnlyForDeploymentPostprocessing = 0;
274+
shellPath = /bin/sh;
275+
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
276+
showEnvVarsInLog = 0;
277+
};
243278
9740EEB61CF901F6004384FC /* Run Script */ = {
244279
isa = PBXShellScriptBuildPhase;
245280
alwaysOutOfDate = 1;

packages/firebase_storage/firebase_storage/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
buildConfiguration = "Debug"
4545
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
4646
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
47+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
4748
shouldUseLaunchSchemeArgsEnv = "YES">
4849
<Testables>
4950
</Testables>
@@ -63,11 +64,13 @@
6364
buildConfiguration = "Debug"
6465
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
6566
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
67+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
6668
launchStyle = "0"
6769
useCustomWorkingDirectory = "NO"
6870
ignoresPersistentStateOnLaunch = "NO"
6971
debugDocumentVersioning = "YES"
7072
debugServiceExtension = "internal"
73+
enableGPUValidationMode = "1"
7174
allowLocationSimulation = "YES">
7275
<BuildableProductRunnable
7376
runnableDebuggingMode = "0">

tests/integration_test/firebase_storage/reference_e2e.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,10 @@ void setupReferenceTests() {
344344
test(
345345
'uploads a file',
346346
() async {
347-
final File file = await createFile('flt-ok.txt');
347+
final File file = await createFile(
348+
'flt-ok.txt',
349+
string: kTestString,
350+
);
348351

349352
final Reference ref =
350353
storage.ref('flutter-tests').child('flt-ok.txt');
@@ -381,7 +384,7 @@ void setupReferenceTests() {
381384
'put file some text to compare with uploaded and downloaded';
382385
final File file = await createFile(
383386
'read-and-write.txt',
384-
largeString: text,
387+
string: text,
385388
);
386389

387390
final Reference ref =

tests/integration_test/firebase_storage/task_e2e.dart

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -235,53 +235,85 @@ void setupTaskTests() {
235235
() {
236236
late Task task;
237237

238-
Future<void> _testCancelTask() async {
238+
Future<void> _testCancelTaskSnapshotEvents(Task task) async {
239239
List<TaskSnapshot> snapshots = [];
240240
expect(task.snapshot.state, TaskState.running);
241241
final Completer<FirebaseException> errorReceived =
242242
Completer<FirebaseException>();
243+
final Completer<bool> started = Completer<bool>();
243244

244245
task.snapshotEvents.listen(
245246
(TaskSnapshot snapshot) {
247+
if (!started.isCompleted) {
248+
started.complete(true);
249+
}
246250
snapshots.add(snapshot);
247251
},
248252
onError: (error) {
249253
errorReceived.complete(error);
250254
},
251255
);
252256

257+
await started.future;
258+
253259
bool canceled = await task.cancel();
254260
expect(canceled, isTrue);
255261
expect(task.snapshot.state, TaskState.canceled);
256262

263+
final streamError = await errorReceived.future;
264+
265+
expect(streamError, isNotNull);
266+
expect(streamError.code, 'canceled');
267+
// Expecting there to only be running states, canceled should not get sent as an event.
268+
expect(
269+
snapshots.every((snapshot) => snapshot.state == TaskState.running),
270+
isTrue,
271+
);
272+
257273
await expectLater(
258274
task,
259275
throwsA(
260276
isA<FirebaseException>()
261277
.having((e) => e.code, 'code', 'canceled'),
262278
),
263279
);
280+
}
264281

282+
Future<void> _testCancelTaskLastEvent(Task task) async {
283+
expect(task.snapshot.state, TaskState.running);
284+
285+
bool canceled = await task.cancel();
286+
expect(canceled, isTrue);
265287
expect(task.snapshot.state, TaskState.canceled);
288+
}
266289

267-
// Need to wait for error to be received before checking
268-
final streamError = await errorReceived.future;
290+
test(
291+
'successfully cancels download task using snapshotEvents',
292+
() async {
293+
file = await createFile('ok.txt');
294+
// Need to put a large file in emulator first to test cancel.
295+
final initialPut = downloadRef.putFile(file);
269296

270-
expect(streamError, isNotNull);
271-
expect(streamError.code, 'canceled');
272-
// Expecting there to only be running states, canceled should not get sent as an event.
273-
expect(
274-
snapshots.every((snapshot) => snapshot.state == TaskState.running),
275-
isTrue,
276-
);
277-
}
297+
await initialPut;
298+
task = downloadRef.writeToFile(file);
299+
300+
await _testCancelTaskSnapshotEvents(task);
301+
},
302+
// There's no DownloadTask on web.
303+
// Windows `task.cancel()` is returning "false", same code on example app works as intended
304+
skip: kIsWeb || defaultTargetPlatform == TargetPlatform.windows,
305+
retry: 2,
306+
);
278307

279308
test(
280-
'successfully cancels download task',
309+
'successfully cancels download task and provides the last `canceled` event',
281310
() async {
282-
file = await createFile('ok.jpeg', largeString: 'A' * 20000000);
311+
file = await createFile('ok.txt');
312+
final initialPut = downloadRef.putFile(file);
313+
314+
await initialPut;
283315
task = downloadRef.writeToFile(file);
284-
await _testCancelTask();
316+
await _testCancelTaskLastEvent(task);
285317
},
286318
// There's no DownloadTask on web.
287319
// Windows `task.cancel()` is returning "false", same code on example app works as intended
@@ -290,10 +322,21 @@ void setupTaskTests() {
290322
);
291323

292324
test(
293-
'successfully cancels upload task',
325+
'successfully cancels upload task using snapshotEvents',
326+
() async {
327+
task = uploadRef.putString('A' * 20000000);
328+
await _testCancelTaskSnapshotEvents(task);
329+
},
330+
retry: 2,
331+
// Windows `task.cancel()` is returning "false", same code on example app works as intended
332+
skip: defaultTargetPlatform == TargetPlatform.windows,
333+
);
334+
335+
test(
336+
'successfully cancels upload task and provides the last `canceled` event',
294337
() async {
295338
task = uploadRef.putString('A' * 20000000);
296-
await _testCancelTask();
339+
await _testCancelTaskLastEvent(task);
297340
},
298341
retry: 2,
299342
// Windows `task.cancel()` is returning "false", same code on example app works as intended

0 commit comments

Comments
 (0)