Repository navigation
[EPIC-V][LOU-V11] Message.content accepts text, image and file parts; providers convert them - #106
Merged
Merged
Conversation
… providers convert them - Message.content is string | ContentPart[] (text, image, file parts); new textOf(message) returns a message's text. - AiSdkProvider maps user parts to ai v4 TextPart/ImagePart/FilePart for all four providers; file parts degrade to a text note with a one-time warning (the pinned peers cannot send files); other roles send text. - estimateTokens, compaction, recordReplay fingerprints (attachments digest), mockModel, MockLLMProvider, GenAI span content and the React reducer read textOf(); FileSessionStore round-trips bytes as base64. - Contract test with MockLanguageModelV1, plus estimateTokens, compaction, fingerprint, session store and reducer tests. - docs/providers.md "Multimodal input", api-overview, CHANGELOG, llms files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR
# Conflicts: # CHANGELOG.md
Merged
6 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Message.contentis nowstring | ContentPart[]. The part types are{ type: 'text', text },{ type: 'image', image: string | Uint8Array, mimeType? }and{ type: 'file', data: string | Uint8Array, mimeType, filename? }. New exported types:ContentPart,TextContentPart,ImageContentPart,FileContentPart. New helpertextOf(message)(exported from the root and./providers) returns the string content, or the text parts joined.AiSdkProvider.convertMessagesturns the parts of a user message intoaiv4TextPart/ImagePart/FilePartfor all four built-in providers. System, assistant and tool messages are sent as their text.supportsImageUrls = false) theaiSDK downloads an image URL before sending it.[file <name> (<mimeType>) not sent]text note, and each provider prints oneconsole.warn. The reason is the peer versions this package pins (@ai-sdk/openai/@ai-sdk/anthropic0.0.42,ollama-ai-provider1.2):undefined.protected readonly acceptsFileParts = trueto send realFileParts instead.contentas a string now usestextOf():estimateTokens: text parts count as text, and each image or file part counts as a flat 1,000 tokens.recordReplayfingerprint: stores the text plus a new optionalattachmentslist, holding the URL or a 32-bit FNV-1a digest of the image/file bytes. The cassette schema accepts that field.mockModel's error text, andMockLLMProvider's tool matching and token count.ui.sendalso accepts parts.FileSessionStoresavesUint8Array/Buffervalues as{ "$bytes": "<base64>" }and loads them back asUint8Array.MemorySessionStorealready kept them throughstructuredClone.Message[]example.normalizeexample narrowscontent, so it type-checks again.Why
LOU-V11 / Epic V: the "Multimodal input" row of the feature matrix is ❌, while eve and open-harness accept image and file parts. Before this change,
Message.contentcould only be a string.Acceptance criteria
Message.content: string | ContentPart[]with the specified part shapes.textOf(message)is used for estimateTokens, compaction, fingerprint, mockModel, the React reducer, and the session store's base64 round trip ofUint8Array. String content works unchanged everywhere.convertMessagesmaps parts to theaiv4 user-content parts for all four providers; assistant/tool messages stay text. Providers that cannot take a part type (file parts with the pinned peers) degrade it to text with a warning; this is documented in docs/providers.md.src/providers/multimodal.contract.test.tsusesai/test'sMockLanguageModelV1and checks, for each provider, that the prompt the model receives contains the image part (as bytes, from a data URL, and as a URL). It also covers file degradation, theacceptsFilePartsFilePart mapping,textOf, and theAgentExecutor.execute({ input: Message[] })path. There are new estimateTokens, compaction and recordReplay fingerprint tests for part content, plus aFileSessionStoreround-trip test and a reducer test.agent.send(parts)/session.send(parts)/t.sendare left for LOU-V12. src/createAgent.ts and src/session/AgentSession.ts are untouched.Outside the listed scope (the build forced it): widening the type broke
tscinsrc/execution/genAiSpans.ts(3 reads ofcontentas a string, for span content attributes) andsrc/execution/transcript.ts(onestartsWith). Each read was replaced withtextOf(); there are no other changes in those files. Two other out-of-scope edits:src/testing/cassette.tsgained the optionalattachmentsfield in its schema (without it zod strips the field on load), and docs/testing.md'snormalizesnippet was updated.How verified
npx tsc --noEmit: clean.npm run lint: 0 errors, 404 warnings. The changed files show only warnings that were already there; no new ones.npm run test:types: 6 files, 26 tests passed, no type errors.npx vitest run(after the build): 153 of 154 files passed; 2248 tests passed, 1 failed, 3 skipped.cloudflare.test.ts > wrangler dev serves the worker on the local workerd runtime. It is not caused by this PR: workerd fails withNo such module "crypto", imported bysrc/execution/toolRunContext.ts(import { randomUUID } from 'node:crypto', which is already on main from LOU-U15).npm run build && npm run build --workspace=packages/create-loushy-agent: passed.npm run docs:verify-snippets -- --skip-build: all 116 snippets type-check against src/; 6 also run cleanly.npm run docs:llms:check: passed (exit 0).npm run test:coverage && npm run fallow:--coverage.reportOnFailureto write the coverage file.fallowexits 0 with "No issues found". It also lists one OpenAI/AnthropicProvider duplicate clone group that this PR does not touch.🤖 Generated with Claude Code
https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR
Generated by Claude Code