G
YouTube TikTok Games
Skip to content

fix(firebase_ai): parse promptTokensDetails and empty CountTokensResponse on GoogleAI - #18916

Open
cynthiajoan wants to merge 3 commits into
firebase:mainfrom
cynthiajoan:agent/count-tokens-tts-firebase_ai
Open

cynthiajoan wants to merge 3 commits into
firebase:mainfrom
cynthiajoan:agent/count-tokens-tts-firebase_ai

Conversation

@cynthiajoan

@cynthiajoan cynthiajoan commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Description

DeveloperSerialization.parseCountTokensResponse (lib/src/developer/api.dart) dropped promptTokensDetails returned by the Gemini Developer API (googleAI backend), so per-modality token counts such as TEXT and AUDIO were always null, and it threw Unhandled format for CountTokensResponse when the server omitted totalTokens (protobuf JSON omits zero values, returning {} or only promptTokensDetails).

parseCountTokensResponse now defaults totalTokens to 0 on an empty object or when promptTokensDetails is present without totalTokens, parses promptTokensDetails with the existing parseUsageMetadata helper (matching the agentPlatform serializer), and rejects non-integer totalTokens or non-list promptTokensDetails with unhandledFormat. The LiveSession.receive() doc comment is also corrected: the stream stays open across turns until the session is closed.

Verification

  • Change kind: bugfix
  • Red (base d07113e3a + only the new tests): flutter test --no-pub test/developer_api_test.dart → exit 1, 25 passed, 3 failed; new tests failing: parses promptTokensDetails when present, defaults totalTokens to 0 on empty JSON object:
    00:00 +20 -1: DeveloperSerialization parseCountTokensResponse parses promptTokensDetails when present [E]
      Expected: not null
        Actual: <null>
      package:matcher                                     expect
      package:flutter_test/src/widget_tester.dart 473:18  expect
      test/developer_api_test.dart 636:9                  main.<fn>.<fn>.<fn>
    00:00 +20 -1: DeveloperSerialization parseCountTokensResponse defaults totalTokens to 0 on empty JSON object
    00:00 +20 -2: DeveloperSerialization parseCountTokensResponse defaults totalTokens to 0 on empty JSON object [E]
      Unhandled format for CountTokensResponse: {}
      This indicates a problem with the Firebase AI Logic SDK. Try updating to the latest version (https://pub.dev/packages/firebase_ai/versions), or file an issue at https://github.com/firebase/flutterfire/issues.
      package:firebase_ai/src/developer/api.dart 106:5  DeveloperSerialization.parseCountTokensResponse
      test/developer_api_test.dart 647:51               main.<fn>.<fn>.<fn>
    00:00 +20 -2: DeveloperSerialization parseCountTokensResponse defaults totalTokens to 0 when omitted with promptTokensDetails
    00:00 +20 -3: DeveloperSerialization parseCountTokensResponse defaults totalTokens to 0 when omitted with promptTokensDetails [E]
      Unhandled format for CountTokensResponse: {promptTokensDetails: [{modality: TEXT, tokenCount: 0}]}
      This indicates a problem with the Firebase AI Logic SDK. Try updating to the latest version (https://pub.dev/packages/firebase_ai/versions), or file an issue at https://github.com/firebase/flutterfire/issues.
      package:firebase_ai/src/developer/api.dart 106:5  DeveloperSerialization.parseCountTokensResponse
      test/developer_api_test.dart 663:53               main.<fn>.<fn>.<fn>
    00:00 +20 -3: DeveloperSerialization parseCountTokensResponse throws FirebaseAIException on error response
    00:00 +21 -3: DeveloperSerialization parseCountTokensResponse throws unhandledFormat on invalid JSON
    
  • Green (head 5d2cc3751): flutter test --no-pub test/developer_api_test.dart → exit 0, 28 passed (4s).
  • Fork CI: fork CI (all_plugins, e2e-ai, e2e-smoke, pr_title)

Related Issues

N/A (AI-COUNT-TOKENS-1)

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]).
This will ensure a smooth and quick review process. Updating the pubspec.yaml and changelogs is not required.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • My PR includes unit or integration tests for all changed/updated/fixed behaviors (See Contributor Guide).
  • All existing and new tests are passing.
  • I updated/added relevant documentation (doc comments with ///).
  • The analyzer (melos run analyze) does not report any problems on my PR.
  • I read and followed the Flutter Style Guide.
  • I signed the CLA.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require plugin users to manually update their apps to accommodate your change?

  • Yes, this is a breaking change.
  • No, this is not a breaking change.

Prepared with the Firebase SDK dev-loop agent (Google-internal, built on Jetski) and reviewed by @cynthiajoan before opening. Evidence: red/green captured by verify-redgreen (verdict verified) at base d07113e3a / head 5d2cc3751.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates parseCountTokensResponse in the developer API to parse promptTokensDetails and handle empty JSON objects, updates documentation for LiveSession.receive(), and adds corresponding unit tests. Feedback highlights a potential parsing failure if totalTokens is omitted while promptTokensDetails is present, and recommends avoiding the ! operator in tests to comply with the repository's null safety style guide.

),
_ => null,
};
if (totalTokens == null && promptTokensDetails == null) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: iOS and Android return 0 for any JSON object, while this still rejects an object that has neither totalTokens nor promptTokensDetails. No current response hits that, but dropping the isEmpty branch and this check (totalTokens ?? 0 for any map) would match the other SDKs and be simpler. It would mean replacing the {'wrongKey': 123} case in throws unhandledFormat on invalid JSON.

/// Returns a [Stream] of [LiveServerResponse] objects representing the
/// messages received from the server. The stream will stops once the server
/// sends turn complete message.
/// messages received from the server. The stream remains open across turns

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stream also ends when the server or the network closes the socket: onDone closes _messageController. Maybe "until the session is closed or the connection ends"? (resumeSession() doesn't reopen it after that either, but that's a separate, older bug.)

);
expect(response.totalTokens, 123);
// Developer API does not return other fields
// ignore: deprecated_member_use_from_same_package

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: CountTokensResponse.promptTokensDetails isn't deprecated, so this // ignore: deprecated_member_use_from_same_package (left over from totalBillableCharacters, removed in #18708) and the three new ones below can go.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants