char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
521a64cd0
char/aliceai-t5-moe
1/** 2* ReadMediaService - Reads local media files for the read_media tool 3* 4* Encodes image and audio files as base64 data URLs with the metadata the 5* model needs. No reactive state; consumed by toolsStore. 6*/ 7 8import { ToolsService } from './tools.service' ; 9import { 10FILE_EXTENSION_SEPARATOR , 11FILE_PATH_SEPARATOR_REGEX , 12NEWLINE , 13PREFIX_FILE , 14PREFIX_MIME , 15PREFIX_SIZE , 16READ_MEDIA_AUDIO_MIME , 17READ_MEDIA_IMAGE_MIME , 18RESP_TYPE_BASE64 19} from '$lib/constants' ; 20import { BuiltInTool , ToolResponseField } from '$lib/enums' ; 21import type { ToolExecutionResult } from '$lib/types' ; 22 23/** Modalities of the model the tool call runs for. */ 24export interface ReadMediaCapabilities { 25audio :boolean ; 26vision :boolean ; 27} 28 29/** Lowercase extension of a path, without the dot. Empty when the file name has none. */ 30function fileExtension ( path :string ) :string { 31const name = path . split ( FILE_PATH_SEPARATOR_REGEX ). pop () ?? '' ; 32const dot = name . lastIndexOf ( FILE_EXTENSION_SEPARATOR ); 33 34return dot > 0 ?name . slice ( dot + 1 ). toLowerCase () :'' ; 35} 36 37/** 38* **ReadMediaService** - browser executor for the `read_media` tool 39* 40* The tool is synthetic: no such tool exists on the server. It reads the file 41* through the server `read_file` tool with the `base64` response type, then 42* turns the bytes into a data URI line. The agentic store lifts that line into 43* an image or audio attachment on the tool result message, which is what makes 44* the model perceive the file instead of reading a wall of base64. 45* 46* Living in the browser is what lets it exist only for models that can 47* actually use the result - the server has no idea which model is selected. 48* 49* @see buildReadMediaToolDefinition in constants/read-media.ts - tool schema sent to the LLM 50* @see agenticStore in stores/agentic/index.svelte.ts - tool dispatch and attachment extraction 51*/ 52export class ReadMediaService { 53static async executeTool ( 54params :Record < string , unknown >, 55capabilities :ReadMediaCapabilities , 56signal ?:AbortSignal , 57cwd ?:string 58) :Promise < ToolExecutionResult > { 59const path = typeof params . path === 'string' ?params . path :'' ; 60 61if ( ! path ) { 62return { content :'Error: missing "path" argument.' , isError :true }; 63} 64 65const extension = fileExtension ( path ); 66const imageMime = READ_MEDIA_IMAGE_MIME [ extension ]; 67const audioMime = READ_MEDIA_AUDIO_MIME [ extension ]; 68 69let resolvedMime :string | undefined ; 70 71if ( imageMime && capabilities . vision ) resolvedMime = imageMime ; 72else if ( audioMime && capabilities . audio ) resolvedMime = audioMime ; 73 74if ( ! resolvedMime ) { 75const supported = [ 76 ...( capabilities . vision ?Object . keys ( READ_MEDIA_IMAGE_MIME ) :[]), 77 ...( capabilities . audio ?Object . keys ( READ_MEDIA_AUDIO_MIME ) :[]) 78]; 79// an unreadable-by-this-model file is a dead end, so say why instead of failing silently 80const reason = 81imageMime || audioMime 82 ?`the current model cannot perceive ". ${ extension } " files` 83 :`". ${ extension } " is not a supported media type` ; 84 85return { 86content :`Error: ${ reason } . Supported: ${ supported . join ( ', ' )} .` , 87isError :true 88}; 89} 90 91const raw = await ToolsService . executeToolRaw ( 92BuiltInTool . SERVER_READ_FILE , 93{ path}, 94signal , 95cwd , 96RESP_TYPE_BASE64 97); 98 99if ( ToolResponseField . ERROR in raw ) { 100return { content :String ( raw [ ToolResponseField . ERROR ]), isError :true }; 101} 102 103const base64 = typeof raw . base64 === 'string' ?raw . base64 :'' ; 104 105if ( ! base64 ) { 106return { content :`Error: no data returned for ${ path } .` , isError :true }; 107} 108 109const sizeBytes = typeof raw . size_bytes === 'number' ?raw . size_bytes :0 ; 110const content = [ 111` ${ PREFIX_FILE } ${ path } ` , 112` ${ PREFIX_SIZE } ${ sizeBytes } bytes` , 113` ${ PREFIX_MIME } ${ resolvedMime } ` , 114`data: ${ resolvedMime } ;base64, ${ base64 } ` 115]. join ( NEWLINE ); 116 117return { content, isError :false }; 118} 119}