serializeConversation function

String serializeConversation(
  1. List<Message> messages
)

Serialize messages to plain text for summarization prompts.

Ported from pi's serializeConversation: [User]:, [Assistant]:, [Assistant thinking]:, [Assistant tool calls]:, and truncated [Tool result]: lines, joined by blank lines. Image blocks are skipped.

Implementation

String serializeConversation(List<Message> messages) {
  final parts = <String>[];

  for (final msg in messages) {
    switch (msg) {
      case UserMessage(:final content):
        final text = content is String
            ? content
            : (content as List<ContentBlock>)
                  .whereType<TextContent>()
                  .map((block) => block.text)
                  .join();
        if (text.isNotEmpty) parts.add('[User]: $text');
      case AssistantMessage(:final content):
        final textParts = <String>[];
        final thinkingParts = <String>[];
        final toolCalls = <String>[];
        for (final block in content) {
          switch (block) {
            case TextContent(:final text):
              textParts.add(text);
            case ThinkingContent(:final thinking):
              thinkingParts.add(thinking);
            case ToolCall(:final name, :final arguments):
              final args = arguments.entries
                  .map(
                    (entry) => '${entry.key}=${_safeJsonEncode(entry.value)}',
                  )
                  .join(', ');
              toolCalls.add('$name($args)');
            default:
          }
        }
        if (thinkingParts.isNotEmpty) {
          parts.add('[Assistant thinking]: ${thinkingParts.join('\n')}');
        }
        if (textParts.isNotEmpty) {
          parts.add('[Assistant]: ${textParts.join('\n')}');
        }
        if (toolCalls.isNotEmpty) {
          parts.add('[Assistant tool calls]: ${toolCalls.join('; ')}');
        }
      case ToolResultMessage(:final content):
        final text = content
            .whereType<TextContent>()
            .map((block) => block.text)
            .join();
        if (text.isNotEmpty) {
          parts.add(
            '[Tool result]: ${_truncateForSummary(text, _toolResultMaxChars)}',
          );
        }
      default:
    }
  }

  return parts.join('\n\n');
}