Skip to content

feat: add CitationsContentBlock for document citation support#568

Open
dbschmigelski wants to merge 13 commits intostrands-agents:mainfrom
dbschmigelski:feat/487
Open

feat: add CitationsContentBlock for document citation support#568
dbschmigelski wants to merge 13 commits intostrands-agents:mainfrom
dbschmigelski:feat/487

Conversation

@dbschmigelski
Copy link
Member

@dbschmigelski dbschmigelski commented Feb 24, 2026

Description

Resolves #487

Adds CitationsBlock for document citation support, achieving parity with the Python SDK (strands-agents/sdk-python#631). Citations are returned by models when document citations are enabled via citations: { enabled: true } on DocumentBlock inputs. They are output-only blocks that appear in conversation history and must be serializable.


Provider-agnostic type design

Rather than mirroring Bedrock's wire format directly, citation types use SDK-native patterns. CitationLocation is a discriminated union with a type field — matching how ContentBlockDelta already works — so consumers can use switch(location.type) instead of 'key' in obj checks. The Bedrock-specific object-key format ({ documentChar: { ... } }) is mapped bidirectionally in bedrock.ts, keeping the public types provider-agnostic. This means other providers can add citation support by implementing their own mapping without changing the shared types.

We also renamed Bedrock's searchResultLocation to searchResult in the SDK type since the "Location" suffix is redundant inside CitationLocation.

Union type convention for extensibility

CitationSourceContent and CitationGeneratedContent are modeled as type aliases rather than interfaces. This follows the same pattern as DocumentSourceData in media.ts — when a type maps to a Bedrock API UNION, we use a type alias so additional variants (e.g., { image: ... }) can be added as a union expansion rather than requiring interface changes. This convention is now documented in AGENTS.md.

Required fields over optional

Bedrock's API docs mark most citation fields as "Not Required", but we kept them required in the SDK types. Integration tests confirm Bedrock always returns documentIndex, start, and end for document locations. Starting with required fields is the safer choice — relaxing to optional is non-breaking, but tightening later would break consumers.

Multi-turn citation support

Citations are filtered and sent back to Bedrock in conversation history, matching the Python SDK behavior. This enables multi-turn conversations where the model can reference previous citations. The formatting pipeline in _formatContentBlock strips to Bedrock-supported fields only (location, sourceContent.text, title) and handles the SDK-to-Bedrock type mapping on the way out.

Python SDK alignment

Python SDK TypeScript (this PR)
CitationsContentBlock in strands/types/citations.py CitationsBlock in src/types/citations.ts
citationsContent field in ContentBlock citationsContent wrapper in ContentBlockData union
Filtered and sent back to Bedrock for multi-turn Same — filtered to Bedrock-supported fields and sent back
Object-key citation locations (Bedrock wire format) type-field discriminated union (mapped in bedrock.ts)

Related Issues

strands-agents/sdk-python#631

Documentation PR

Type of Change

Bug fix
New feature
Breaking change
Documentation update
Other (please describe):

Testing

How have you tested the change?

  • I ran npm run check

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

…s-agents#487)

Add CitationsBlock content type to support document citations returned
by models (particularly Bedrock) when citations are enabled. This aligns
the TypeScript SDK with the Python SDK's citation support.
Bedrock's _formatContentBlock now filters CitationsBlock fields and
sends them back in conversation history, matching the Python SDK
behavior. Also adds citations feature flag to ProviderFeatures for
integration test coverage.
- Fix searchResult → searchResultLocation to match Bedrock API wire format
- Add missing domain field to WebLocation
- Add missing source field to Citation
- Pass through source field in Bedrock citation filter
- Test both streaming and non-streaming Bedrock paths for citations
- Test documentChar (text) and documentPage (PDF) location variants
- Verify citationsContentDelta events in streaming
…n types

Use type aliases instead of interfaces to follow the established pattern
for Bedrock API union types (like DocumentSourceData). This allows
non-breaking expansion when new variants are added in the future.
… tests

Add comprehensive tests for all 5 CitationLocation union variants
(documentChar, documentPage, documentChunk, searchResultLocation, web)
to verify round-trip serialization, Bedrock formatting pipeline, and
multi-turn conversation history preservation.
Replace object-key discrimination with a `type` field on CitationLocation
to match the ContentBlockDelta pattern and provide better ergonomics for
consumers (switch/case instead of `in` checks). Bedrock's wire format
mapping is now handled in bedrock.ts via _mapBedrockCitationLocation and
_mapCitationLocationToBedrock, decoupling the SDK types from the provider.
Make source and title required on Citation — integration tests will
verify Bedrock always returns them. Consolidate duplicated citation
unit tests into a single all-variants round-trip test.
Import BedrockCitationLocation, BedrockCitation, and
BedrockCitationsContentBlock from the Bedrock SDK to type the mapping
functions at the boundary instead of using Record<string, unknown> and
as-unknown-as casts. Simplify _formatContentBlock citationsBlock case
by delegating to _mapCitationToBedrock.
…is from AGENTS.md

- Strip Bedrock-rejected `domain` field from web citation locations in _mapCitationLocationToBedrock
- Refactor citation integ tests: use non-streaming (Bedrock streaming lacks citation support),
  content source format, object-level assertions, and remove seeded multi-turn test
- Remove all emojis from AGENTS.md
@dbschmigelski dbschmigelski marked this pull request as ready for review February 26, 2026 18:53
@dbschmigelski dbschmigelski deployed to manual-approval February 26, 2026 18:53 — with GitHub Actions Active
@github-actions github-actions bot added the strands-running <strands-managed> Whether or not an agent is currently running label Feb 26, 2026
return { searchResultLocation: fields }
}
case 'web':
return { web: { url: location.url } }

Choose a reason for hiding this comment

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

Issue: Web citation domain field is lost in round-trip

The incoming mapping at line 1117 preserves the optional domain field, but this outgoing mapping drops it. This means web citations won't survive a multi-turn conversation with their domain intact.

Suggestion:

case 'web':
  return { web: { url: location.url, ...(location.domain && { domain: location.domain }) } }

@github-actions
Copy link

Assessment: Comment (Request Changes on single issue)

Well-designed implementation of CitationsBlock with provider-agnostic types and comprehensive test coverage. One issue to address with web citation domain handling.

Review Summary
  • Type Design: CitationLocation discriminated union is well-designed with proper type-field pattern. The decision to use type aliases for union types (CitationSourceContent, CitationGeneratedContent) is documented in AGENTS.md.
  • Bi-directional Mapping: Bedrock mapping handles all citation location variants correctly in both directions, except for the domain field on web citations (see inline comment).
  • Testing: Comprehensive unit tests (6 tests) and integration tests (3 scenarios including multi-turn and streaming) cover the key functionality.

Nice addition achieving Python SDK parity with clean provider-agnostic design.

@github-actions github-actions bot removed the strands-running <strands-managed> Whether or not an agent is currently running label Feb 26, 2026
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.

[V1] Protocols - Content Blocks

1 participant