Skip to content

fix(ai): Handle duplicate urlContextMetadata in stream aggregation #9446

@Yash7256

Description

@Yash7256

Operating System

All operating systems

Environment (if applicable)

Node.js and Browser environments

Firebase SDK Version

Latest (main branch)

Firebase SDK Product(s)

AI

Project Tooling

Typescript

Detailed Problem Description

Description

The aggregateResponses function in packages/ai/src/requests/stream-reader.ts has a FIXME comment (line 221) questioning how to handle duplicate urlContextMetadata objects in streaming responses.

Current Behavior

// Line 218-230
// The urlContextMetadata object is defined in the first chunk of the response stream.
// In all subsequent chunks, the urlContextMetadata object will be undefined. We need to
// make sure that we don't overwrite the first value urlContextMetadata object with undefined.
// FIXME: What happens if we receive a second, valid urlContextMetadata object?
const urlContextMetadata = candidate.urlContextMetadata as unknown;
if (
  typeof urlContextMetadata === 'object' &&
  urlContextMetadata !== null &&
  Object.keys(urlContextMetadata).length > 0
) {
  aggregatedResponse.candidates[i].urlContextMetadata =
    urlContextMetadata as URLContextMetadata;
}

Problem: The current implementation uses a last-write-wins strategy. If a second valid urlContextMetadata object arrives in a subsequent chunk, it overwrites the first one, potentially causing data loss.

Expected Behavior

According to the comment on line 218: "The urlContextMetadata object is defined in the first chunk of the response stream", the code should preserve the first valid urlContextMetadata and ignore subsequent ones.

Evidence

  1. Comment states metadata should only be in the first chunk
  2. No tests exist for the duplicate metadata scenario (checked packages/ai/src/requests/stream-reader.test.ts)
  3. Integration tests (lines 197-208 in packages/ai/integration/generate-content.test.ts) only verify single occurrence
  4. Other fields like finishReason, safetyRatings use last-write-wins, but urlContextMetadata has special handling code suggesting different intent

Impact

  • Data loss risk: If backend sends duplicate valid metadata, the first value is lost
  • Inconsistent behavior: Code doesn't match documented expectation
  • FIXME unresolved: Outstanding question in codebase needs answer

Location

  • File: packages/ai/src/requests/stream-reader.ts
  • Lines: 218-230
  • Function: aggregateResponses()

Steps and code to reproduce issue

Reproduction Steps

  1. Navigate to the code:
# View the FIXME comment
grep -n "FIXME: What happens if we receive a second" packages/ai/src/requests/stream-reader.ts

# Output:
# 221:        // FIXME: What happens if we receive a second, valid urlContextMetadata object?
  1. Examine the aggregation logic:
# View lines 218-230
sed -n '218,230p' packages/ai/src/requests/stream-reader.ts
  1. Check for existing tests:
# No tests for duplicate metadata
grep -n "urlContextMetadata" packages/ai/src/requests/stream-reader.test.ts
# Returns: No test cases for duplicate metadata handling

Proposed Fix

Implement first-write-wins strategy to match the documented behavior:

// Only set if not already set (preserve first valid value)
if (
  !aggregatedResponse.candidates[i].urlContextMetadata &&
  typeof urlContextMetadata === 'object' &&
  urlContextMetadata !== null &&
  Object.keys(urlContextMetadata).length > 0
) {
  aggregatedResponse.candidates[i].urlContextMetadata =
    urlContextMetadata as URLContextMetadata;
}

This ensures:

  • First valid urlContextMetadata is preserved
  • Subsequent valid ones are ignored (no overwrite)
  • Behavior matches comment documentation
  • Prevents potential data loss

Testing Requirements

The fix should include:

  1. Test case with duplicate urlContextMetadata in multiple chunks
  2. Verification that first value is preserved
  3. Verification that subsequent values are ignored
  4. Edge case: empty first metadata followed by valid second metadata

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions