ui: add read_media tool (#25877)
* server: add read_image tool (#25875) Adds a server-tool that allows vision models to analyze server-side images. This tool is reading 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. * cleanup read_image tool: move magic strings to constants * 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 * server: rename read_image tool to read_media for images and audio * 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' * ui: consolidate audio data URI handling into shared utility * Extract getAudioInputFormat to a shared utility (was duplicated inline) * Store raw base64 in base64Data on the message object * Use base64Data to construct data URIs for audio rendering * Update agentic store to build INPUT_AUDIO parts from base64Data * server: read_media: restrict audio to wav/mp3 and minor fixes * Server get_mime_from_extension now only advertises audio/wav and audio/mpeg (the only formats the model's input_audio API accepts) * Case-insensitive extension matching (fixes .MP3, .Wav, etc.) * Unknown extensions return an error instead of a multi-MB data URI that inflates model context with garbage * Updated tool description to document supported formats * Frontend AUDIO_MIME_TO_EXTENSION trimmed to match server * fix a missing import in tools/ui/src/lib/stores/agentic.svelte.ts * server: read_media: add to --tools help text and README tool list * ui: fix indentation in ChatMessageToolCallBlockDefault.svelte * server: read_media tool: fix a cast to use the correct type * server: read_media: multiple fixes * server-tools.cpp import cctype, remove UTF-8 char, check mime before reading file * ui: add MimeTypePrefix.AUDIO and use it in agentic.svelte.ts * server: make read_media inherit from read_file and add uses_cwd * ui: fix formating issues * rm from server * move it to frontend-only tool * correct partial commit * rm unused * ui: address review from allozaur Replace the magic strings, regexes and number in the read_media parser and service with named constants. Path splitting reuses FILE_PATH_SEPARATOR_REGEX, the size header regex moves to READ_MEDIA_SIZE_REGEX derived from PREFIX_SIZE, and FILE_EXTENSION_SEPARATOR lands next to it in constants/code.ts. --------- Co-authored-by: ckrafft <ckrafft@epyc> Co-authored-by: Xuan Son Nguyen <son@huggingface.co> Co-authored-by: Pascal <admin@serveurperso.com>
This commit is contained in:
co-authored by
ckrafft
Xuan Son Nguyen
Pascal
parent
89e0aa6fd3
commit
4dd127584b
@@ -50,11 +50,11 @@ export interface AgenticSection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a tool result line that may reference an image attachment
|
||||
* Represents a tool result line that may reference a media attachment (image or audio)
|
||||
*/
|
||||
export type ToolResultLine = {
|
||||
text: string;
|
||||
image?: DatabaseMessageExtraImageFile;
|
||||
media?: DatabaseMessageExtraImageFile | DatabaseMessageExtraAudioFile;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -301,16 +301,16 @@ export function splitSearchSummaryList(
|
||||
return { lines };
|
||||
}
|
||||
|
||||
/** Bounded cache for parseToolResultWithImages results. */
|
||||
/** Bounded cache for parseToolResultWithMedia results. */
|
||||
const TOOL_RESULT_LINES_CACHE_MAX_SIZE = 32;
|
||||
const toolResultLinesCache = new Map<string, ToolResultLine[]>();
|
||||
|
||||
/**
|
||||
* Parse tool result text into lines, matching image attachments by name.
|
||||
* Parse tool result text into lines, matching media attachments (images and audio) by name.
|
||||
* Memoized: called per render during streaming on unchanged tool result
|
||||
* strings with unchanged extras.
|
||||
*/
|
||||
export function parseToolResultWithImages(
|
||||
export function parseToolResultWithMedia(
|
||||
toolResult: string,
|
||||
extras?: DatabaseMessageExtra[]
|
||||
): ToolResultLine[] {
|
||||
@@ -332,12 +332,13 @@ export function parseToolResultWithImages(
|
||||
if (!match || !extras) return { text: line };
|
||||
|
||||
const attachmentName = match[1];
|
||||
const image = extras.find(
|
||||
(e): e is DatabaseMessageExtraImageFile =>
|
||||
e.type === AttachmentType.IMAGE && e.name === attachmentName
|
||||
const media = extras.find(
|
||||
(e): e is DatabaseMessageExtraImageFile | DatabaseMessageExtraAudioFile =>
|
||||
(e.type === AttachmentType.IMAGE || e.type === AttachmentType.AUDIO) &&
|
||||
e.name === attachmentName
|
||||
);
|
||||
|
||||
return { image, text: line };
|
||||
return { media, text: line };
|
||||
});
|
||||
|
||||
if (toolResultLinesCache.size >= TOOL_RESULT_LINES_CACHE_MAX_SIZE) {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { FileTypeAudio, MimeTypeAudio } from '$lib/enums';
|
||||
import type { AudioInputFormat } from '$lib/types/api';
|
||||
|
||||
/**
|
||||
* Map a MIME type to the AudioInputFormat expected by the API.
|
||||
*/
|
||||
export function getAudioInputFormat(mimeType: string): AudioInputFormat {
|
||||
const normalizedMimeType = mimeType.trim().toLowerCase();
|
||||
|
||||
if (
|
||||
normalizedMimeType === MimeTypeAudio.WAV ||
|
||||
normalizedMimeType === MimeTypeAudio.WAVE ||
|
||||
normalizedMimeType === MimeTypeAudio.X_WAV ||
|
||||
normalizedMimeType === MimeTypeAudio.X_WAVE ||
|
||||
normalizedMimeType === MimeTypeAudio.VND_WAVE ||
|
||||
normalizedMimeType === MimeTypeAudio.X_PN_WAV
|
||||
) {
|
||||
return FileTypeAudio.WAV;
|
||||
}
|
||||
|
||||
return FileTypeAudio.MP3;
|
||||
}
|
||||
@@ -248,7 +248,7 @@ export {
|
||||
export {
|
||||
deriveAgenticSections,
|
||||
buildAssistantRawOutput,
|
||||
parseToolResultWithImages,
|
||||
parseToolResultWithMedia,
|
||||
splitSearchSummaryList,
|
||||
hasAgenticContent,
|
||||
classifyToolResult,
|
||||
@@ -325,3 +325,6 @@ export { uuid } from './uuid';
|
||||
|
||||
// CSS utilities
|
||||
export { remToPx } from './css';
|
||||
|
||||
// Audio format helper (used by agentic store and chat service)
|
||||
export { getAudioInputFormat } from './audio-format';
|
||||
|
||||
Reference in New Issue
Block a user