samplingMessagesProblem function

String? samplingMessagesProblem(
  1. Object? messages
)

Why the messages of a sampling/createMessage request break the sampling rules, or null (specification, client/sampling: message content constraints, 2025-11-25+):

  • every message has role user or assistant and content;
  • a user message with a tool_result contains only tool results;
  • an assistant message with tool_use blocks is followed by a user message of tool results answering every one of them by toolUseId.

Implementation

String? samplingMessagesProblem(Object? messages) {
  if (messages is! List) return '"messages" must be an array';
  for (var i = 0; i < messages.length; i++) {
    final m = messages[i];
    if (m is! Map) return 'messages[$i] is not an object';
    final role = m['role'];
    if (role != 'user' && role != 'assistant') {
      return 'messages[$i].role must be "user" or "assistant"';
    }
    if (!m.containsKey('content')) return 'messages[$i] has no content';
    final blocks = _blocks(m['content']);
    final types = [for (final b in blocks) b is Map ? b['type'] : null];
    if (role == 'user' && types.contains('tool_result') && types.any((t) => t != 'tool_result')) {
      return 'messages[$i] mixes tool results with other content';
    }
    if (role == 'assistant' && types.contains('tool_use')) {
      final ids = {
        for (final b in blocks)
          if (b is Map && b['type'] == 'tool_use') b['id'],
      };
      final next = i + 1 < messages.length ? messages[i + 1] : null;
      if (next is! Map || next['role'] != 'user') {
        return 'messages[$i] uses tools but is not followed by their results';
      }
      final results = _blocks(next['content']);
      if (results.any((b) => b is! Map || b['type'] != 'tool_result')) {
        return 'messages[${i + 1}] must contain only tool results';
      }
      final answered = {for (final b in results) (b as Map)['toolUseId']};
      final missing = ids.difference(answered);
      if (missing.isNotEmpty) return 'tool result missing for ${missing.join(', ')}';
    }
  }
  return null;
}