{% from "_macros.html" import icon %} {# One step of a reply: what the model thought, what it said, or what it ran. The only `|safe` here is `step.html`, which came out of `services/markdown.py` and is the one path allowed to emit HTML. Thinking is printed as text and autoescaped by Jinja; tool events go through `_tool_activity.html`, which escapes everything for itself. Ids are `think-{message}-{index}` and, inside the tool block, `tool-{message}-{index}-{n}`. The index is the position of the mark this step came from and the marks are append-only, so an id means the same step for ever -- live, and again in the finished bubble that replaces it. #} {% if step.kind == "thinking" %} {# Collapsed. The answer is what the reader is waiting for and the thinking is one click away -- and on a long agent reply there are a dozen of these, so any other default would bury the work between them. #}
{{ icon("sparkle", "icon--sm reasoning__icon") }} {% if step.index == 0 and message.reasoning_ms %} {# The duration is for the whole reply, so only the first block may claim it. Repeating it on each would be four blocks each saying they took ninety seconds. #} Thought for {{ message.reasoning_ms | duration }} {% else %} Thought {% endif %} {{ icon("chevron-down", "icon--sm reasoning__chevron") }}
{{ step.text }}
{% elif step.kind == "text" %} {# `--live` only on the step still being written, so the caret lands at the end of the reply rather than after every paragraph that preceded a tool call. #}
{{ step.html|safe }}
{% else %}
{% with tool_events = step.events, message_id = message.id, step_index = step.index, live = step.open %} {% include "chat/_tool_activity.html" %} {% endwith %}
{% endif %}