Appearance
Attachment Validation
Before the chat stream is opened, the AI module validates that any attachments in Message.parts are compatible with the agent's model. The check happens in CreateMessageStreamHandler — between the agent lookup and the regenerate / stream branches — so callers receive a clean HTTP 400 with an actionable error code instead of a noisy provider-side rejection.
Two layers of truth
| Question | Source |
|---|---|
Does the agent's model accept this modality (image, file, …)? | Model.architecture.input_modalities — populated by the Vercel AI Gateway sync. Dynamic; auto-updates when a new modality is added upstream. |
| Which MIME types map to which modality? | Static map in attachment-modality.helper.ts. Vercel does not expose per-MIME granularity, so the map acts as a defense-in-depth whitelist. |
Supported MIME types
| MIME | Modality |
|---|---|
image/jpeg | image |
image/png | image |
image/webp | image |
image/gif | image |
application/pdf | file |
A MIME outside this list is rejected with AI.UNSUPPORTED_ATTACHMENT_MEDIA_TYPE regardless of what the model claims to support — the goal is to fail fast with a clear allowed-list rather than forward an exotic type to the provider.
Detected attachment shapes
The validator extracts MIME types from two parts shapes:
{ type: 'file', mediaType, url }— Vercel AI SDK v6 native file part.{ type: 'data-attachment', data: { documentId, mediaType, filename } }— custom part introduced by the chat-attachments feature (Iter 3 of issue #296). Validated here so the contract is enforced as soon as it lands.
Parts with a missing or non-string mediaType are silently ignored at extraction time — they cannot be classified.
Errors
| Error code | HTTP | When |
|---|---|---|
AI.UNSUPPORTED_ATTACHMENT_MEDIA_TYPE | 400 | The MIME is not in the static whitelist. |
AI.MODEL_DOES_NOT_SUPPORT_ATTACHMENTS | 400 | The MIME maps to a modality (e.g. image), but the agent's model does not list that modality in input_modalities. Also fires when the model has no synced modalities (input_modalities: []). |
AI.MODEL_NOT_FOUND | 404 | The modelId referenced by the agent's brain config does not resolve to a row in gateway_models. Only fires when the message has at least one attachment (text-only messages skip the model lookup). |
The check fails on the first invalid attachment in the array — there is no aggregation. If a request has three attachments and the second one is invalid, the response references the second only.
Performance note
The model lookup is skipped entirely for text-only messages (extractAttachmentMediaTypes(parts).length === 0). Validation cost is zero on the hot path; it only adds one DB read when an attachment is actually present.
Out of scope
- Size validation: not enforced here. Native
fileparts from the SDK do not always carry asizefield; the upload-time presigned POST policy of S3 enforces the byte cap (see Direct Upload Presigned URL in the storage module). - Tenancy: validating that an attached
documentIdbelongs to the caller'sorgIdis the responsibility of the chat-attachments feature (Iter 3), not this validator.