- TextDecoder in the text-preview stream now flushes on completion/cancel, so a multibyte UTF-8 char split across a chunk boundary isn't dropped (Copilot).
- send() clears self._wire_part_cache in a finally so the per-send memo (which can hold large rasterized PDF page-images) is released at send end instead of retained on an idle session until the next send (Copilot + fix-review).
- Make the implicit byte-string concatenation in _minimal_pdf explicit (+) in test_pdf.py and test_thumbnails.py so it can't read as a missing comma (CodeQL / github-code-quality).
A vision-capable model that can't ingest PDF natively now gets the PDF
rendered to one image per page instead of extracted text; falls back to
text extraction when rendering yields nothing.
- core/pdf.py: rasterize_pdf via pypdfium2 render + Pillow PNG (page-capped
at 10, never raises)
- session._wire_content_part: pdf + !supports_pdf + supports_vision ->
rasterized image parts; else text extraction
- trajectory.resolve_attachment_parts: a placeholder can now expand to a
list of parts (1->N); the resolve_attachments callback return type widened
to dict[str, Any] across the provider protocol + 4 providers
- pyproject: pillow dependency
- tests: rasterize_pdf, vision-rasterize gate path, 1->N materialization
When the active model can't ingest a kind natively, the wire resolver
converts it client-side instead of sending a part the model can't read.
Per-kind ownership, no shared machinery: PDF text-extraction is a
pure-local PDF concern; audio transcription is an STT concern memoized
in the audio domain.
- core/pdf.py: extract_pdf_text via pypdfium2 (pure-local, no network, no
cache — re-run per build; page-capped)
- core/audio.py: transcribe_cached — non-raising, memoized by
(alias, content-hash); backend failures not cached
- session._wire_content_part: per-kind dispatch — native where the model
supports the kind (supports_pdf / supports_audio_input), else fallback;
display/export resolve natively so no conversion fires on a render
- image left ungated (pre-existing behavior unchanged)
- pyproject: pypdfium2 dependency + mypy untyped-import override
- tests: pdf extraction, transcript memoization, per-kind gate dispatch