Skip to content

feat(core): preserve multimodal content blocks in provider responses#833

Merged
christso merged 2 commits intomainfrom
feat/818-provider-preserve
Mar 29, 2026
Merged

feat(core): preserve multimodal content blocks in provider responses#833
christso merged 2 commits intomainfrom
feat/818-provider-preserve

Conversation

@christso
Copy link
Copy Markdown
Collaborator

Closes #818

Depends on #828

Summary

Updates Claude and Pi providers to preserve non-text content blocks (images) in Message.content instead of discarding them via extractTextContent(). This is the critical pipeline change that enables multimodal content to flow from provider response through to evaluators.

Changes

New: claude-content.ts — shared content mapping

  • Extracts toContentArray() and extractTextContent() into a shared module used by all 3 Claude providers (claude-cli, claude-sdk, claude)
  • Eliminates code duplication across providers

Updated providers

Provider Change
claude-cli Now imports from shared claude-content.ts (was already using toContentArray from #828)
claude-sdk Added toContentArray pattern — image blocks now preserved in Content[]
claude (legacy) Same as claude-sdk
pi-coding-agent Added toPiContentArray() to pi-utils.ts; convertAgentMessage() now preserves structured content

Providers NOT changed (text-only APIs)

  • ai-sdk (Vercel AI SDK generateText() returns text only)
  • copilot-cli/sdk (ACP events are text-only)
  • codex (text-only events)
  • vscode (file-based text)
  • agentv (grader-only, not for agent responses)

Pattern used

const structuredContent = toContentArray(content);  // Content[] with images, or undefined
const textContent = extractTextContent(content);     // Plain text fallback
content: structuredContent ?? textContent,            // Prefer structured when available

Text-only responses still produce plain strings (no unnecessary wrapping).

Tests

23 new tests in content-preserve.test.ts:

  • toContentArray: 7 tests (image preservation, format handling, edge cases)
  • extractTextContent: 5 tests (backward compat)
  • toPiContentArray: 4 tests (Pi format handling)
  • extractPiTextContent: 3 tests (backward compat)
  • End-to-end: 4 tests (Content[] → getTextContent, image survives into Message.content)

All 1666 existing tests continue to pass.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 29, 2026

Deploying agentv with  Cloudflare Pages  Cloudflare Pages

Latest commit: 989b04b
Status:⚡️  Build in progress...

View logs

christso and others added 2 commits March 29, 2026 04:28
Update Claude and Pi providers to preserve non-text content blocks
(images) in Message.content instead of discarding them via
extractTextContent(). This enables multimodal content to flow from
provider response through to evaluators.

Changes:
- Create shared claude-content.ts with toContentArray() and
  extractTextContent() used by all 3 Claude providers
- Update claude-cli, claude-sdk, claude providers to use
  structuredContent ?? textContent pattern
- Add toPiContentArray() to pi-utils.ts for Pi provider
- Update pi-coding-agent convertAgentMessage() to preserve
  structured content
- Add 23 unit tests covering content preservation, backward
  compat, and end-to-end multimodal flow

Text-only responses still produce plain strings (no unnecessary
wrapping). extractTextContent() remains available for backward
compatibility.

Closes #818

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Skip ContentImage blocks when Claude's image block has neither valid
source.data nor a non-empty url, preventing invalid empty-source blocks
in the structured content output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@christso christso force-pushed the feat/818-provider-preserve branch from 4ff1bac to 989b04b Compare March 29, 2026 04:33
@christso christso merged commit 352d550 into main Mar 29, 2026
1 of 2 checks passed
@christso christso deleted the feat/818-provider-preserve branch March 29, 2026 04:33
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.

feat(core): preserve multimodal content blocks in provider responses

1 participant