chore(attachments): hygiene sweep — dead code, stale comments, SDK type, pdf nit

- Remove the unused PerceptionUnavailableError (never raised/caught/imported).

- Reword the now-shipped 'Phase 3' placeholder comments on the Anthropic + OpenAI-Responses audio paths to describe the live upstream STT/perception fallback (these placeholders are defensive, not pending work).

- Clarify the no-vision image fall-through comment (fires when perception is unconfigured OR can't see, not only the former).

- Type AttachmentInfo.kind as the image|text|pdf|audio union in the TS SDK.

- extract_pdf_text: append the truncation marker only when there's actual text, so a scanned PDF over the page cap returns '' (-> placeholder) instead of a content-free document part.
This commit is contained in:
Patrick Buckley
2026-06-15 20:54:22 -07:00
parent 7939d70b36
commit 1251ecaa11
6 changed files with 16 additions and 15 deletions
+1 -2
View File
@@ -96,8 +96,7 @@ export interface AttachmentInfo {
filename: string;
mime_type: string;
size_bytes: number;
/** "image", "text", "pdf", or "audio". */
kind: string;
kind: "image" | "text" | "pdf" | "audio";
}
export type UploadAttachmentResponse = AttachmentInfo;
+4 -1
View File
@@ -54,7 +54,10 @@ def extract_pdf_text(data: bytes) -> str:
textpage.close()
page.close()
text = "\n\n".join(p.strip() for p in parts if p.strip())
if truncated:
# Only annotate truncation when there's actual text — otherwise a scanned
# (no text layer) PDF over the page cap would return just the marker, i.e.
# a content-free document part. Empty stays empty → caller placeholders it.
if truncated and text:
text += f"\n\n[PDF truncated at {_MAX_PAGES} pages]"
return text
except Exception as exc:
-4
View File
@@ -50,10 +50,6 @@ _DESCRIBE_PROMPT = (
)
class PerceptionUnavailableError(RuntimeError):
"""No usable perception backend is configured/resolvable (maps to a placeholder)."""
class PerceptionBackendError(RuntimeError):
"""A configured perception backend failed during the perceive call."""
+4 -3
View File
@@ -692,9 +692,10 @@ class AnthropicProvider:
converted.append(block)
continue
if part.get("type") == "input_audio":
# Anthropic has no audio-input API. Phase 3 transcribes via the
# STT role upstream of this translator; until then surface a
# placeholder rather than passing an unsupported block to the API.
# Anthropic has no audio-input API. The STT-role fallback runs
# upstream of this translator (audio → text transcript when a
# model lacks supports_audio_input), so by here any remaining
# input_audio is a defensive placeholder, not the live path.
converted.append(
{"type": "text", "text": "[audio attachment — not supported by this model]"}
)
@@ -84,9 +84,10 @@ def convert_content_parts(parts: list[Any]) -> list[dict[str, Any]]:
}
)
elif ptype == "input_audio":
# Audio-input is not wired on the Responses lane; until the Phase 3
# capability-gated fallback (STT) lands upstream, surface a
# placeholder rather than leaking an unhandled part to the API.
# Audio-input is not wired on the Responses lane. The capability-gated
# fallback (STT / perception) runs upstream of this translator, so by
# here any remaining input_audio is a defensive placeholder rather than
# an unhandled part leaking to the API.
converted.append(
{"type": "input_text", "text": "[audio attachment — not supported by this model]"}
)
+3 -2
View File
@@ -3015,8 +3015,9 @@ class ChatSession:
perceived = self._perception_fallback_part(att, "image")
if perceived is not None:
return perceived
# No perception backend configured: emit the native image_url
# unchanged (the model may ignore it) — pre-existing behavior.
# No usable perception backend (none configured, or it can't see):
# emit the native image_url unchanged — a no-vision model ignores it.
# Image is intentionally left ungated here (pre-existing behavior).
if kind == "audio" and not caps.supports_audio_input:
return self._audio_fallback_part(att)
return attachment_to_content_part(att)