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.