b653261772
Closes audit gap #2 (FilePart \u2192 MediaPart not implemented). The bridge now lowers `MessageV2.FilePart` on user messages into `LLM.MediaPart`, unblocking image and document inputs. The first pass supports `data:` URLs only — the inline base64 form most commonly produced by the OpenCode UI for pasted screenshots and attached files. `http(s):` and `file:` URLs are explicitly rejected with a clear error so a future fetch / filesystem-read path can plug in cleanly without regressing safety. Implementation: - New `lowerFilePart` helper extracts the base64 payload from a data URL via a single regex; failure yields a typed `UnsupportedContentError` carrying both the partType and a `reason` that includes the offending URL for debuggability. - New `lowerUserPart` dispatches user-side parts: text \u2192 `LLM.text`, file \u2192 `MediaPart`. Returns identity-empty for any unsupported part type the static gate would have caught. - `userMessage` is now `Effect.fnUntraced` so file conversion can yield typed errors. `lowerMessage` (the per-message dispatcher, renamed from `messages` to free the local name) cascades the Effect through the request flow via `Effect.forEach`. - `supportsPart` static gate now allows `file` parts on user messages. Assistant messages still reject file parts (the LLM IR's MediaPart isn't valid in assistant content for any adapter we ship today). - `UnsupportedContentError` gains an optional `reason` field that appends to the canonical message as `<base>: <reason>`. Existing static-gate failures keep the same shape (no reason). Tests (3 new, 1 rewritten): - Image data URL with filename round-trips to MediaPart with base64-stripped data. - PDF data URL preserves filename and base64 payload. - `https:` URL rejected with an error mentioning both the file partType, the message ID, and the offending URL. - The pre-existing "fails instead of dropping unsupported native parts" test now uses a reasoning part on a user message (reasoning is valid for assistants only) since file parts with data URLs are no longer rejected by the static gate. Out of scope, intentional follow-ups: - HTTP/HTTPS URL fetching (would need HttpClient.HttpClient and a decision on caching, retries, size limits). - File path / file:// URL reading (would need FileSystem.FileSystem and a permission check against the session's working directory). - File parts on assistant messages (LLM IR doesn't model assistant-side media; defer until we hit a provider that needs it). - text/plain and application/x-directory file parts that the AI-SDK path converts to text inline at message-v2.ts:791 — for the bridge, those should be converted upstream before reaching LLMNative.request rather than handled here. Verified: bun typecheck clean, 28/0/0 across native + bridge tests (was 21; +7 from the FilePart additions plus the rewritten unsupported-parts test).