feat: stream pi thinking as ephemeral live preview #27

Merged
weiwen merged 2 commits from sandcastle/issue-19 into main 2026-07-05 12:04:31 +08:00
Owner

Summary

While pi is thinking (but before text starts streaming), forward thinking content to the user as a live 💭-prefixed preview. The moment text begins streaming, the preview is replaced by the answer — so the final delivered message contains only the answer, not the thinking.

What changed

  • assistant_thinking() — new helper that extracts thinking content blocks from a MessageUpdate, parallel to the existing assistant_text().
  • thinking_preview() — formats thinking text with a 💭 prefix and tail-bounds it to 3800 Unicode chars. The 3800-char limit keeps the preview inside Telegram's 4096 UTF-16-unit message cap even after MarkdownV2 escaping.
  • read_agent_response() — the snapshot selection now uses assistant_text().or_else(|| thinking_preview): text always wins when present; thinking preview fires only while no text exists yet.
  • CONTEXT.md — updated the pi.rs summary to document the new ephemeral-preview behaviour.

Tests added

  • test_thinking_only_update_emits_preview — thinking-only event emits a 💭-prefixed snapshot.
  • test_preview_flips_to_answer_text_when_text_arrives — once text appears, text takes over; thinking preview stops.
  • test_thinking_preview_tail_bounded — a 5000-char thinking string is capped to ≤ 3800 chars in the preview.

Review notes

  • The tail-bound is in Unicode chars, not bytes, to avoid splitting multi-byte sequences.
  • No changes to the Telegram or HTTP layers — both already render whatever snapshot they receive.

Closes #19

## Summary While pi is thinking (but before text starts streaming), forward thinking content to the user as a live `💭`-prefixed preview. The moment text begins streaming, the preview is replaced by the answer — so the final delivered message contains only the answer, not the thinking. ## What changed - **`assistant_thinking()`** — new helper that extracts `thinking` content blocks from a `MessageUpdate`, parallel to the existing `assistant_text()`. - **`thinking_preview()`** — formats thinking text with a `💭 ` prefix and tail-bounds it to 3800 Unicode chars. The 3800-char limit keeps the preview inside Telegram's 4096 UTF-16-unit message cap even after MarkdownV2 escaping. - **`read_agent_response()`** — the snapshot selection now uses `assistant_text().or_else(|| thinking_preview)`: text always wins when present; thinking preview fires only while no text exists yet. - **CONTEXT.md** — updated the `pi.rs` summary to document the new ephemeral-preview behaviour. ## Tests added - `test_thinking_only_update_emits_preview` — thinking-only event emits a `💭`-prefixed snapshot. - `test_preview_flips_to_answer_text_when_text_arrives` — once text appears, text takes over; thinking preview stops. - `test_thinking_preview_tail_bounded` — a 5000-char thinking string is capped to ≤ 3800 chars in the preview. ## Review notes - The tail-bound is in Unicode chars, not bytes, to avoid splitting multi-byte sequences. - No changes to the Telegram or HTTP layers — both already render whatever snapshot they receive. Closes #19
Closes #19

Task: While pi thinks before answering, show a 💭-prefixed preview in the
live Telegram message. The instant answer text arrives the preview flips to
the answer; the final delivered message is answer-only.

Key decisions:
- assistant_thinking() mirrors assistant_text() to extract thinking blocks
  from a message_update; text takes priority (if both present, text wins).
- thinking_preview() tail-bounds the raw thinking text to 3800 Unicode chars
  before prefixing with "💭 ". This guarantees the preview stays within one
  Telegram message (4096 UTF-16 units) even after MarkdownV2 escaping, so
  orphaned messages cannot form when the shorter answer lands.
- read_agent_response now resolves a snapshot per message_update: text if
  present, thinking preview otherwise. Dedup logic (last_snapshot) unchanged.
- Snapshot channel type (UnboundedSender<String>) and the Telegram/HTTP
  layers are untouched.

Files changed:
- src/pi.rs: assistant_thinking, thinking_preview, THINKING_PREVIEW_CHAR_LIMIT,
  updated read_agent_response, 3 new tests
- CONTEXT.md: pi.rs description updated

Blockers/notes:
- No Rust toolchain available in sandbox; CI must validate compilation and tests.
  Logic is a straightforward extension of the existing snapshot pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
refactor: simplify thinking snapshot selection with or_else
Some checks failed
CI / check (pull_request) Failing after 1m0s
36957a68b7
Replace the if/else-if/else chain building the message_update snapshot
with an idiomatic Option::or_else fallback. Behaviour is unchanged:
assistant text still takes priority, thinking preview is the fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
weiwen merged commit 6d9c57dfca into main 2026-07-05 12:04:31 +08:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
weiwen/evie!27
No description provided.