Let a string definition say what it contains with contentMediaType

Problem/Motivation

A string definition cannot say whether it holds plain text, HTML, or Markdown. These two advertise the same schema:

'title' => new OutputDefinition(
  data_type: 'string',
  label: new TranslatableMarkup('Title'),
  description: new TranslatableMarkup('The image title.'),
),
'caption' => new OutputDefinition(
  data_type: 'string',
  label: new TranslatableMarkup('Caption'),
  description: new TranslatableMarkup('The image caption, as HTML.'),
),
// Both: {"type": "string", ...}

The only signal is prose in the description. A front end rendering the output cannot tell which string to escape, and a model cannot tell which one is safe to quote verbatim. JSON Schema has a keyword for this, contentMediaType. Canvas already reads it from component prop schemas, for text/html strings and image/* URLs.

Proposed resolution

Add a MediaType constraint and have ContextDefinitionNormalizer emit its type option as contentMediaType:

'caption' => new OutputDefinition(
  data_type: 'string',
  label: new TranslatableMarkup('Caption'),
  description: new TranslatableMarkup('The image caption.'),
  constraints: ['MediaType' => ['type' => 'text/html']],
),
// {"type": "string", "contentMediaType": "text/html", ...}

MediaType is an annotation. Its validator accepts every value, the same way JSON Schema treats contentMediaType as descriptive. It is a constraint because the constraints array is the one place a definition carries facts beyond its type, and the normalizer already reads Email and Uuid from there to emit format.

This came out of #3518120 (closed), where media_type: on the attribute-defined tool attributes is built on it. It is useful to any #[Tool] plugin on its own.

Remaining tasks

  • Review the MR

API changes

New MediaType validation constraint. No changes to existing definitions or schemas.

AI disclosure: drafted with Claude Code.

Edited by Matt Glaman