Skip to content

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

QuestionSource
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

MIMEModality
image/jpegimage
image/pngimage
image/webpimage
image/gifimage
application/pdffile

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 codeHTTPWhen
AI.UNSUPPORTED_ATTACHMENT_MEDIA_TYPE400The MIME is not in the static whitelist.
AI.MODEL_DOES_NOT_SUPPORT_ATTACHMENTS400The 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_FOUND404The 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 file parts from the SDK do not always carry a size field; 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 documentId belongs to the caller's orgId is the responsibility of the chat-attachments feature (Iter 3), not this validator.