char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
521a64cd0
char/aliceai-t5-moe
1/** 2* ToolsService - Stateless server tools API layer 3* 4* Fetches the server's /tools listing and streams tool execution results. 5* No reactive state; consumed by toolsStore. 6*/ 7 8import { base } from '$app/paths' ; 9import { API_TOOLS , HEADERS } from '$lib/constants' ; 10import { ToolResponseField } from '$lib/enums' ; 11import type { ServerToolInfo , ToolExecutionResult } from '$lib/types' ; 12import { apiFetch } from '$lib/utils' ; 13import { getJsonHeaders } from '$lib/utils/api-headers' ; 14import { parseSseJsonStream , type SseJsonEvent } from '$lib/utils/sse' ; 15 16export class ToolsService { 17/** 18* Execute a server tool on the server. 19* 20* @param cwd - Working directory for the tool call, sent as the 21* x-tool-cwd request header. The server resolves relative paths 22* against it; the model cannot override it. 23*/ 24static async executeTool ( 25toolName :string , 26params :Record < string , unknown >, 27signal ?:AbortSignal , 28cwd ?:string 29) :Promise < ToolExecutionResult > { 30const result = await apiFetch < Record < string , unknown >>( API_TOOLS . EXECUTE , { 31body :JSON . stringify ({ params, tool :toolName }), 32headers :cwd ?{ [ HEADERS . X_TOOL_CWD_HEADER ] :cwd } :undefined , 33method :'POST' , 34 signal 35}); 36 37if ( ToolResponseField . ERROR in result ) { 38return { content :String ( result [ ToolResponseField . ERROR ]), isError :true }; 39} 40 41if ( ToolResponseField . PLAIN_TEXT in result ) { 42return { content :String ( result [ ToolResponseField . PLAIN_TEXT ]), isError :false }; 43} 44 45return { content :JSON . stringify ( result ), isError :false }; 46} 47 48/** 49* Execute a server tool and return the raw JSON response. Unlike 50* executeTool, this preserves structured fields (e.g. file_glob_search's 51* `entries` and `base`) that the flattened ToolExecutionResult drops. 52* 53* @param respType - sent as the x-resp-type request header. Only read_file 54* honors it, with `base64` to get the raw bytes instead of decoded text. 55*/ 56static async executeToolRaw ( 57toolName :string , 58params :Record < string , unknown >, 59signal ?:AbortSignal , 60cwd ?:string , 61respType ?:string 62) :Promise < Record < string , unknown >> { 63const headers :Record < string , string > = {}; 64 65if ( cwd ) headers [ HEADERS . X_TOOL_CWD_HEADER ] = cwd ; 66 67if ( respType ) headers [ HEADERS . X_RESP_TYPE_HEADER ] = respType ; 68 69return apiFetch < Record < string , unknown >>( API_TOOLS . EXECUTE , { 70body :JSON . stringify ({ params, tool :toolName }), 71headers :Object . keys ( headers ). length > 0 ?headers :undefined , 72method :'POST' , 73 signal 74}); 75} 76 77/** 78* Fetch the list of server tools from the server. 79* 80* @returns Array of tool definitions in OpenAI-compatible format 81*/ 82static async list () :Promise < ServerToolInfo []> { 83return apiFetch < ServerToolInfo []>( API_TOOLS . LIST ); 84} 85 86/** 87* Stream a server tool's output chunks from the server. The server 88* `POST /tools` endpoint with `{stream: true}` emits `data: {"chunk": "..."}` 89* events followed by a terminal `data: {"done": true}` (optionally with 90* `error`). Yields the chunk string for each partial event. 91* 92* The terminal event's `error` field, if present, is yielded as a final 93* synthetic chunk prefixed with an error marker so the accumulated content 94* already carries the failure context for the caller. 95* 96* Throws synchronously if the server rejects the request (e.g. tool does 97* not support streaming, or 4xx/5xx response). The HTTP fetch goes through 98* a minimal text/event-stream reader since the chat SSE parser in 99* chat.service.ts embeds extra resume logic that is unnecessary here. 100*/ 101static async * streamTool ( 102toolName :string , 103params :Record < string , unknown >, 104signal ?:AbortSignal , 105cwd ?:string 106) :AsyncGenerator < ToolStreamEvent > { 107const headers = getJsonHeaders (); 108 109if ( cwd ) headers [ HEADERS . X_TOOL_CWD_HEADER ] = cwd ; 110 111const response = await fetch ( ` ${ base } ${ API_TOOLS . EXECUTE } ` , { 112body :JSON . stringify ({ params, stream :true , tool :toolName }), 113 headers, 114method :'POST' , 115 signal 116}); 117 118if ( ! response . ok || ! response . body ) { 119const detail = await formatNonOkResponse ( response ); 120 121throw new Error ( detail ); 122} 123 124const iterator = parseSseJsonStream < ToolServerEvent >( response , signal ); 125 126while ( true ) { 127const next :IteratorResult < SseJsonEvent < ToolServerEvent >> = await iterator . next (); 128 129if ( next . done ) return ; 130 131const event = next . value . data ; 132 133if ( event . chunk !== undefined ) { 134yield { chunk :event . chunk , done :false }; 135} 136 137if ( event . done ) { 138yield { chunk :null , done :true , error :event . error }; 139 140return ; 141} 142} 143} 144} 145 146/** 147* One event from streaming a tool's output. 148* - During execution: `chunk` is a non-empty text fragment, `done: false`. 149* - On terminal event: `done: true`, `error` populated if the call failed, 150* and `chunk` is null. 151*/ 152export interface ToolStreamEvent { 153chunk :string | null ; 154done :boolean ; 155error ?:string ; 156} 157 158/** Wire shape of one SSE event from `POST /tools?stream=true`. */ 159interface ToolServerEvent { 160chunk ?:string ; 161done ?:boolean ; 162error ?:string ; 163} 164 165async function formatNonOkResponse ( response :Response ) :Promise < string > { 166const status = ` ${ response . status } ${ response . statusText } ` . trim (); 167 168try { 169const errBody = ( await response . clone (). json ()) as { error ?:string ; message ?:string }; 170 171if ( errBody ?. error ) return ` ${ status } : ${ errBody . error } ` ; 172 173if ( errBody ?. message ) return ` ${ status } : ${ errBody . message } ` ; 174} catch ( error ) { 175console . error ( '[tools] Non-JSON error response, falling back to raw text:' , error ); 176try { 177const text = await response . text (); 178 179if ( text . trim ()) return ` ${ status } : ${ text . trim ()} ` ; 180} catch ( error ) { 181console . error ( '[tools] Failed to read error response as text:' , error ); 182} 183} 184 185return status || `HTTP ${ response . status } ` ; 186}