First user message's first line, truncated to 50 chars (export.tsx:20-42). Mirrors TS ``msg.message?.content`` reads via the renderer's :func:`extract_message_content` accessor so flat dataclasses and wire-shaped dicts both resolve.
(messages: Any)
| 93 | |
| 94 | |
| 95 | def extract_first_prompt(messages: Any) -> str: |
| 96 | """First user message's first line, truncated to 50 chars (export.tsx:20-42). |
| 97 | |
| 98 | Mirrors TS ``msg.message?.content`` reads via the renderer's |
| 99 | :func:`extract_message_content` accessor so flat dataclasses and wire-shaped |
| 100 | dicts both resolve. |
| 101 | """ |
| 102 | first_user = None |
| 103 | for msg in messages or []: |
| 104 | if _message_type(msg) == "user": |
| 105 | first_user = msg |
| 106 | break |
| 107 | if first_user is None: |
| 108 | return "" |
| 109 | |
| 110 | content = extract_message_content(first_user) |
| 111 | result = "" |
| 112 | if isinstance(content, str): |
| 113 | result = content.strip() |
| 114 | elif isinstance(content, list): |
| 115 | # TS uses ``find(b => b.type === 'text')`` and reads ``.text`` off the |
| 116 | # match; a malformed first text block (no ``text`` key) yields ``''``. |
| 117 | # ``is_text_block`` additionally requires a string ``text``, so a |
| 118 | # malformed block is skipped and scanning continues — a negligible |
| 119 | # divergence (TextBlock.text is typed ``str = ""``, so unreachable in |
| 120 | # practice) that fails no worse than TS. |
| 121 | for item in content: |
| 122 | if is_text_block(item): |
| 123 | result = _block_text(item).strip() |
| 124 | break |
| 125 | |
| 126 | result = result.split("\n")[0] |
| 127 | if len(result) > 50: |
| 128 | result = result[:49] + "…" |
| 129 | return result |
| 130 | |
| 131 | |
| 132 | def sanitize_filename(text: str) -> str: |