mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-21 17:25:26 -04:00
9ae927d73e
Large pastes (>800 chars or >2 newlines) are collapsed into a `[Pasted text #N +M lines]` placeholder in the input box. At submission the placeholder is expanded back to full text so the agent receives the complete content. Rendered user messages over 10,000 chars are truncated to head+tail (2,500 each) with an elision marker, matching Claude Code's behaviour. ## Changes - **`paste_collapse.py`** (new) — threshold constants, `PastedContent` dataclass, `should_collapse_paste`, `format_paste_ref`, `parse_paste_refs`, `expand_paste_refs` - **`ChatTextArea`** — new `PastedText` message; `_on_paste` and `_flush_paste_burst` intercept large pastes before Textual inserts them - **`ChatInput`** — stores paste content, inserts placeholder, expands at submit, cleans up orphaned content on delete, expands for clipboard copy - **`UserMessage` / `QueuedUserMessage`** — `_truncate_for_display()` renders head+tail with elision marker for messages over 10k chars; full text preserved in `_content` for copy/select ## Video https://github.com/user-attachments/assets/1d5425ed-d953-41f7-a898-0520610dd711 --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Mason Daugherty <github@mdrxy.com> Co-authored-by: Mason Daugherty <mason@langchain.dev>
104 lines
3.1 KiB
Python
104 lines
3.1 KiB
Python
r"""Large paste collapsing for the chat input.
|
|
|
|
When the user pastes text exceeding a size or line threshold, the full text
|
|
is stored off-screen and a compact `[Pasted text #N +M lines]` placeholder
|
|
is inserted into the input box instead. At submission time the placeholder
|
|
is expanded back to the original content so the agent receives the full text.
|
|
|
|
This mirrors the behavior of Claude Code's paste-collapsing system.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from dataclasses import dataclass
|
|
|
|
PASTE_THRESHOLD_CHARS = 800
|
|
"""Minimum character count for a paste to be collapsed into a placeholder."""
|
|
|
|
PASTE_THRESHOLD_LINES = 2
|
|
"""Minimum line count (newline-separated) for a paste to be collapsed."""
|
|
|
|
PASTE_PLACEHOLDER_PATTERN = re.compile(r"\[Pasted text #(\d+)(?: \+(\d+) lines)?\]")
|
|
"""Regex matching `[Pasted text #N]` or `[Pasted text #N +M lines]`."""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PastedContent:
|
|
"""Stored content for a collapsed paste.
|
|
|
|
The paste's numeric identifier is the key under which this record is
|
|
stored in the input's paste map, so it is not duplicated on the record.
|
|
|
|
Attributes:
|
|
content: The full pasted text.
|
|
"""
|
|
|
|
content: str
|
|
|
|
|
|
def count_lines(text: str) -> int:
|
|
r"""Return the number of newline characters in `text`.
|
|
|
|
Args:
|
|
text: The text to count newlines in.
|
|
|
|
Returns:
|
|
The number of `\n` occurrences (0 for single-line text).
|
|
"""
|
|
return text.count("\n")
|
|
|
|
|
|
def should_collapse_paste(text: str) -> bool:
|
|
"""Return whether `text` should be collapsed into a placeholder.
|
|
|
|
Collapses when the text exceeds the character threshold or the line
|
|
threshold.
|
|
|
|
Args:
|
|
text: The pasted text to evaluate.
|
|
|
|
Returns:
|
|
`True` if the paste should be collapsed.
|
|
"""
|
|
return (
|
|
len(text) > PASTE_THRESHOLD_CHARS or count_lines(text) > PASTE_THRESHOLD_LINES
|
|
)
|
|
|
|
|
|
def format_paste_ref(paste_id: int, num_lines: int) -> str:
|
|
"""Format a paste placeholder reference string.
|
|
|
|
Args:
|
|
paste_id: The numeric paste identifier.
|
|
num_lines: The number of extra lines (newlines) in the pasted content.
|
|
|
|
Returns:
|
|
`[Pasted text #N]` when `num_lines` is 0, otherwise
|
|
`[Pasted text #N +M lines]`.
|
|
"""
|
|
if num_lines == 0:
|
|
return f"[Pasted text #{paste_id}]"
|
|
return f"[Pasted text #{paste_id} +{num_lines} lines]"
|
|
|
|
|
|
def expand_paste_refs(text: str, pasted_contents: dict[int, PastedContent]) -> str:
|
|
"""Replace all paste placeholders in `text` with their full content.
|
|
|
|
Placeholders whose IDs are not in `pasted_contents` are left unchanged.
|
|
|
|
Args:
|
|
text: The text containing placeholders.
|
|
pasted_contents: Mapping of paste IDs to stored content.
|
|
|
|
Returns:
|
|
The text with all known placeholders expanded.
|
|
"""
|
|
|
|
def _replace(match: re.Match[str]) -> str:
|
|
content = pasted_contents.get(int(match.group(1)))
|
|
# Return the stored text literally; unknown IDs keep their placeholder.
|
|
return content.content if content is not None else match.group(0)
|
|
|
|
return PASTE_PLACEHOLDER_PATTERN.sub(_replace, text)
|