server: add read_media tool#25877
Conversation
This adds a server-tool that allows vision models to analyze server-side images. This tool is reading only a single file for now: The image data is base64 encoded and passed to the UI, which decodes it, fills the <img> tag and removes the data URI before passing the tool result back to the model.
|
Hi @parabelboi, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
allozaur
left a comment
There was a problem hiding this comment.
few architectural remarks. but i really like it :) great stuff. @ngxson @ServeurpersoCom u guys can do a more thorough review/testing on the server side + security aspects
| const extras = section.toolResultExtras; | ||
| if (!extras || extras.length === 0) return null; | ||
| // Extract the attachment name from the cleaned result text | ||
| const match = section.toolResult?.match(/\[Attachment saved: ([^\]]+)\]/); |
There was a problem hiding this comment.
let's move the regex to constants
| if (trimmed.startsWith('Image: ')) { | ||
| path = trimmed.slice('Image: '.length).trim(); | ||
| fileName = path.split('/').pop() ?? path; | ||
| } else if (trimmed.startsWith('Size: ')) { | ||
| const match = trimmed.match(/Size:\s*(\d+)\s*bytes/); | ||
| if (match) sizeBytes = parseInt(match[1], 10); | ||
| } else if (trimmed.startsWith('MIME: ')) { | ||
| mimeType = trimmed.slice('MIME: '.length).trim(); |
There was a problem hiding this comment.
magic strings/regexes — let's move to $lib/constants
| export function parseReadImageMeta(section: AgenticSection): ReadImageMeta | null { | ||
| if (!section.toolResult) return null; | ||
|
|
||
| const lines = section.toolResult.split('\n'); |
There was a problem hiding this comment.
should use existing NEWLINE constant
- Add dedicated constants file: tools/ui/src/lib/constants/read-image.ts with PREFIX_IMAGE, PREFIX_SIZE, PREFIX_MIME constants - Use ATTACHMENT_SAVED_REGEX from agentic.ts in ChatMessageToolCallBlockReadImage.svelte - Use NEWLINE constant from code.ts instead of hardcoded '\n' - Use PREFIX_SIZE in regex pattern for size parsing - Add SERVER_TOOL_READ_IMAGE_PREFIX_* constants in C++ server-tools.cpp to match the TypeScript PREFIX_* constants for consistency
ngxson
left a comment
There was a problem hiding this comment.
I don't think it should be a dedicated tool. Instead, it should be read_file tool with a parameter type:
type: can be"text"or"media"; if "media", returns file content as base64 with mimetype; media file can be image, audio or video file
I also thought about this, but decided to have a separate tool, because for two reasons: First (technical) point is, that users can decide to disable Second (non-technical) point was, that it might be quicker to integrate a new tool, than to extend an existing one. It would create less friction because of separation of concerns. |
| return (it != mime_map.end()) ? it->second : "application/octet-stream"; | ||
| } | ||
|
|
||
| struct server_tool_read_image : server_tool { |
There was a problem hiding this comment.
first, I'd expect to have a generic read_media tool that allow reading image/audio/video
second, if we expect to keep read_media / read_file as separate tools, I'd expect server_tool_read_media to be a derived class of server_tool_read_file
the read_media::invoke() should do only one job: convert the output to base64 and provide the appropriate mime type
There was a problem hiding this comment.
Would it be sufficient if the server_tool_read_media class is a derived class of server_tool_read_file, but would only support images (for now)?
There was a problem hiding this comment.
And do you still want to have a media-type parameter and if so, would it be ok, if it's optional? Because making it mandatory and defining media-specific options based on media-type would make the json-schema more complicated.
There was a problem hiding this comment.
hmm I think it's ok to allow image-only server_tool_read_media for now, maybe we should add a param type that can be image/audio/video in the future, tbd
There was a problem hiding this comment.
Very good, I'll rename the tool and leave the type parameter out for now.
Also I'd concentrate to only a single file for now:
I already have a branch that reads in multiple images successfully, but that required a small fix in agentic.svelte.ts (directly after the tool call, attachments have been displayed duplicated, after a page reload everything was fine again). That could lead to a regression in mcp tools and needsmore testing especially with multiple attachments. Also it's way more code to review and test. Especially the code that collects the list of files based on a glob expression still contains bugs. So it's probably a good idea to leave that code out for now.
hmm ok maybe better to skip video for now, as the file can be large. but audio might still be a valid use case for video, we might be better to allow /v1/chat/completion to read the file directly somehow, but I need to think more about it because that can introduce a huge security risk. the tools system doesn't apply because it's already allowed to read arbitrary files by default. |
- Rename server_tool_read_image to server_tool_read_media in C++ - Rename enum BuiltInTool.READ_IMAGE to READ_MEDIA - Rename UI constants, parser, and Svelte component files - Update display label from 'Read image' to 'Read media'
- rename parseToolResultWithImages to parseToolResultWithMedia - update all consumers (Default, ExecShellCommand blocks) to use line.media - rename image to media in ToolResultLine and parseToolResultWithMedia, it now holds images and audio - server-tools.cpp: add audio MIME types to get_mime_from_extension() - extend extractBase64Attachments() to detect audio/ MIME types and create AudioFile extras with base64Url - add AUDIO_MIME_TO_EXTENSION to mcp-resource.ts constants alongside IMAGE_MIME_TO_EXTENSION - widen ToolResultLine.image type to include DatabaseMessageExtraAudioFile - ChatMessageToolCallBlockReadMedia renders now <audio> for audio files - ChatMessageToolCallBlockDefault fallback renderer now also handles audio attachments
| } | ||
|
|
||
| // | ||
| // read_media: read a media file (image or audio) and return base64-encoded data with metadata |
There was a problem hiding this comment.
this is not the good placement in the file, move it to after the last tool definition above
There was a problem hiding this comment.
yes, indeed. section has been moved
|
@ggml-gh-bot review |
Automated code reviewReview of PR #25877 - I ran the always-on scope/security/general checks plus the Server checklist (touched paths are Blocking(point 1) Audio attachments are stored with the wrong field, breaking the type contract, model input, and UI rendering. { type: AttachmentType.AUDIO, name, mimeType, base64Url: trimmedLine }But
Decide on one shape and apply it consistently: either push (point 2) Server advertises audio formats the model-input path cannot handle. Will slow the review(point 3) (point 4) No test for the new builtin tool. Nits(point 5) Stray duplicated (point 6) Svelte indentation in (point 7) (point 8) Overall the metadata/parsing wiring (prefix constants on both sides, This review was generated automatically by pi coding agent using |
|
please address point 1, 2, 3; testing is optional, but recommended to add |




This adds a server-tool that allows vision models to analyze server-side images. It fixes #25875
Overview
This tool is reading only a single file for now:
The image data is base64 encoded and passed to the UI, which decodes it, fills the
<img>tag and removes the data URI before passing the tool result back to the model.Additional information
Requirements