From fa23e9da13c49a9c3bd364f6923af69d43da5023 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 18:40:05 +0530 Subject: [PATCH 001/187] SimpleChatToolCalling: Test/Explore srvr initial hs using cmdline --- .../public_simplechat/test-tools-cmdline.sh | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tools/server/public_simplechat/test-tools-cmdline.sh diff --git a/tools/server/public_simplechat/test-tools-cmdline.sh b/tools/server/public_simplechat/test-tools-cmdline.sh new file mode 100644 index 0000000000000..6aa97753c904e --- /dev/null +++ b/tools/server/public_simplechat/test-tools-cmdline.sh @@ -0,0 +1,83 @@ +echo "DONT FORGET TO RUN llama-server" +echo "build/bin/llama-server -m ~/Downloads/GenAi.Text/gemma-3n-E4B-it-Q8_0.gguf --path tools/server/public_simplechat --jinja" +curl http://localhost:8080/v1/chat/completions -d '{ + "model": "gpt-3.5-turbo", + "tools": [ + { + "type":"function", + "function":{ + "name":"javascript", + "description":"Runs code in an javascript interpreter and returns the result of the execution after 60 seconds.", + "parameters":{ + "type":"object", + "properties":{ + "code":{ + "type":"string", + "description":"The code to run in the javascript interpreter." + } + }, + "required":["code"] + } + } + }, + { + "type":"function", + "function":{ + "name":"web_fetch", + "description":"Connects to the internet and fetches the specified url, may take few seconds", + "parameters":{ + "type":"object", + "properties":{ + "url":{ + "type":"string", + "description":"The url to fetch from internet." + } + }, + "required":["url"] + } + } + }, + { + "type":"function", + "function":{ + "name":"simple_calc", + "description":"Calculates the provided arithmatic expression using javascript interpreter and returns the result of the execution after few seconds.", + "parameters":{ + "type":"object", + "properties":{ + "arithexp":{ + "type":"string", + "description":"The arithmatic expression that will be calculated using javascript interpreter." + } + }, + "required":["arithexp"] + } + } + } + ], + "messages": [ + { + "role": "user", + "content": "Add 324 to todays temperature in celcius in kochi. Dont forget to get todays weather info about kochi so that the temperature is valid. Use a free weather info site which doesnt require any api keys to get the info" + } + ] +}' + + +exit + + + "content": "Print a hello world message with python." + "content": "Print a hello world message with javascript." + "content": "Calculate the sum of 5 and 27." + "content": "Can you get me todays date." + "content": "Can you get todays date. And inturn add 10 to todays date" + "content": "Who is known as father of the nation in India, also is there a similar figure for USA as well as UK" + "content": "Who is known as father of the nation in India, Add 10 to double his year of birth and show me the results." + "content": "How is the weather today in london." + "content": "How is the weather today in london. Add 324 to todays temperature in celcius in london" + "content": "How is the weather today in london. Add 324 to todays temperature in celcius in kochi" + "content": "Add 324 to todays temperature in celcius in london" + "content": "Add 324 to todays temperature in celcius in delhi. Dont forget to get todays weather info about delhi so that the temperature is valid" + "content": "Add 324 to todays temperature in celcius in mumbai. Dont forget to get todays weather info about mumbai so that the temperature is valid. Use a free weather info site which doesnt require any api keys to get the info" + "content": "Can you get the cutoff rank for all the deemed medical universities in India for UGNeet 25" From 75ce9e47e1035c3a1e33b70188337f1b535986a1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 20:18:16 +0530 Subject: [PATCH 002/187] SimpleChatTools: Add boolean to allow user control of tools use --- tools/server/public_simplechat/simplechat.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 2fcd24a860bd4..bed0afbcdf559 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -726,6 +726,7 @@ class Me { this.defaultChatIds = [ "Default", "Other" ]; this.multiChat = new MultiChatUI(); this.bStream = true; + this.bTools = false; this.bCompletionFreshChatAlways = true; this.bCompletionInsertStandardRolePrefix = false; this.bTrimGarbage = true; @@ -804,6 +805,8 @@ class Me { ui.el_create_append_p(`bStream:${this.bStream}`, elDiv); + ui.el_create_append_p(`bTools:${this.bTools}`, elDiv); + ui.el_create_append_p(`bTrimGarbage:${this.bTrimGarbage}`, elDiv); ui.el_create_append_p(`ApiEndPoint:${this.apiEP}`, elDiv); @@ -878,6 +881,11 @@ class Me { }); elDiv.appendChild(bb.div); + bb = ui.el_creatediv_boolbutton("SetTools", "Tools", {true: "[+] yes tools", false: "[-] no tools"}, this.bTools, (val)=>{ + this.bTools = val; + }); + elDiv.appendChild(bb.div); + bb = ui.el_creatediv_boolbutton("SetTrimGarbage", "TrimGarbage", {true: "[+] yes trim", false: "[-] dont trim"}, this.bTrimGarbage, (val)=>{ this.bTrimGarbage = val; }); From 68fc28f0ec7e4f11f5a00182a9c68c3ed8c23b42 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 23:02:30 +0530 Subject: [PATCH 003/187] SimpleChatTC: Update test shell script a bit Enable streaming by default, to check the handshake before going on to change the code, given that havent looked into this for more than a year now and have been busy with totally different stuff. Also updated the user messages used for testing a bit --- tools/server/public_simplechat/test-tools-cmdline.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/test-tools-cmdline.sh b/tools/server/public_simplechat/test-tools-cmdline.sh index 6aa97753c904e..e5fb7652f4727 100644 --- a/tools/server/public_simplechat/test-tools-cmdline.sh +++ b/tools/server/public_simplechat/test-tools-cmdline.sh @@ -1,7 +1,10 @@ echo "DONT FORGET TO RUN llama-server" echo "build/bin/llama-server -m ~/Downloads/GenAi.Text/gemma-3n-E4B-it-Q8_0.gguf --path tools/server/public_simplechat --jinja" +echo "Note: Remove stream: true line below, if you want one shot instead of streaming response from ai server" +echo "Note: Using different locations below, as the mechanism / url used to fetch will / may need to change" curl http://localhost:8080/v1/chat/completions -d '{ "model": "gpt-3.5-turbo", + "stream": true, "tools": [ { "type":"function", @@ -58,7 +61,7 @@ curl http://localhost:8080/v1/chat/completions -d '{ "messages": [ { "role": "user", - "content": "Add 324 to todays temperature in celcius in kochi. Dont forget to get todays weather info about kochi so that the temperature is valid. Use a free weather info site which doesnt require any api keys to get the info" + "content": "what is your name." } ] }' @@ -67,6 +70,7 @@ curl http://localhost:8080/v1/chat/completions -d '{ exit + "content": "what is your name." "content": "Print a hello world message with python." "content": "Print a hello world message with javascript." "content": "Calculate the sum of 5 and 27." @@ -76,8 +80,9 @@ exit "content": "Who is known as father of the nation in India, Add 10 to double his year of birth and show me the results." "content": "How is the weather today in london." "content": "How is the weather today in london. Add 324 to todays temperature in celcius in london" - "content": "How is the weather today in london. Add 324 to todays temperature in celcius in kochi" + "content": "How is the weather today in bengaluru. Add 324 to todays temperature in celcius in kochi" "content": "Add 324 to todays temperature in celcius in london" + "content": "Add 324 to todays temperature in celcius in delhi" "content": "Add 324 to todays temperature in celcius in delhi. Dont forget to get todays weather info about delhi so that the temperature is valid" - "content": "Add 324 to todays temperature in celcius in mumbai. Dont forget to get todays weather info about mumbai so that the temperature is valid. Use a free weather info site which doesnt require any api keys to get the info" + "content": "Add 324 to todays temperature in celcius in bengaluru. Dont forget to get todays weather info about bengaluru so that the temperature is valid. Use a free weather info site which doesnt require any api keys to get the info" "content": "Can you get the cutoff rank for all the deemed medical universities in India for UGNeet 25" From 85845a011cb7f21326ef1a29c0b0b4061bbd9fec Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 23:31:36 +0530 Subject: [PATCH 004/187] SimpleChatTC: Add skeleton for a javascript interpretor tool call Define the meta that needs to be passed to the GenAi Engine. Define the logic that implements the tool call, if called. Implement the flow/structure such that a single tool calls implementation file can define multiple tool calls. --- tools/server/public_simplechat/tooljs.mjs | 40 +++++++++++++++++++++++ tools/server/public_simplechat/tools.mjs | 7 ++++ 2 files changed, 47 insertions(+) create mode 100644 tools/server/public_simplechat/tooljs.mjs create mode 100644 tools/server/public_simplechat/tools.mjs diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs new file mode 100644 index 0000000000000..cfbdc61e56b63 --- /dev/null +++ b/tools/server/public_simplechat/tooljs.mjs @@ -0,0 +1,40 @@ +//@ts-check +// Helpers to handle tools/functions calling +// by Humans for All +// + + +let metas = [ + { + "type":"function", + "function":{ + "name": "javascript", + "description":"Runs code in an javascript interpreter and returns the result of the execution after 60 seconds.", + "parameters":{ + "type":"object", + "properties":{ + "code":{ + "type":"string", + "description":"The code to run in the javascript interpreter." + } + }, + "required":["code"] + } + } + } +] + + +/** + * Implementation of the javascript interpretor logic. Minimal skeleton for now. + * @param {any} obj + */ +function tool_run(obj) { + let func = new Function(obj["code"]) + func() +} + +let tswitch = { + "javascript": tool_run, +} + diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs new file mode 100644 index 0000000000000..6d0b375447cb7 --- /dev/null +++ b/tools/server/public_simplechat/tools.mjs @@ -0,0 +1,7 @@ +//@ts-check +// Helpers to handle tools/functions calling +// by Humans for All +// + + +import * as tjs from './tooljs.mjs' \ No newline at end of file From bbaae70503d56fc24dc2618beee951b12eae20c0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 00:02:32 +0530 Subject: [PATCH 005/187] SimpleChatTC: More generic tooljs, SimpCalc, some main skeleton Make tooljs structure and flow more generic Add a simple_calculator tool/function call logic Add initial skeleton wrt the main tools.mjs file. --- tools/server/public_simplechat/tooljs.mjs | 77 ++++++++++++++++++----- tools/server/public_simplechat/tools.mjs | 26 +++++++- 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index cfbdc61e56b63..1796bfaa2b1f0 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -1,40 +1,83 @@ //@ts-check -// Helpers to handle tools/functions calling +// DANGER DANGER DANGER - Simple and Stupid - Use from a discardable VM only +// Helpers to handle tools/functions calling wrt +// * javascript interpreter +// * simple arithmatic calculator // by Humans for All // -let metas = [ - { - "type":"function", - "function":{ +let js_meta = { + "type": "function", + "function": { "name": "javascript", - "description":"Runs code in an javascript interpreter and returns the result of the execution after 60 seconds.", - "parameters":{ - "type":"object", - "properties":{ - "code":{ - "type":"string", - "description":"The code to run in the javascript interpreter." + "description": "Runs code in an javascript interpreter and returns the result of the execution after few seconds", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The code to run in the javascript interpreter." } }, - "required":["code"] + "required": ["code"] } } } -] /** * Implementation of the javascript interpretor logic. Minimal skeleton for now. + * ALERT: Has access to the javascript environment and can mess with it and beyond * @param {any} obj */ -function tool_run(obj) { +function js_run(obj) { let func = new Function(obj["code"]) func() } -let tswitch = { - "javascript": tool_run, + +let calc_meta = { + "type": "function", + "function": { + "name": "simple_calculator", + "description": "Calculates the provided arithmatic expression using javascript interpreter and returns the result of the execution after few seconds", + "parameters": { + "type": "object", + "properties": { + "arithexpr":{ + "type":"string", + "description":"The arithmatic expression that will be calculated using javascript interpreter." + } + }, + "required": ["arithexpr"] + } + } + } + + +/** + * Implementation of the simple calculator logic. Minimal skeleton for now. + * ALERT: Has access to the javascript environment and can mess with it and beyond + * @param {any} obj + */ +function calc_run(obj) { + let func = new Function(obj["arithexpr"]) + func() +} + + +/** + * @type {Object} + */ +export let tc_switch = { + "javascript": { + "handler": js_run, + "meta": js_meta + }, + "simple_calculator": { + "handler": calc_run, + "meta": calc_meta + } } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 6d0b375447cb7..ba80e30a91347 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -1,7 +1,29 @@ //@ts-check -// Helpers to handle tools/functions calling +// DANGER DANGER DANGER - Simple and Stupid - Use from a discardable VM only +// Helpers to handle tools/functions calling in a direct and dangerous way // by Humans for All // -import * as tjs from './tooljs.mjs' \ No newline at end of file +import * as tjs from './tooljs.mjs' + + +/** + * @type {Object} + */ +let tc_switch = {} + +export function setup() { + for (const key in tjs.tc_switch) { + tc_switch[key] = tjs.tc_switch[key] + } +} + +export function meta() { + let tools = [] + for (const key in tc_switch) { + tools.push(tc_switch[key]["meta"]) + } + return tools +} + From f09156831c4399cab69e11686d4cd743b022fa84 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 00:18:37 +0530 Subject: [PATCH 006/187] SimpleChatTC: Bring in the tools meta into the main flow --- tools/server/public_simplechat/simplechat.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index bed0afbcdf559..6f33457e733f2 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -4,6 +4,8 @@ import * as du from "./datautils.mjs"; import * as ui from "./ui.mjs" +import * as tools from "./tools.mjs" + class Roles { static System = "system"; @@ -228,6 +230,9 @@ class SimpleChat { if (gMe.bStream) { obj["stream"] = true; } + if (gMe.bTools) { + obj["tools"] = tools.meta(); + } return JSON.stringify(obj); } @@ -927,6 +932,8 @@ function startme() { gMe.debug_disable(); document["gMe"] = gMe; document["du"] = du; + document["tools"] = tools; + tools.setup() for (let cid of gMe.defaultChatIds) { gMe.multiChat.new_chat_session(cid); } From 9d8be85b0eac91a901bf1693d6da5e029702d00c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 01:06:23 +0530 Subject: [PATCH 007/187] SimpleChatTC: use tcpdump to dbg hs; check if ai aware of tools --- tools/server/public_simplechat/test-tools-cmdline.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/test-tools-cmdline.sh b/tools/server/public_simplechat/test-tools-cmdline.sh index e5fb7652f4727..adea59cb6c279 100644 --- a/tools/server/public_simplechat/test-tools-cmdline.sh +++ b/tools/server/public_simplechat/test-tools-cmdline.sh @@ -2,6 +2,7 @@ echo "DONT FORGET TO RUN llama-server" echo "build/bin/llama-server -m ~/Downloads/GenAi.Text/gemma-3n-E4B-it-Q8_0.gguf --path tools/server/public_simplechat --jinja" echo "Note: Remove stream: true line below, if you want one shot instead of streaming response from ai server" echo "Note: Using different locations below, as the mechanism / url used to fetch will / may need to change" +echo "Note: sudo tcpdump -i lo -s 0 -vvv -A host 127.0.0.1 and port 8080 | tee /tmp/td.log can be used to capture the hs" curl http://localhost:8080/v1/chat/completions -d '{ "model": "gpt-3.5-turbo", "stream": true, @@ -61,7 +62,7 @@ curl http://localhost:8080/v1/chat/completions -d '{ "messages": [ { "role": "user", - "content": "what is your name." + "content": "What and all tools you have access to" } ] }' @@ -71,6 +72,7 @@ exit "content": "what is your name." + "content": "What and all tools you have access to" "content": "Print a hello world message with python." "content": "Print a hello world message with javascript." "content": "Calculate the sum of 5 and 27." From 2e4693cf0dfa0f06023eb81ba79190562da0a42e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 01:17:19 +0530 Subject: [PATCH 008/187] SimpleChatTC: Skeleton to handle diff fields when streaming Changed latestResponse type to an object instead of a string. Inturn it contains entries for content, toolname and toolargs. Added a custom clear logic due to the same and used it to replace the previously simple assigning of empty string to latestResponse. For now in all places where latestReponse is used, I have replaced with latestReponse.content. Next need to handle identifying the field being streamed and inturn append to it. Also need to add logic to call tool, when tool_call triggered by genai. --- tools/server/public_simplechat/simplechat.js | 22 ++++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 6f33457e733f2..bc3d2f00f566f 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -72,7 +72,7 @@ class SimpleChat { */ this.xchat = []; this.iLastSys = -1; - this.latestResponse = ""; + this.latestResponse = { content: "", toolname: "", toolargs: "" }; } clear() { @@ -80,6 +80,10 @@ class SimpleChat { this.iLastSys = -1; } + clear_latestresponse() { + this.latestResponse = { content: "", toolname: "", toolargs: "" }; + } + ods_key() { return `SimpleChat-${this.chatId}` } @@ -151,7 +155,7 @@ class SimpleChat { * @param {string} content */ append_response(content) { - this.latestResponse += content; + this.latestResponse.content += content; } /** @@ -392,7 +396,7 @@ class SimpleChat { } let tdUtf8 = new TextDecoder("utf-8"); let rr = resp.body.getReader(); - this.latestResponse = ""; + this.clear_latestresponse() let xLines = new du.NewLines(); while(true) { let { value: cur, done: done } = await rr.read(); @@ -419,14 +423,14 @@ class SimpleChat { console.debug("DBUG:SC:PART:Json:", curJson); this.append_response(this.response_extract_stream(curJson, apiEP)); } - elP.innerText = this.latestResponse; + elP.innerText = this.latestResponse.content; elP.scrollIntoView(false); if (done) { break; } } - console.debug("DBUG:SC:PART:Full:", this.latestResponse); - return this.latestResponse; + console.debug("DBUG:SC:PART:Full:", this.latestResponse.content); + return this.latestResponse.content; } /** @@ -455,11 +459,11 @@ class SimpleChat { if (gMe.bStream) { try { theResp.assistant = await this.handle_response_multipart(resp, apiEP, elDiv); - this.latestResponse = ""; + this.clear_latestresponse() } catch (error) { - theResp.assistant = this.latestResponse; + theResp.assistant = this.latestResponse.content; this.add(Roles.Assistant, theResp.assistant); - this.latestResponse = ""; + this.clear_latestresponse() throw error; } } else { From 27161cb110fda1b534802b636c0788aa3f77e36b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 01:46:23 +0530 Subject: [PATCH 009/187] SimpleChatTC: Extract streamed field - assume only 1f at any time Update response_extract_stream to check for which field is being currently streamed ie is it normal content or tool call func name or tool call func args and then return the field name and extracted value. Previously it was always assumed that only normal content will be returned. Currently it is assumed that the server will only stream one of the 3 supported fields at any time and not more than one of them at the same time. TODO: Have to also add logic to extract the reasoning field later, ie wrt gen ai models which give out their thinking. Have updated append_response to expect both the key and the value wrt the latestResponse object, which it will be manipualted. Previously it was always assumed that content is what will be got and inturn appended. --- tools/server/public_simplechat/simplechat.js | 27 +++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index bc3d2f00f566f..5adae099715ac 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -152,10 +152,10 @@ class SimpleChat { /** * Collate the latest response from the server/ai-model, as it is becoming available. * This is mainly useful for the stream mode. - * @param {string} content + * @param {{key: string, value: string}} resp */ - append_response(content) { - this.latestResponse.content += content; + append_response(resp) { + this.latestResponse[resp.key] += resp.value; } /** @@ -311,10 +311,25 @@ class SimpleChat { * @param {string} apiEP */ response_extract_stream(respBody, apiEP) { + let key = "content" let assistant = ""; if (apiEP == ApiEP.Type.Chat) { - if (respBody["choices"][0]["finish_reason"] !== "stop") { - assistant = respBody["choices"][0]["delta"]["content"]; + if (respBody["choices"][0]["finish_reason"] !== null) { + if (respBody["choices"][0]["delta"]["content"] !== undefined) { + assistant = respBody["choices"][0]["delta"]["content"]; + } else { + if (respBody["choices"][0]["delta"]["tool_calls"] !== undefined) { + if (respBody["choices"][0]["delta"]["tool_calls"][0]["function"]["name"] !== undefined) { + key = "toolname"; + assistant = respBody["choices"][0]["delta"]["tool_calls"][0]["function"]["name"]; + } else { + if (respBody["choices"][0]["delta"]["tool_calls"][0]["function"]["arguments"] !== undefined) { + key = "toolargs"; + assistant = respBody["choices"][0]["delta"]["tool_calls"][0]["function"]["arguments"]; + } + } + } + } } } else { try { @@ -323,7 +338,7 @@ class SimpleChat { assistant = respBody["content"]; } } - return assistant; + return { key: key, value: assistant }; } /** From 788d56aeee3d56c2c03d04a2e3c737b9ce6fdf5e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 02:35:01 +0530 Subject: [PATCH 010/187] SimpleChatTC: Avoid null content, Fix oversight wrt finish_reason I was wrongly checking for finish_reason to be non null, before trying to extract the genai content/toolcalls, have fixed this oversight with the new flow in progress. I had added few debug logs to identify the above issue, need to remove them later. Note: given that debug logs are disabled by replacing the debug function during this program's initialisation, which I had forgotten about, I didnt get the debug messages and had to scratch my head a bit, before realising this and the other issue ;) Also either when I had originally implemented simplechat 1+ years back, or later due to changes on the server end, the streaming flow sends a initial null wrt the content, where it only sets the role. This was not handled in my flow on the client side, so a null was getting prepended to the chat messages/responses from the server. This has been fixed now in the new generic flow. --- tools/server/public_simplechat/simplechat.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 5adae099715ac..cd075f37bdf04 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -155,6 +155,10 @@ class SimpleChat { * @param {{key: string, value: string}} resp */ append_response(resp) { + if (resp.value == null) { + return + } + console.debug(resp.key, resp.value) this.latestResponse[resp.key] += resp.value; } @@ -311,10 +315,11 @@ class SimpleChat { * @param {string} apiEP */ response_extract_stream(respBody, apiEP) { + console.debug(respBody, apiEP) let key = "content" let assistant = ""; if (apiEP == ApiEP.Type.Chat) { - if (respBody["choices"][0]["finish_reason"] !== null) { + if (respBody["choices"][0]["finish_reason"] === null) { if (respBody["choices"][0]["delta"]["content"] !== undefined) { assistant = respBody["choices"][0]["delta"]["content"]; } else { From 4cbe1d291aba4714d73cd8460b2604e3f29001b4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 02:58:03 +0530 Subject: [PATCH 011/187] SimpleChatTC: Show toolcall being generated by ai - Temp --- tools/server/public_simplechat/simplechat.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index cd075f37bdf04..4fefe48ea4ab5 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -443,7 +443,11 @@ class SimpleChat { console.debug("DBUG:SC:PART:Json:", curJson); this.append_response(this.response_extract_stream(curJson, apiEP)); } - elP.innerText = this.latestResponse.content; + if (this.latestResponse.content !== "") { + elP.innerText = this.latestResponse.content; + } else { + elP.innerText = `ToolCall:${this.latestResponse.toolname}:${this.latestResponse.toolargs}`; + } elP.scrollIntoView(false); if (done) { break; From 174b0b1548744cdf660a8bccc5284f83aafeb04a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 03:14:27 +0530 Subject: [PATCH 012/187] SimpleChatTC: AssistantResponse class initial go Make latestResponse into a new class based type instance wrt ai assistant response, which is what it represents. Move clearing, appending fields' values and getting assistant's response info (irrespective of a content or toolcall response) into this new class and inturn use the same. --- tools/server/public_simplechat/simplechat.js | 70 ++++++++++++-------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 4fefe48ea4ab5..510749c17c88d 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -37,6 +37,39 @@ class ApiEP { } +class AssistantResponse { + + constructor() { + this.response = { content: "", toolname: "", toolargs: "" }; + } + + clear() { + this.response = { content: "", toolname: "", toolargs: "" }; + } + + /** + * Helps collate the latest response from the server/ai-model, as it is becoming available. + * This is mainly useful for the stream mode. + * @param {{key: string, value: string}} resp + */ + append_response(resp) { + if (resp.value == null) { + return + } + console.debug(resp.key, resp.value) + this.response[resp.key] += resp.value; + } + + content_equiv() { + if (this.response.content !== "") { + return this.response.content; + } else { + return `ToolCall:${this.response.toolname}:${this.response.toolargs}`; + } + } + +} + let gUsageMsg = `

Usage

@@ -72,7 +105,7 @@ class SimpleChat { */ this.xchat = []; this.iLastSys = -1; - this.latestResponse = { content: "", toolname: "", toolargs: "" }; + this.latestResponse = new AssistantResponse(); } clear() { @@ -80,10 +113,6 @@ class SimpleChat { this.iLastSys = -1; } - clear_latestresponse() { - this.latestResponse = { content: "", toolname: "", toolargs: "" }; - } - ods_key() { return `SimpleChat-${this.chatId}` } @@ -149,19 +178,6 @@ class SimpleChat { return rchat; } - /** - * Collate the latest response from the server/ai-model, as it is becoming available. - * This is mainly useful for the stream mode. - * @param {{key: string, value: string}} resp - */ - append_response(resp) { - if (resp.value == null) { - return - } - console.debug(resp.key, resp.value) - this.latestResponse[resp.key] += resp.value; - } - /** * Add an entry into xchat * @param {string} role @@ -416,7 +432,7 @@ class SimpleChat { } let tdUtf8 = new TextDecoder("utf-8"); let rr = resp.body.getReader(); - this.clear_latestresponse() + this.latestResponse.clear() let xLines = new du.NewLines(); while(true) { let { value: cur, done: done } = await rr.read(); @@ -441,20 +457,16 @@ class SimpleChat { } let curJson = JSON.parse(curLine); console.debug("DBUG:SC:PART:Json:", curJson); - this.append_response(this.response_extract_stream(curJson, apiEP)); - } - if (this.latestResponse.content !== "") { - elP.innerText = this.latestResponse.content; - } else { - elP.innerText = `ToolCall:${this.latestResponse.toolname}:${this.latestResponse.toolargs}`; + this.latestResponse.append_response(this.response_extract_stream(curJson, apiEP)); } + elP.innerText = this.latestResponse.content_equiv() elP.scrollIntoView(false); if (done) { break; } } - console.debug("DBUG:SC:PART:Full:", this.latestResponse.content); - return this.latestResponse.content; + console.debug("DBUG:SC:PART:Full:", this.latestResponse.content_equiv()); + return this.latestResponse; } /** @@ -483,11 +495,11 @@ class SimpleChat { if (gMe.bStream) { try { theResp.assistant = await this.handle_response_multipart(resp, apiEP, elDiv); - this.clear_latestresponse() + this.latestResponse.clear() } catch (error) { theResp.assistant = this.latestResponse.content; this.add(Roles.Assistant, theResp.assistant); - this.clear_latestresponse() + this.latestResponse.clear() throw error; } } else { From 10b1013eb9c30e307e5c424dcb0366de19305cd6 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 03:31:18 +0530 Subject: [PATCH 013/187] SimpleChatTC: AssistantResponse everywhere initial go Switch oneshot handler to use AssistantResponse, inturn currenlty only handle the normal content in the response. TODO: If any tool_calls in the oneshot response, it is currently not handled. Inturn switch the generic/toplevel handle response logic to use AssistantResponse class, given that both oneshot and the multipart/streaming flows use/return it. Inturn add trimmedContent member to AssistantResponse class and make the generic handle response logic to save the trimmed content into this. Update users of trimmed to work with this structure. --- tools/server/public_simplechat/simplechat.js | 39 ++++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 510749c17c88d..c38c740efe9e0 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -40,11 +40,11 @@ class ApiEP { class AssistantResponse { constructor() { - this.response = { content: "", toolname: "", toolargs: "" }; + this.response = { content: "", toolname: "", toolargs: "", trimmedContent: "" }; } clear() { - this.response = { content: "", toolname: "", toolargs: "" }; + this.response = { content: "", toolname: "", toolargs: "", trimmedContent: "" }; } /** @@ -312,14 +312,14 @@ class SimpleChat { * @param {string} apiEP */ response_extract(respBody, apiEP) { - let assistant = ""; + let assistant = new AssistantResponse(); if (apiEP == ApiEP.Type.Chat) { - assistant = respBody["choices"][0]["message"]["content"]; + assistant.response.content = respBody["choices"][0]["message"]["content"]; } else { try { - assistant = respBody["choices"][0]["text"]; + assistant.response.content = respBody["choices"][0]["text"]; } catch { - assistant = respBody["content"]; + assistant.response.content = respBody["content"]; } } return assistant; @@ -483,34 +483,33 @@ class SimpleChat { /** * Handle the response from the server be it in oneshot or multipart/stream mode. * Also take care of the optional garbage trimming. + * TODO: Need to handle tool calling and related flow, including how to show + * the assistant's request for tool calling and the response from tool. * @param {Response} resp * @param {string} apiEP * @param {HTMLDivElement} elDiv */ async handle_response(resp, apiEP, elDiv) { - let theResp = { - assistant: "", - trimmed: "", - } + let theResp = null if (gMe.bStream) { try { - theResp.assistant = await this.handle_response_multipart(resp, apiEP, elDiv); + theResp = await this.handle_response_multipart(resp, apiEP, elDiv); this.latestResponse.clear() } catch (error) { - theResp.assistant = this.latestResponse.content; - this.add(Roles.Assistant, theResp.assistant); + theResp = this.latestResponse; + this.add(Roles.Assistant, theResp.content_equiv()); this.latestResponse.clear() throw error; } } else { - theResp.assistant = await this.handle_response_oneshot(resp, apiEP); + theResp = await this.handle_response_oneshot(resp, apiEP); } if (gMe.bTrimGarbage) { - let origMsg = theResp.assistant; - theResp.assistant = du.trim_garbage_at_end(origMsg); - theResp.trimmed = origMsg.substring(theResp.assistant.length); + let origMsg = theResp.response.content; + theResp.response.content = du.trim_garbage_at_end(origMsg); + theResp.response.trimmedContent = origMsg.substring(theResp.response.content.length); } - this.add(Roles.Assistant, theResp.assistant); + this.add(Roles.Assistant, theResp.content_equiv()); return theResp; } @@ -678,8 +677,8 @@ class MultiChatUI { let theResp = await chat.handle_response(resp, apiEP, this.elDivChat); if (chatId == this.curChatId) { chat.show(this.elDivChat); - if (theResp.trimmed.length > 0) { - let p = ui.el_create_append_p(`TRIMMED:${theResp.trimmed}`, this.elDivChat); + if (theResp.response.trimmedContent.length > 0) { + let p = ui.el_create_append_p(`TRIMMED:${theResp.response.trimmedContent}`, this.elDivChat); p.className="role-trim"; } } else { From e4e29a245216301cfeb482a502e7890f27784b64 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 04:03:53 +0530 Subject: [PATCH 014/187] SimpleChatTC: twins wrt streamed response handling As there could be failure wrt getting the response from the ai server some where in between a long response spread over multiple parts, the logic uses the latestResponse to cache the response as it is being received. However once the full response is got, one needs to transfer it to a new instance of AssistantResponse class, so that latestResponse can be cleared, while the new instance can be used in other locations in the flow as needed. Achieve the same now. --- tools/server/public_simplechat/simplechat.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index c38c740efe9e0..7cce5b5f5f8da 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -39,8 +39,16 @@ class ApiEP { class AssistantResponse { - constructor() { - this.response = { content: "", toolname: "", toolargs: "", trimmedContent: "" }; + constructor(content="", toolname="", toolargs="", trimmedContent="") { + this.response = { content: content, toolname: toolname, toolargs: toolargs, trimmedContent: trimmedContent }; + } + + /** + * Create a new instance from an existing instance + * @param {AssistantResponse} old + */ + static newFrom(old) { + return new AssistantResponse(old.response.content, old.response.toolname, old.response.toolargs, old.response.trimmedContent) } clear() { @@ -466,7 +474,7 @@ class SimpleChat { } } console.debug("DBUG:SC:PART:Full:", this.latestResponse.content_equiv()); - return this.latestResponse; + return AssistantResponse.newFrom(this.latestResponse); } /** From d7f612ffd03c69e0e46806de6f1cc952241154e0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 04:22:15 +0530 Subject: [PATCH 015/187] SimpleChatTC: Saner/Robust AssistantResponse content_equiv Previously if content was empty, it would have always sent the toolcall info related version even if there was no toolcall info in it. Fixed now to return empty string, if both content and toolname are empty. --- tools/server/public_simplechat/simplechat.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 7cce5b5f5f8da..8c9fa56698e7a 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -71,8 +71,10 @@ class AssistantResponse { content_equiv() { if (this.response.content !== "") { return this.response.content; - } else { + } else if (this.response.toolname !== "") { return `ToolCall:${this.response.toolname}:${this.response.toolargs}`; + } else { + return "" } } From 2a2769778f578e362e5225bb59b998459dcdf13c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 23:27:08 +0530 Subject: [PATCH 016/187] SimpleChatTC:tooljs: Trap console.log and store in new result key The implementations of javascript and simple_calculator now use provided helpers to trap console.log messages when they execute the code / expression provided by GenAi and inturn store the captured log messages in the newly added result key in tc_switch This should help trap the output generated by the provided code or expression as the case maybe and inturn return the same to the GenAi, for its further processing. --- tools/server/public_simplechat/tooljs.mjs | 48 +++++++++++++++++++++-- tools/server/public_simplechat/tools.mjs | 2 +- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 1796bfaa2b1f0..628563ebd1cd6 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -7,6 +7,40 @@ // +let gConsoleStr = "" +/** + * @type { {(...data: any[]): void} | null} + */ +let gOrigConsoleLog = null + + +/** + * @param {any[]} args + */ +function console_trapped(...args) { + let res = args.map((arg)=>{ + if (typeof arg == 'object') { + return JSON.stringify(arg); + } else { + return String(arg); + } + }).join(' '); + gConsoleStr += res; +} + +function console_redir() { + gOrigConsoleLog = console.log + console.log = console_trapped + gConsoleStr = "" +} + +function console_revert() { + if (gOrigConsoleLog !== null) { + console.log = gOrigConsoleLog + } +} + + let js_meta = { "type": "function", "function": { @@ -32,8 +66,11 @@ let js_meta = { * @param {any} obj */ function js_run(obj) { + console_redir() let func = new Function(obj["code"]) func() + console_revert() + tc_switch["javascript"]["result"] = gConsoleStr } @@ -62,22 +99,27 @@ let calc_meta = { * @param {any} obj */ function calc_run(obj) { + console_redir() let func = new Function(obj["arithexpr"]) func() + console_revert() + tc_switch["simple_calculator"]["result"] = gConsoleStr } /** - * @type {Object} + * @type {Object>} */ export let tc_switch = { "javascript": { "handler": js_run, - "meta": js_meta + "meta": js_meta, + "result": "" }, "simple_calculator": { "handler": calc_run, - "meta": calc_meta + "meta": calc_meta, + "result": "" } } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index ba80e30a91347..d249a3f5433cd 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -9,7 +9,7 @@ import * as tjs from './tooljs.mjs' /** - * @type {Object} + * @type {Object>} */ let tc_switch = {} From 92b82ae5c7388331fe0957a4d964a3d8cc48d10e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 01:20:53 +0530 Subject: [PATCH 017/187] SimpleChatTC: Implement a simple toolcall handling flow Checks for toolname to be defined or not in the GenAi's response If toolname is set, then check if a corresponding tool/func exists, and if so call the same by passing it the GenAi provided toolargs as a object. Inturn the text generated by the tool/func is captured and put into the user input entry text box, with tool_response tag around it. --- tools/server/public_simplechat/simplechat.js | 24 ++++++++++++++++++++ tools/server/public_simplechat/tools.mjs | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 8c9fa56698e7a..f0156e007ffb9 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -190,6 +190,7 @@ class SimpleChat { /** * Add an entry into xchat + * Also update iLastSys system prompt index tracker * @param {string} role * @param {string|undefined|null} content */ @@ -398,6 +399,7 @@ class SimpleChat { /** * Allow setting of system prompt, at any time. + * Updates the system prompt, if one was never set or if the newly passed is different from the last set system prompt. * @param {string} sysPrompt * @param {string} msgTag */ @@ -523,6 +525,24 @@ class SimpleChat { return theResp; } + /** + * Call the requested tool/function and get its response + * @param {AssistantResponse} ar + */ + async handle_toolcall(ar) { + let toolname = ar.response.toolname.trim(); + if (toolname === "") { + return undefined + } + for (const fn in tools.tc_switch) { + if (fn == toolname) { + tools.tc_switch[fn]["handler"](JSON.parse(ar.response.toolargs)) + return tools.tc_switch[fn]["result"] + } + } + return `Unknown Tool/Function Call:${toolname}` + } + } @@ -694,6 +714,10 @@ class MultiChatUI { } else { console.debug(`DBUG:SimpleChat:MCUI:HandleUserSubmit:ChatId has changed:[${chatId}] [${this.curChatId}]`); } + let toolResult = await chat.handle_toolcall(theResp) + if (toolResult !== undefined) { + this.elInUser.value = `${toolResult}` + } this.ui_reset_userinput(); } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index d249a3f5433cd..adf87fbdf432e 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -11,7 +11,7 @@ import * as tjs from './tooljs.mjs' /** * @type {Object>} */ -let tc_switch = {} +export let tc_switch = {} export function setup() { for (const key in tjs.tc_switch) { From d8b1b36bb3fe2567cec7e774d80780e8f3ac1272 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 01:36:43 +0530 Subject: [PATCH 018/187] SimpleChatTC: Cleanup initial/1st go toolcall flow As output generated by any tool/function call is currently placed into the TextArea provided for End user (for their queries), bcas the GenAi (engine/LLM) may be expecting the tool response to be sent as a user role data with tool_response tag surrounding the results from the tool call. So also now at the end of submit btn click handling, the end user input text area is not cleared, if there was a tool call handled, for above reasons. Also given that running a simple arithmatic expression in itself doesnt generate any output, so wrap them in a console.log, to help capture the result using the console.log trapping flow that is already setup. --- tools/server/public_simplechat/simplechat.js | 11 +++++++---- tools/server/public_simplechat/tooljs.mjs | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index f0156e007ffb9..618bb95679fa1 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -586,12 +586,15 @@ class MultiChatUI { /** * Reset user input ui. - * * clear user input + * * clear user input (if requested, default true) * * enable user input * * set focus to user input + * @param {boolean} [bClearElInUser=true] */ - ui_reset_userinput() { - this.elInUser.value = ""; + ui_reset_userinput(bClearElInUser=true) { + if (bClearElInUser) { + this.elInUser.value = ""; + } this.elInUser.disabled = false; this.elInUser.focus(); } @@ -718,7 +721,7 @@ class MultiChatUI { if (toolResult !== undefined) { this.elInUser.value = `${toolResult}` } - this.ui_reset_userinput(); + this.ui_reset_userinput(toolResult === undefined); } /** diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 628563ebd1cd6..ae62fd017691d 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -100,7 +100,7 @@ let calc_meta = { */ function calc_run(obj) { console_redir() - let func = new Function(obj["arithexpr"]) + let func = new Function(`console.log(${obj["arithexpr"]})`) func() console_revert() tc_switch["simple_calculator"]["result"] = gConsoleStr From 7a2bcfb5590700e4143b114ad8a84c8fff28c2d4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 02:53:08 +0530 Subject: [PATCH 019/187] SimpleChatTC: Trap any exception raised during tool call and inform the GenAi/LLM about the same --- tools/server/public_simplechat/simplechat.js | 8 ++++++-- tools/server/public_simplechat/test-tools-cmdline.sh | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 618bb95679fa1..36cd5a59372f3 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -536,8 +536,12 @@ class SimpleChat { } for (const fn in tools.tc_switch) { if (fn == toolname) { - tools.tc_switch[fn]["handler"](JSON.parse(ar.response.toolargs)) - return tools.tc_switch[fn]["result"] + try { + tools.tc_switch[fn]["handler"](JSON.parse(ar.response.toolargs)) + return tools.tc_switch[fn]["result"] + } catch (error) { + return `Tool/Function call raised an exception:${error.name}:${error.message}` + } } } return `Unknown Tool/Function Call:${toolname}` diff --git a/tools/server/public_simplechat/test-tools-cmdline.sh b/tools/server/public_simplechat/test-tools-cmdline.sh index adea59cb6c279..8fc62d2af9a48 100644 --- a/tools/server/public_simplechat/test-tools-cmdline.sh +++ b/tools/server/public_simplechat/test-tools-cmdline.sh @@ -73,10 +73,12 @@ exit "content": "what is your name." "content": "What and all tools you have access to" + "content": "do you have access to any tools" "content": "Print a hello world message with python." "content": "Print a hello world message with javascript." "content": "Calculate the sum of 5 and 27." "content": "Can you get me todays date." + "content": "Can you get me a summary of latest news from bbc world" "content": "Can you get todays date. And inturn add 10 to todays date" "content": "Who is known as father of the nation in India, also is there a similar figure for USA as well as UK" "content": "Who is known as father of the nation in India, Add 10 to double his year of birth and show me the results." From f10ab96c77a49eec1b7d5e18d1f9992c80bbca51 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 12:58:37 +0530 Subject: [PATCH 020/187] SimpleChatTC: More clearer description of toolcalls execution env Should hopeful ensure that the GenAi/LLM will generate appropriate code/expression as the argument to pass to these tool calls, to some extent. --- tools/server/public_simplechat/tooljs.mjs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index ae62fd017691d..c2bbc0c43c3a1 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -44,14 +44,14 @@ function console_revert() { let js_meta = { "type": "function", "function": { - "name": "javascript", - "description": "Runs code in an javascript interpreter and returns the result of the execution after few seconds", + "name": "run_javascript_function_code", + "description": "Runs given code as a function in a browser's javascript environment and returns the console.log outputs of the execution after few seconds", "parameters": { "type": "object", "properties": { "code": { "type": "string", - "description": "The code to run in the javascript interpreter." + "description": "The code belonging to a function to run in the browser's javascript interpreter." } }, "required": ["code"] @@ -78,13 +78,13 @@ let calc_meta = { "type": "function", "function": { "name": "simple_calculator", - "description": "Calculates the provided arithmatic expression using javascript interpreter and returns the result of the execution after few seconds", + "description": "Calculates the provided arithmatic expression using console.log of a browser's javascript interpreter and returns the output of the execution once it is done in few seconds", "parameters": { "type": "object", "properties": { "arithexpr":{ "type":"string", - "description":"The arithmatic expression that will be calculated using javascript interpreter." + "description":"The arithmatic expression that will be calculated by passing it to console.log of a browser's javascript interpreter." } }, "required": ["arithexpr"] @@ -111,7 +111,7 @@ function calc_run(obj) { * @type {Object>} */ export let tc_switch = { - "javascript": { + "run_javascript_function_code": { "handler": js_run, "meta": js_meta, "result": "" From a1f177656d7381ed5f1adae5e0e423a8e94944a2 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 13:01:59 +0530 Subject: [PATCH 021/187] SimpleChatTC: Clarify some type definitions to avoid warnings ie in vs code with ts-check --- tools/server/public_simplechat/simplechat.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 36cd5a59372f3..e39d9245ed2cc 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -18,6 +18,7 @@ class ApiEP { Chat: "chat", Completion: "completion", } + /** @type {Object} */ static UrlSuffix = { 'chat': `/chat/completions`, 'completion': `/completions`, @@ -40,6 +41,7 @@ class ApiEP { class AssistantResponse { constructor(content="", toolname="", toolargs="", trimmedContent="") { + /** @type {Object} */ this.response = { content: content, toolname: toolname, toolargs: toolargs, trimmedContent: trimmedContent }; } @@ -539,7 +541,7 @@ class SimpleChat { try { tools.tc_switch[fn]["handler"](JSON.parse(ar.response.toolargs)) return tools.tc_switch[fn]["result"] - } catch (error) { + } catch (/** @type {any} */error) { return `Tool/Function call raised an exception:${error.name}:${error.message}` } } @@ -824,11 +826,15 @@ class Me { "Last4": 5, }; this.apiEP = ApiEP.Type.Chat; + /** @type {Object} */ this.headers = { "Content-Type": "application/json", "Authorization": "", // Authorization: Bearer OPENAI_API_KEY } - // Add needed fields wrt json object to be sent wrt LLM web services completions endpoint. + /** + * Add needed fields wrt json object to be sent wrt LLM web services completions endpoint. + * @type {Object} + */ this.apiRequestOptions = { "model": "gpt-3.5-turbo", "temperature": 0.7, From 4ac6f0a59bbd1b533e6267d0f03afe7a6ecec80e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 16:08:11 +0530 Subject: [PATCH 022/187] SimpleChatTC: Move tool calling to tools, try trap async failures Move tool calling logic into tools module. Try trap async promise failures by awaiting results of tool calling and putting full thing in an outer try catch. Have forgotten the nitty gritties of JS flow, this might help, need to check. --- tools/server/public_simplechat/simplechat.js | 14 ++++---------- tools/server/public_simplechat/tools.mjs | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index e39d9245ed2cc..4e243db2f02c6 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -536,17 +536,11 @@ class SimpleChat { if (toolname === "") { return undefined } - for (const fn in tools.tc_switch) { - if (fn == toolname) { - try { - tools.tc_switch[fn]["handler"](JSON.parse(ar.response.toolargs)) - return tools.tc_switch[fn]["result"] - } catch (/** @type {any} */error) { - return `Tool/Function call raised an exception:${error.name}:${error.message}` - } - } + try { + return await tools.tool_call(toolname, ar.response.toolargs) + } catch (/** @type {any} */error) { + return `Tool/Function call raised an exception:${error.name}:${error.message}` } - return `Unknown Tool/Function Call:${toolname}` } } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index adf87fbdf432e..686d47a241b23 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -27,3 +27,22 @@ export function meta() { return tools } + +/** + * Try call the specified tool/function call and return its response + * @param {string} toolname + * @param {string} toolargs + */ +export async function tool_call(toolname, toolargs) { + for (const fn in tc_switch) { + if (fn == toolname) { + try { + tc_switch[fn]["handler"](JSON.parse(toolargs)) + return tc_switch[fn]["result"] + } catch (/** @type {any} */error) { + return `Tool/Function call raised an exception:${error.name}:${error.message}` + } + } + } + return `Unknown Tool/Function Call:${toolname}` +} From 379630622545ff0d1388c82e93c98ac2727c38c9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 17:19:51 +0530 Subject: [PATCH 023/187] SimpleChatTC: Pass toolname to the tool handler So that when tool handler writes the result to the tc_switch, it can make use of the same, to write to the right location. NOTE: This also fixes the issue with I forgetting to rename the key in js_run wrt writing of result. --- tools/server/public_simplechat/tooljs.mjs | 10 ++++++---- tools/server/public_simplechat/tools.mjs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index c2bbc0c43c3a1..dfaab850099bf 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -63,14 +63,15 @@ let js_meta = { /** * Implementation of the javascript interpretor logic. Minimal skeleton for now. * ALERT: Has access to the javascript environment and can mess with it and beyond + * @param {string} toolname * @param {any} obj */ -function js_run(obj) { +function js_run(toolname, obj) { console_redir() let func = new Function(obj["code"]) func() console_revert() - tc_switch["javascript"]["result"] = gConsoleStr + tc_switch[toolname]["result"] = gConsoleStr } @@ -96,14 +97,15 @@ let calc_meta = { /** * Implementation of the simple calculator logic. Minimal skeleton for now. * ALERT: Has access to the javascript environment and can mess with it and beyond + * @param {string} toolname * @param {any} obj */ -function calc_run(obj) { +function calc_run(toolname, obj) { console_redir() let func = new Function(`console.log(${obj["arithexpr"]})`) func() console_revert() - tc_switch["simple_calculator"]["result"] = gConsoleStr + tc_switch[toolname]["result"] = gConsoleStr } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 686d47a241b23..d75d3eb7bf73d 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -37,7 +37,7 @@ export async function tool_call(toolname, toolargs) { for (const fn in tc_switch) { if (fn == toolname) { try { - tc_switch[fn]["handler"](JSON.parse(toolargs)) + tc_switch[fn]["handler"](fn, JSON.parse(toolargs)) return tc_switch[fn]["result"] } catch (/** @type {any} */error) { return `Tool/Function call raised an exception:${error.name}:${error.message}` From 0ed8329a84791da9842caa69b379c317535aae5e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 21:21:00 +0530 Subject: [PATCH 024/187] SimpleChatTC: Cleanup the function description a bit to better describe how it will be run, so that genai/llm while creating the code to run, will hopefully take care of any naunces required. --- tools/server/public_simplechat/tooljs.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index dfaab850099bf..b79b79dd821de 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -45,13 +45,13 @@ let js_meta = { "type": "function", "function": { "name": "run_javascript_function_code", - "description": "Runs given code as a function in a browser's javascript environment and returns the console.log outputs of the execution after few seconds", + "description": "Runs given code using function constructor mechanism in a browser's javascript environment and returns the console.log outputs of the execution after few seconds", "parameters": { "type": "object", "properties": { "code": { "type": "string", - "description": "The code belonging to a function to run in the browser's javascript interpreter." + "description": "The code belonging to the dynamic function to run in the browser's javascript interpreter environment." } }, "required": ["code"] From aa81f519332c2f46a5215578c9e44c072ca15ac3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 22:16:34 +0530 Subject: [PATCH 025/187] SimpleChatTC: Update the readme.md wrt tool calling a bit --- tools/server/public_simplechat/readme.md | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 24e026d455b03..388202156e935 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -78,6 +78,7 @@ Once inside * try trim garbage in response or not * amount of chat history in the context sent to server/ai-model * oneshot or streamed mode. + * use built in tool calling or not * In completion mode * one normally doesnt use a system prompt in completion mode. @@ -116,6 +117,13 @@ Once inside * the user input box will be disabled and a working message will be shown in it. * if trim garbage is enabled, the logic will try to trim repeating text kind of garbage to some extent. +* tool calling flow + * if tool calling is enabled and the user query results in need for one of the builtin tools to be + called, then the response will include request for tool call. + * the SimpleChat client will call the requested tool and inturn place the returned result into user + entry text area with generated result + * if user is ok with the tool response, they can click submit to send the same to the GenAi/LLM. + * just refresh the page, to reset wrt the chat history and or system prompt and start afresh. * Using NewChat one can start independent chat sessions. @@ -158,6 +166,15 @@ It is attached to the document object. Some of these can also be updated using t inturn the machine goes into power saving mode or so, the platform may stop network connection, leading to exception. + bTools - control whether tool calling is enabled or not + + remember to enable this only for GenAi/LLM models which support tool/function calling. + + the builtin tools meta data is sent to the ai model in the requests sent to it. + + inturn if the ai model requests a tool call to be made, the same will be done and the response + sent back to the ai model, under user control. + apiEP - select between /completions and /chat/completions endpoint provided by the server/ai-model. bCompletionFreshChatAlways - whether Completion mode collates complete/sliding-window history when @@ -281,6 +298,27 @@ NOTE: Not tested, as there is no free tier api testing available. However logica work. +### Tool Calling + +Provide a descriptive meta data explaining the tool / function being provided for tool calling. + +Provide a handler which should implement the specified tool / function call. It should place +the result to be sent back to the ai model in the result key of the tc_switch entry for the +corresponding tool. + +Update the tc_switch to include a object entry for the tool, which inturn icnludes +* the meta data as well as +* a reference to the handler and also +* the result key + + +### Debuging the handshake + +When working with llama.cpp server based GenAi/LLM running locally + +sudo tcpdump -i lo -s 0 -vvv -A host 127.0.0.1 and port 8080 | tee /tmp/td.log + + ## At the end Also a thank you to all open source and open model developers, who strive for the common good. From 5ed2bc3f481d55100f3fc8199074aad31fce3105 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 23:50:35 +0530 Subject: [PATCH 026/187] SimpleChatTC: ToolCall hs info in normal assistant-user chat flow Also as part of same, wrap the request details in the assistant block using a similar tagging format as the tool_response in user block. --- tools/server/public_simplechat/readme.md | 19 +++++++++++++++++++ tools/server/public_simplechat/simplechat.js | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 388202156e935..19e04520692e7 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -300,6 +300,8 @@ work. ### Tool Calling +#### Extending wiht new tools + Provide a descriptive meta data explaining the tool / function being provided for tool calling. Provide a handler which should implement the specified tool / function call. It should place @@ -311,6 +313,23 @@ Update the tc_switch to include a object entry for the tool, which inturn icnlud * a reference to the handler and also * the result key +#### Mapping tool calls and responses to normal assistant - user chat flow + +Instead of maintaining tool_call request and resultant response in logically seperate parallel +channel used for requesting tool_calls by the assistant and the resulstant tool role response, +the SimpleChatTC pushes it into the normal assistant - user chat flow itself, by including the +tool call and response as a pair of tagged request with details in the assistant block and inturn +tagged response in the subsequent user block. + +This allows the GenAi/LLM to be aware of the tool calls it made as well as the responses it got, +so that it can incorporate the results of the same in the subsequent chat / interactions. + +NOTE: This flow tested to be ok enough with Gemma-3N-E4B-it-Q8_0 LLM ai model for now. + +TODO: Need to think later, whether to continue this simple flow, or atleast use tool role wrt +the tool call responses or even go further and have the logically seperate tool_call request +structures also. + ### Debuging the handshake diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 4e243db2f02c6..83911fde42662 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -74,7 +74,7 @@ class AssistantResponse { if (this.response.content !== "") { return this.response.content; } else if (this.response.toolname !== "") { - return `ToolCall:${this.response.toolname}:${this.response.toolargs}`; + return `\n${this.response.toolname}\n${this.response.toolargs}\n`; } else { return "" } From 619d64d30ebcf42394067c5af8ee5270fdfc1945 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 00:41:19 +0530 Subject: [PATCH 027/187] SimpleChatTC: Add ui elements for tool call verify and trigger Instead of automatically calling the requested tool with supplied arguments, rather allow user to verify things before triggering the tool. NOTE: User already provided control over tool_response before submitting it to the ai assistant. --- tools/server/public_simplechat/index.html | 9 +++++++++ tools/server/public_simplechat/simplechat.js | 14 +++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/index.html b/tools/server/public_simplechat/index.html index f6413016fcc53..c9b508eecfe93 100644 --- a/tools/server/public_simplechat/index.html +++ b/tools/server/public_simplechat/index.html @@ -40,6 +40,15 @@

You need to have javascript enabled.

+
+
+
+ + +
+ +
+
diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 83911fde42662..73e1cafc80f6c 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -91,7 +91,11 @@ let gUsageMsg = `
  • Completion mode - no system prompt normally.
  • Use shift+enter for inserting enter/newline.
  • -
  • Enter your query to ai assistant below.
  • +
  • Enter your query to ai assistant in textarea provided below.
  • +
  • If ai assistant requests a tool call, varify same before triggering it.
  • +
      +
    • submit tool response placed into user query textarea
    • +
  • Default ContextWindow = [System, Last Query+Resp, Cur Query].
    • ChatHistInCtxt, MaxTokens, ModelCtxt window to expand
    • @@ -562,6 +566,10 @@ class MultiChatUI { this.elDivHeading = /** @type{HTMLSelectElement} */(document.getElementById("heading")); this.elDivSessions = /** @type{HTMLDivElement} */(document.getElementById("sessions-div")); this.elBtnSettings = /** @type{HTMLButtonElement} */(document.getElementById("settings")); + this.elDivTool = /** @type{HTMLDivElement} */(document.getElementById("tool-div")); + this.elBtnTool = /** @type{HTMLButtonElement} */(document.getElementById("tool-btn")); + this.elInToolName = /** @type{HTMLInputElement} */(document.getElementById("toolname-in")); + this.elInToolArgs = /** @type{HTMLInputElement} */(document.getElementById("toolargs-in")); this.validate_element(this.elInSystem, "system-in"); this.validate_element(this.elDivChat, "chat-div"); @@ -569,6 +577,10 @@ class MultiChatUI { this.validate_element(this.elDivHeading, "heading"); this.validate_element(this.elDivChat, "sessions-div"); this.validate_element(this.elBtnSettings, "settings"); + this.validate_element(this.elDivTool, "tool-div"); + this.validate_element(this.elInToolName, "toolname-in"); + this.validate_element(this.elInToolArgs, "toolargs-in"); + this.validate_element(this.elBtnTool, "tool-btn"); } /** From 226aa7d4aa55510f63acb10092ff5977238507ab Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 01:19:37 +0530 Subject: [PATCH 028/187] SimpleChatTC: Let user trigger tool call, instead of automatic Instead of automatically calling any requested tool by the GenAi / llm, that is from the tail end of the handle user submit btn click, Now if the GenAi/LLM has requested any tool to be called, then enable the Tool Run related UI elements and fill them with the tool name and tool args. In turn the user can verify if they are ok with the tool being called and the arguments being passed to it. Rather they can even fix any errors in the tool usage like the arithmatic expr to calculate that is being passed to simple_calculator or the javascript code being passed to run_javascript_function_code If user is ok with the tool call being requested, then trigger the same. The results if any will be automatically placed into the user query text area. User can cross verify if they are ok with the result and or modify it suitabley if required and inturn submit the same to the GenAi/LLM. --- tools/server/public_simplechat/simplechat.js | 55 +++++++++++++++++--- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 73e1cafc80f6c..9b4cc58a15687 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -70,10 +70,17 @@ class AssistantResponse { this.response[resp.key] += resp.value; } + has_toolcall() { + if (this.response.toolname.trim() == "") { + return false + } + return true + } + content_equiv() { if (this.response.content !== "") { return this.response.content; - } else if (this.response.toolname !== "") { + } else if (this.has_toolcall()) { return `\n${this.response.toolname}\n${this.response.toolargs}\n`; } else { return "" @@ -533,15 +540,15 @@ class SimpleChat { /** * Call the requested tool/function and get its response - * @param {AssistantResponse} ar + * @param {string} toolname + * @param {string} toolargs */ - async handle_toolcall(ar) { - let toolname = ar.response.toolname.trim(); + async handle_toolcall(toolname, toolargs) { if (toolname === "") { return undefined } try { - return await tools.tool_call(toolname, ar.response.toolargs) + return await tools.tool_call(toolname, toolargs) } catch (/** @type {any} */error) { return `Tool/Function call raised an exception:${error.name}:${error.message}` } @@ -596,6 +603,24 @@ class MultiChatUI { } } + /** + * Reset/Setup Tool Call UI parts as needed + * @param {AssistantResponse} ar + */ + ui_reset_toolcall_as_needed(ar) { + if (ar.has_toolcall()) { + this.elDivTool.hidden = false + this.elInToolName.value = ar.response.toolname + this.elInToolArgs.value = ar.response.toolargs + this.elBtnTool.disabled = false + } else { + this.elDivTool.hidden = true + this.elInToolName.value = "" + this.elInToolArgs.value = "" + this.elBtnTool.disabled = true + } + } + /** * Reset user input ui. * * clear user input (if requested, default true) @@ -641,6 +666,13 @@ class MultiChatUI { }); }); + this.elBtnTool.addEventListener("click", (ev)=>{ + if (this.elDivTool.hidden) { + return; + } + this.handle_tool_run(this.curChatId); + }) + this.elInUser.addEventListener("keyup", (ev)=> { // allow user to insert enter into their message using shift+enter. // while just pressing enter key will lead to submitting. @@ -729,7 +761,18 @@ class MultiChatUI { } else { console.debug(`DBUG:SimpleChat:MCUI:HandleUserSubmit:ChatId has changed:[${chatId}] [${this.curChatId}]`); } - let toolResult = await chat.handle_toolcall(theResp) + this.ui_reset_toolcall_as_needed(theResp); + this.ui_reset_userinput(); + } + + /** + * @param {string} chatId + */ + async handle_tool_run(chatId) { + let chat = this.simpleChats[chatId]; + this.elInUser.value = "toolcall in progress..."; + this.elInUser.disabled = true; + let toolResult = await chat.handle_toolcall(this.elInToolName.value, this.elInToolArgs.value) if (toolResult !== undefined) { this.elInUser.value = `${toolResult}` } From 2aabca21357e2ec69803fbf0f46cf6231fd591ed Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 02:57:17 +0530 Subject: [PATCH 029/187] SimpleChatTC: Update readme with bit more details, Cleaner UI Also avoid showing Tool calling UI elements, when not needed to be shown. --- tools/server/public_simplechat/readme.md | 54 +++++++++++++++++--- tools/server/public_simplechat/simplechat.js | 5 ++ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 19e04520692e7..d2a1e22df63f2 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -33,6 +33,10 @@ Allows developer/end-user to control some of the behaviour by updating gMe membe console. Parallely some of the directly useful to end-user settings can also be changed using the provided settings ui. +For GenAi/LLM models supporting tool / function calling, allows one to interact with them and explore use of +ai driven augmenting of the knowledge used for generating answers by using the predefined tools/functions. +The end user is provided control over tool calling and response submitting. + NOTE: Current web service api doesnt expose the model context length directly, so client logic doesnt provide any adaptive culling of old messages nor of replacing them with summary of their content etal. However there is a optional sliding window based chat logic, which provides a simple minded culling of old messages from @@ -117,12 +121,15 @@ Once inside * the user input box will be disabled and a working message will be shown in it. * if trim garbage is enabled, the logic will try to trim repeating text kind of garbage to some extent. -* tool calling flow +* tool calling flow when working with ai models which support tool / function calling * if tool calling is enabled and the user query results in need for one of the builtin tools to be - called, then the response will include request for tool call. - * the SimpleChat client will call the requested tool and inturn place the returned result into user - entry text area with generated result + called, then the ai response might include request for tool call. + * the SimpleChat client will show details of the tool call (ie tool name and args passed) requested + and allow the user to trigger it as is or after modifying things as needed. + * inturn returned / generated result is placed into user query entry text area with approriate tags + ie generated result * if user is ok with the tool response, they can click submit to send the same to the GenAi/LLM. + User can even modify the response generated by the tool, if required, before submitting. * just refresh the page, to reset wrt the chat history and or system prompt and start afresh. @@ -170,11 +177,15 @@ It is attached to the document object. Some of these can also be updated using t remember to enable this only for GenAi/LLM models which support tool/function calling. - the builtin tools meta data is sent to the ai model in the requests sent to it. + the builtin tools' meta data is sent to the ai model in the requests sent to it. inturn if the ai model requests a tool call to be made, the same will be done and the response sent back to the ai model, under user control. + as tool calling will involve a bit of back and forth between ai assistant and end user, it is + recommended to set iRecentUserMsgCnt to 5 or more, so that enough context is retained during + chatting with ai models with tool support. + apiEP - select between /completions and /chat/completions endpoint provided by the server/ai-model. bCompletionFreshChatAlways - whether Completion mode collates complete/sliding-window history when @@ -300,9 +311,31 @@ work. ### Tool Calling +ALERT: Currently the way this is implemented, it is dangerous to use this, unless one verifies +all the tool calls requested and the responses generated manually to ensure everything is fine, +during interaction with ai modles with tools support. + +#### Builtin Tools + +The following tools/functions are currently provided by default +* simple_calculator - which can solve simple arithmatic expressions +* run_javascript_function_code - which can be used to run some javascript code in the browser + context. + +Currently the generated code / expression is run through a simple dynamic function mechanism. +May update things, in future, so that a WebWorker is used to avoid exposing browser global scope +to the generated code directly. Either way always remember to cross check the tool requests and +generated responses when using tool calling. + +May add +* web_fetch along with a corresponding simple local proxy server logic that can bypass the + CORS restrictions applied if trying to directly fetch from the browser js runtime environment. + + #### Extending wiht new tools -Provide a descriptive meta data explaining the tool / function being provided for tool calling. +Provide a descriptive meta data explaining the tool / function being provided for tool calling, +as well as its arguments. Provide a handler which should implement the specified tool / function call. It should place the result to be sent back to the ai model in the result key of the tc_switch entry for the @@ -330,6 +363,15 @@ TODO: Need to think later, whether to continue this simple flow, or atleast use the tool call responses or even go further and have the logically seperate tool_call request structures also. +#### ToDo + +Update to use web worker. + +Make the Tool Call related ui elements use up horizontal space properly. + +Try and trap promises based flows to ensure all generated results or errors if any are caught +before responding back to the ai model. + ### Debuging the handshake diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 9b4cc58a15687..599b147adcd59 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -649,6 +649,8 @@ class MultiChatUI { this.handle_session_switch(this.curChatId); } + this.ui_reset_toolcall_as_needed(new AssistantResponse()); + this.elBtnSettings.addEventListener("click", (ev)=>{ this.elDivChat.replaceChildren(); gMe.show_settings(this.elDivChat); @@ -729,6 +731,8 @@ class MultiChatUI { chat.clear(); } + this.ui_reset_toolcall_as_needed(new AssistantResponse()); + chat.add_system_anytime(this.elInSystem.value, chatId); let content = this.elInUser.value; @@ -766,6 +770,7 @@ class MultiChatUI { } /** + * Handle running of specified tool call if any, for the specified chat session. * @param {string} chatId */ async handle_tool_run(chatId) { From 90b2491223f21ee2d34639f3dde0ab5fe88458cf Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 03:18:09 +0530 Subject: [PATCH 030/187] SimpleChatTC: Tool Calling UI elements use up horizontal space --- tools/server/public_simplechat/index.html | 6 +++++- tools/server/public_simplechat/readme.md | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/index.html b/tools/server/public_simplechat/index.html index c9b508eecfe93..3cd840569c3a7 100644 --- a/tools/server/public_simplechat/index.html +++ b/tools/server/public_simplechat/index.html @@ -42,11 +42,15 @@
      +
      - +
      +
      +
      +

      diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index d2a1e22df63f2..29214369ec18d 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -328,8 +328,9 @@ to the generated code directly. Either way always remember to cross check the to generated responses when using tool calling. May add -* web_fetch along with a corresponding simple local proxy server logic that can bypass the - CORS restrictions applied if trying to directly fetch from the browser js runtime environment. +* web_fetch along with a corresponding simple local web proxy/caching server logic that can bypass + the CORS restrictions applied if trying to directly fetch from the browser js runtime environment. + Inturn maybe with a white list of allowed sites to access or so. #### Extending wiht new tools @@ -367,7 +368,7 @@ structures also. Update to use web worker. -Make the Tool Call related ui elements use up horizontal space properly. +WebFetch and Local web proxy/caching server Try and trap promises based flows to ensure all generated results or errors if any are caught before responding back to the ai model. From a8eadc429932e304beb7c19b5ac6fb38cc346764 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 03:32:58 +0530 Subject: [PATCH 031/187] SimpleChatTC: Update readme wrt --jinja argument and bit more --- tools/server/public_simplechat/readme.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 29214369ec18d..6f9f986b01844 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -7,7 +7,7 @@ by Humans for All. To run from the build dir -bin/llama-server -m path/model.gguf --path ../tools/server/public_simplechat +bin/llama-server -m path/model.gguf --path ../tools/server/public_simplechat --jinja Continue reading for the details. @@ -68,6 +68,16 @@ next run this web front end in tools/server/public_simplechat * cd ../tools/server/public_simplechat * python3 -m http.server PORT +### for tool calling + +remember to + +* pass --jinja to llama-server to enable tool calling support from the server ai engine end. + +* enable bTools in the settings page of the client side gui. + +* use a GenAi/LLM model which supports tool calling. + ### using the front end Open this simple web front end from your local browser From 70bc1b42b91364037e293a41df6bc5a64cf377cf Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 22:11:25 +0530 Subject: [PATCH 032/187] SimpleChatTC: Move console.log trapping into its own module So that it can be used from different modules, if required. --- tools/server/public_simplechat/tooljs.mjs | 45 +++---------------- .../server/public_simplechat/toolsconsole.mjs | 38 ++++++++++++++++ 2 files changed, 45 insertions(+), 38 deletions(-) create mode 100644 tools/server/public_simplechat/toolsconsole.mjs diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index b79b79dd821de..23b340e514b17 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -7,38 +7,7 @@ // -let gConsoleStr = "" -/** - * @type { {(...data: any[]): void} | null} - */ -let gOrigConsoleLog = null - - -/** - * @param {any[]} args - */ -function console_trapped(...args) { - let res = args.map((arg)=>{ - if (typeof arg == 'object') { - return JSON.stringify(arg); - } else { - return String(arg); - } - }).join(' '); - gConsoleStr += res; -} - -function console_redir() { - gOrigConsoleLog = console.log - console.log = console_trapped - gConsoleStr = "" -} - -function console_revert() { - if (gOrigConsoleLog !== null) { - console.log = gOrigConsoleLog - } -} +import * as tconsole from "./toolsconsole.mjs" let js_meta = { @@ -67,11 +36,11 @@ let js_meta = { * @param {any} obj */ function js_run(toolname, obj) { - console_redir() + tconsole.console_redir() let func = new Function(obj["code"]) func() - console_revert() - tc_switch[toolname]["result"] = gConsoleStr + tconsole.console_revert() + tc_switch[toolname]["result"] = tconsole.gConsoleStr } @@ -101,11 +70,11 @@ let calc_meta = { * @param {any} obj */ function calc_run(toolname, obj) { - console_redir() + tconsole.console_redir() let func = new Function(`console.log(${obj["arithexpr"]})`) func() - console_revert() - tc_switch[toolname]["result"] = gConsoleStr + tconsole.console_revert() + tc_switch[toolname]["result"] = tconsole.gConsoleStr } diff --git a/tools/server/public_simplechat/toolsconsole.mjs b/tools/server/public_simplechat/toolsconsole.mjs new file mode 100644 index 0000000000000..0c7d436a0af02 --- /dev/null +++ b/tools/server/public_simplechat/toolsconsole.mjs @@ -0,0 +1,38 @@ +//@ts-check +// Helpers to handle tools/functions calling wrt console +// by Humans for All +// + + +export let gConsoleStr = "" +/** + * @type { {(...data: any[]): void} | null} + */ +export let gOrigConsoleLog = null + + +/** + * @param {any[]} args + */ +export function console_trapped(...args) { + let res = args.map((arg)=>{ + if (typeof arg == 'object') { + return JSON.stringify(arg); + } else { + return String(arg); + } + }).join(' '); + gConsoleStr += res; +} + +export function console_redir() { + gOrigConsoleLog = console.log + console.log = console_trapped + gConsoleStr = "" +} + +export function console_revert() { + if (gOrigConsoleLog !== null) { + console.log = gOrigConsoleLog + } +} From f8ebe8f8dd9738bd23ca0a846be31398cba1417b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 22:31:31 +0530 Subject: [PATCH 033/187] SimpleChatTC:ToolsConsole:Cleanup a bit, add basic set of notes Try ensure as well as verify that original console.log is saved and not overwritten. Throw an exception if things seem off wrt same. Also ensure to add a newline at end of console.log messages --- .../server/public_simplechat/toolsconsole.mjs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/toolsconsole.mjs b/tools/server/public_simplechat/toolsconsole.mjs index 0c7d436a0af02..b372dc74ef329 100644 --- a/tools/server/public_simplechat/toolsconsole.mjs +++ b/tools/server/public_simplechat/toolsconsole.mjs @@ -4,14 +4,17 @@ // +/** The redirected console.log's capture-data-space */ export let gConsoleStr = "" /** + * Maintain original console.log, when needed * @type { {(...data: any[]): void} | null} */ -export let gOrigConsoleLog = null +let gOrigConsoleLog = null /** + * The trapping console.log * @param {any[]} args */ export function console_trapped(...args) { @@ -22,17 +25,33 @@ export function console_trapped(...args) { return String(arg); } }).join(' '); - gConsoleStr += res; + gConsoleStr += `${res}\n`; } +/** + * Save the original console.log, if needed. + * Setup redir of console.log. + * Clear the redirected console.log's capture-data-space. + */ export function console_redir() { - gOrigConsoleLog = console.log + if (gOrigConsoleLog == null) { + if (console.log == console_trapped) { + throw new Error("ERRR:ToolsConsole:ReDir:Original Console.Log lost???"); + } + gOrigConsoleLog = console.log + } console.log = console_trapped gConsoleStr = "" } +/** + * Revert the redirected console.log to the original console.log, if possible. + */ export function console_revert() { if (gOrigConsoleLog !== null) { + if (gOrigConsoleLog == console_trapped) { + throw new Error("ERRR:ToolsConsole:Revert:Original Console.Log lost???"); + } console.log = gOrigConsoleLog } } From cc60600bb39f2ea64f22e30c02ba619d25e962e0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 23:07:44 +0530 Subject: [PATCH 034/187] SimpleChatTC: Initial skeleton of a simple toolsworker --- .../server/public_simplechat/toolsworker.mjs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tools/server/public_simplechat/toolsworker.mjs diff --git a/tools/server/public_simplechat/toolsworker.mjs b/tools/server/public_simplechat/toolsworker.mjs new file mode 100644 index 0000000000000..b17c5bb197cea --- /dev/null +++ b/tools/server/public_simplechat/toolsworker.mjs @@ -0,0 +1,19 @@ +//@ts-check +// STILL DANGER DANGER DANGER - Simple and Stupid - Use from a discardable VM only +// Helpers to handle tools/functions calling using web worker +// by Humans for All +// + +import * as tconsole from "./toolsconsole.mjs" + +tconsole.console_redir() + +onmessage = async (ev) => { + try { + eval(ev.data) + } catch (/** @type {any} */error) { + console.log(`\n\nTool/Function call raised an exception:${error.name}:${error.message}\n\n`) + } + tconsole.console_revert() + postMessage(tconsole.gConsoleStr) +} From 7ea9bf6bf632d21fab4de9c9691f6f5e34758636 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 00:54:21 +0530 Subject: [PATCH 035/187] SimpleChatTC: Pass around structured objects wrt tool worker The request for code to run as well as the resultant response data both need to follow a structured object convention, so that it is easy to map a request and the corresponding response to some extent. --- tools/server/public_simplechat/toolsworker.mjs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/toolsworker.mjs b/tools/server/public_simplechat/toolsworker.mjs index b17c5bb197cea..d17b772b4f66b 100644 --- a/tools/server/public_simplechat/toolsworker.mjs +++ b/tools/server/public_simplechat/toolsworker.mjs @@ -4,16 +4,23 @@ // by Humans for All // +/** + * Expects to get a message with identifier name and code to run + * Posts message with identifier name and data captured from console.log outputs + */ + + import * as tconsole from "./toolsconsole.mjs" + tconsole.console_redir() -onmessage = async (ev) => { +self.onmessage = function (ev) { try { - eval(ev.data) + eval(ev.data.code) } catch (/** @type {any} */error) { - console.log(`\n\nTool/Function call raised an exception:${error.name}:${error.message}\n\n`) + console.log(`\n\nTool/Function call "${ev.data.name}" raised an exception:${error.name}:${error.message}\n\n`) } tconsole.console_revert() - postMessage(tconsole.gConsoleStr) + self.postMessage({ name: ev.data.name, data: tconsole.gConsoleStr}) } From 466474822cd9fcaee32a81aac8a71d1861102e50 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 00:58:23 +0530 Subject: [PATCH 036/187] SimpleChatTC: Actual tool call implementations simplified These no longer need to worry about * setting up the console.log related redirection to capture the generated outputs, nor about * setting up a dynamic function for executing the needed tool call related code The web worker setup to help run tool calls in a relatively isolated environment independent of the main browser env, takes care of these. One needs to only worry about getting the handle to the web worker to use and inturn pass the need code wrt the tool call to it. --- tools/server/public_simplechat/tooljs.mjs | 26 +++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 23b340e514b17..5ee8e83004b35 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -7,7 +7,7 @@ // -import * as tconsole from "./toolsconsole.mjs" +let gToolsWorker = /** @type{Worker} */(/** @type {unknown} */(null)); let js_meta = { @@ -31,16 +31,12 @@ let js_meta = { /** * Implementation of the javascript interpretor logic. Minimal skeleton for now. - * ALERT: Has access to the javascript environment and can mess with it and beyond + * ALERT: Has access to the javascript web worker environment and can mess with it and beyond * @param {string} toolname * @param {any} obj */ function js_run(toolname, obj) { - tconsole.console_redir() - let func = new Function(obj["code"]) - func() - tconsole.console_revert() - tc_switch[toolname]["result"] = tconsole.gConsoleStr + gToolsWorker.postMessage({ name: toolname, code: obj["code"]}) } @@ -65,16 +61,12 @@ let calc_meta = { /** * Implementation of the simple calculator logic. Minimal skeleton for now. - * ALERT: Has access to the javascript environment and can mess with it and beyond + * ALERT: Has access to the javascript web worker environment and can mess with it and beyond * @param {string} toolname * @param {any} obj */ function calc_run(toolname, obj) { - tconsole.console_redir() - let func = new Function(`console.log(${obj["arithexpr"]})`) - func() - tconsole.console_revert() - tc_switch[toolname]["result"] = tconsole.gConsoleStr + gToolsWorker.postMessage({ name: toolname, code: `console.log(${obj["arithexpr"]})`}) } @@ -94,3 +86,11 @@ export let tc_switch = { } } + +/** + * Used to get hold of the web worker to use for running tool/function call related code + * @param {Worker} toolsWorker + */ +export function init(toolsWorker) { + gToolsWorker = toolsWorker +} From 5933b2866966dc454327dcac2b5f7789923653b9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 01:06:00 +0530 Subject: [PATCH 037/187] SimpleChatTC: Get ready for decoupled tool call response tools manager/module * setup the web worker that will help execute the tool call related codes in a js environment that is isolated from the browsers main js environment * pass the web worker to the tool call providers, for them to use * dont wait for the result from the tool call, as it will be got later asynchronously through a message * allow users of the tools manager to register a call back, which will be called when ever a message is got from the web worker containing response wrt previously requested tool call execution. simplechat * decouple toolcall response handling and toolcall requesting logic * setup a timeout to take back control if tool call takes up too much time. Inturn help alert the ai model, that the tool call took up too much time and so was aborted, by placing a approriate tagged tool response into user query area. * register a call back that will be called when response is got asynchronously wrt anye requested tool calls. In turn take care of updating the user query area with response got wrt the tool call, along with tool response tag around it. --- tools/server/public_simplechat/simplechat.js | 24 ++++++++++++++++---- tools/server/public_simplechat/tools.mjs | 21 ++++++++++++++--- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 599b147adcd59..925a181dbfa87 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -539,13 +539,15 @@ class SimpleChat { } /** - * Call the requested tool/function and get its response + * Call the requested tool/function. + * Returns undefined, if the call was placed successfully + * Else some appropriate error message will be returned. * @param {string} toolname * @param {string} toolargs */ async handle_toolcall(toolname, toolargs) { if (toolname === "") { - return undefined + return "Tool/Function call name not specified" } try { return await tools.tool_call(toolname, toolargs) @@ -675,6 +677,11 @@ class MultiChatUI { this.handle_tool_run(this.curChatId); }) + tools.setup((name, data)=>{ + this.elInUser.value = `${data}` + this.ui_reset_userinput(false) + }) + this.elInUser.addEventListener("keyup", (ev)=> { // allow user to insert enter into their message using shift+enter. // while just pressing enter key will lead to submitting. @@ -771,17 +778,24 @@ class MultiChatUI { /** * Handle running of specified tool call if any, for the specified chat session. + * Also sets up a timeout, so that user gets control back to interact with the ai model. * @param {string} chatId */ async handle_tool_run(chatId) { let chat = this.simpleChats[chatId]; this.elInUser.value = "toolcall in progress..."; this.elInUser.disabled = true; - let toolResult = await chat.handle_toolcall(this.elInToolName.value, this.elInToolArgs.value) + let toolname = this.elInToolName.value.trim() + let toolResult = await chat.handle_toolcall(toolname, this.elInToolArgs.value) if (toolResult !== undefined) { this.elInUser.value = `${toolResult}` + this.ui_reset_userinput(false) + } else { + setTimeout(() => { + this.elInUser.value = `Tool/Function call ${toolname} taking too much time, aborting...` + this.ui_reset_userinput(false) + }, 10000) } - this.ui_reset_userinput(toolResult === undefined); } /** @@ -1073,7 +1087,7 @@ function startme() { document["gMe"] = gMe; document["du"] = du; document["tools"] = tools; - tools.setup() + tools.init() for (let cid of gMe.defaultChatIds) { gMe.multiChat.new_chat_session(cid); } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index d75d3eb7bf73d..4ece70ae565ed 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -8,12 +8,14 @@ import * as tjs from './tooljs.mjs' +let gToolsWorker = new Worker('./toolsworker.mjs'); /** * @type {Object>} */ export let tc_switch = {} -export function setup() { +export function init() { + tjs.init(gToolsWorker) for (const key in tjs.tc_switch) { tc_switch[key] = tjs.tc_switch[key] } @@ -27,9 +29,22 @@ export function meta() { return tools } +/** + * Setup the callback that will be called when ever message + * is recieved from the Tools Web Worker. + * @param {(name: string, data: string) => void} cb + */ +export function setup(cb) { + gToolsWorker.onmessage = function (ev) { + cb(ev.data.name, ev.data.data) + } +} + /** - * Try call the specified tool/function call and return its response + * Try call the specified tool/function call. + * Returns undefined, if the call was placed successfully + * Else some appropriate error message will be returned. * @param {string} toolname * @param {string} toolargs */ @@ -38,7 +53,7 @@ export async function tool_call(toolname, toolargs) { if (fn == toolname) { try { tc_switch[fn]["handler"](fn, JSON.parse(toolargs)) - return tc_switch[fn]["result"] + return undefined } catch (/** @type {any} */error) { return `Tool/Function call raised an exception:${error.name}:${error.message}` } From 50be171bccf24c28d810db02f5d5c7ae544fbbb5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 01:34:22 +0530 Subject: [PATCH 038/187] SimpleChatTC: Web worker flow initial go cleanup Had forgotten to specify type as module wrt web worker, in order to allow it to import the toolsconsole module. Had forgotten to maintain the id of the timeout handler, which is needed to clear/stop the timeout handler from triggering, if tool call response is got well in time. As I am currently reverting the console redirection at end of handling a tool call code in the web worker message handler, I need to setup the redirection each time. Also I had forgotten to clear the console.log capture data space, before a new tool call code is executed, this is also fixed by this change. TODO: Need to abort the tool call code execution in the web worker if possible in future, if the client / browser side times out waiting for tool call response, ie if the tool call code is taking up too much time. --- tools/server/public_simplechat/simplechat.js | 3 ++- tools/server/public_simplechat/tools.mjs | 2 +- tools/server/public_simplechat/toolsworker.mjs | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 925a181dbfa87..6b897448c2f5c 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -678,6 +678,7 @@ class MultiChatUI { }) tools.setup((name, data)=>{ + clearTimeout(this.idTimeOut) this.elInUser.value = `${data}` this.ui_reset_userinput(false) }) @@ -791,7 +792,7 @@ class MultiChatUI { this.elInUser.value = `${toolResult}` this.ui_reset_userinput(false) } else { - setTimeout(() => { + this.idTimeOut = setTimeout(() => { this.elInUser.value = `Tool/Function call ${toolname} taking too much time, aborting...` this.ui_reset_userinput(false) }, 10000) diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 4ece70ae565ed..75fe56e4f4e5f 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -8,7 +8,7 @@ import * as tjs from './tooljs.mjs' -let gToolsWorker = new Worker('./toolsworker.mjs'); +let gToolsWorker = new Worker('./toolsworker.mjs', { type: 'module' }); /** * @type {Object>} */ diff --git a/tools/server/public_simplechat/toolsworker.mjs b/tools/server/public_simplechat/toolsworker.mjs index d17b772b4f66b..e370fd0a9df34 100644 --- a/tools/server/public_simplechat/toolsworker.mjs +++ b/tools/server/public_simplechat/toolsworker.mjs @@ -13,9 +13,8 @@ import * as tconsole from "./toolsconsole.mjs" -tconsole.console_redir() - self.onmessage = function (ev) { + tconsole.console_redir() try { eval(ev.data.code) } catch (/** @type {any} */error) { From 44cfebcdd08ea25c27aa2b8ae7da6066333d5bc9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 01:56:34 +0530 Subject: [PATCH 039/187] SimpleChatTC: Increase the sliding window context to Last4 QA As the tool calling, if enabled, will need access to last few user query and ai assistant responses (which will also include in them the tool call requests and the corresponding results), so that the model can build answers based on its tool call reqs and got responses, and also given that most of the models these days have sufficiently large context windows, so the sliding window context implemented by SimpleChat logic has been increased by default to include last 4 query and their responses roughlty. --- tools/server/public_simplechat/simplechat.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 6b897448c2f5c..4804c88ea0d28 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -103,7 +103,7 @@ let gUsageMsg = `
      • submit tool response placed into user query textarea
      -
    • Default ContextWindow = [System, Last Query+Resp, Cur Query].
    • +
    • Default ContextWindow = [System, Last4 Query+Resp, Cur Query].
      • ChatHistInCtxt, MaxTokens, ModelCtxt window to expand
      @@ -886,7 +886,7 @@ class Me { this.bCompletionFreshChatAlways = true; this.bCompletionInsertStandardRolePrefix = false; this.bTrimGarbage = true; - this.iRecentUserMsgCnt = 2; + this.iRecentUserMsgCnt = 5; this.sRecentUserMsgCnt = { "Full": -1, "Last0": 1, From 6f137f231186e0defb1a0275fc5c02256ada8219 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 02:28:42 +0530 Subject: [PATCH 040/187] SimpleChatTC: Update readme.md wrt latest updates. 2k maxtokens --- tools/server/public_simplechat/readme.md | 51 +++++++++++--------- tools/server/public_simplechat/simplechat.js | 4 +- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 6f9f986b01844..c8cb786c3c6c8 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -239,10 +239,10 @@ It is attached to the document object. Some of these can also be updated using t be set if needed using the settings ui. iRecentUserMsgCnt - a simple minded SlidingWindow to limit context window load at Ai Model end. - This is disabled by default. However if enabled, then in addition to latest system message, only - the last/latest iRecentUserMsgCnt user messages after the latest system prompt and its responses - from the ai model will be sent to the ai-model, when querying for a new response. IE if enabled, - only user messages after the latest system message/prompt will be considered. + This is set to 5 by default. So in addition to latest system message, last/latest iRecentUserMsgCnt + user messages after the latest system prompt and its responses from the ai model will be sent + to the ai-model, when querying for a new response. Note that if enabled, only user messages after + the latest system message/prompt will be considered. This specified sliding window user message count also includes the latest user query. <0 : Send entire chat history to server @@ -282,9 +282,11 @@ full chat history. This way if there is any response with garbage/repeatation, i mess with things beyond the next question/request/query, in some ways. The trim garbage option also tries to help avoid issues with garbage in the context to an extent. -Set max_tokens to 1024, so that a relatively large previous reponse doesnt eat up the space -available wrt next query-response. However dont forget that the server when started should -also be started with a model context size of 1k or more, to be on safe side. +Set max_tokens to 2048, so that a relatively large previous reponse doesnt eat up the space +available wrt next query-response. While parallely allowing a good enough context size for +some amount of the chat history in the current session to influence future answers. However +dont forget that the server when started should also be started with a model context size of +2k or more, to be on safe side. The /completions endpoint of tools/server doesnt take max_tokens, instead it takes the internal n_predict, for now add the same here on the client side, maybe later add max_tokens @@ -321,9 +323,9 @@ work. ### Tool Calling -ALERT: Currently the way this is implemented, it is dangerous to use this, unless one verifies -all the tool calls requested and the responses generated manually to ensure everything is fine, -during interaction with ai modles with tools support. +ALERT: The simple minded way in which this is implemented, it can be dangerous in the worst case, +Always remember to verify all the tool calls requested and the responses generated manually to +ensure everything is fine, during interaction with ai modles with tools support. #### Builtin Tools @@ -332,10 +334,10 @@ The following tools/functions are currently provided by default * run_javascript_function_code - which can be used to run some javascript code in the browser context. -Currently the generated code / expression is run through a simple dynamic function mechanism. -May update things, in future, so that a WebWorker is used to avoid exposing browser global scope -to the generated code directly. Either way always remember to cross check the tool requests and -generated responses when using tool calling. +Currently the generated code / expression is run through a simple minded eval inside a web worker +mechanism. Use of WebWorker helps avoid exposing browser global scope to the generated code directly. +However any shared web worker scope isnt isolated. Either way always remember to cross check the tool +requests and generated responses when using tool calling. May add * web_fetch along with a corresponding simple local web proxy/caching server logic that can bypass @@ -343,19 +345,20 @@ May add Inturn maybe with a white list of allowed sites to access or so. -#### Extending wiht new tools +#### Extending with new tools Provide a descriptive meta data explaining the tool / function being provided for tool calling, as well as its arguments. -Provide a handler which should implement the specified tool / function call. It should place -the result to be sent back to the ai model in the result key of the tc_switch entry for the -corresponding tool. +Provide a handler which should implement the specified tool / function call or rather constructs +the code to be run to get the tool / function call job done, and inturn pass the same to the +provided web worker to get it executed. Remember to use console.log while generating any response +that should be sent back to the ai model, in your constructed code. -Update the tc_switch to include a object entry for the tool, which inturn icnludes +Update the tc_switch to include a object entry for the tool, which inturn includes * the meta data as well as * a reference to the handler and also -* the result key +* the result key (was used previously, may use in future, but for now left as is) #### Mapping tool calls and responses to normal assistant - user chat flow @@ -368,16 +371,16 @@ tagged response in the subsequent user block. This allows the GenAi/LLM to be aware of the tool calls it made as well as the responses it got, so that it can incorporate the results of the same in the subsequent chat / interactions. -NOTE: This flow tested to be ok enough with Gemma-3N-E4B-it-Q8_0 LLM ai model for now. +NOTE: This flow tested to be ok enough with Gemma-3N-E4B-it-Q8_0 LLM ai model for now. Logically +given the way current ai models work, most of them should understand things as needed, but need +to test this with other ai models later. TODO: Need to think later, whether to continue this simple flow, or atleast use tool role wrt -the tool call responses or even go further and have the logically seperate tool_call request +the tool call responses or even go further and have the logically seperate tool_calls request structures also. #### ToDo -Update to use web worker. - WebFetch and Local web proxy/caching server Try and trap promises based flows to ensure all generated results or errors if any are caught diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 4804c88ea0d28..9c791222188d5 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -907,8 +907,8 @@ class Me { this.apiRequestOptions = { "model": "gpt-3.5-turbo", "temperature": 0.7, - "max_tokens": 1024, - "n_predict": 1024, + "max_tokens": 2048, + "n_predict": 2048, "cache_prompt": false, //"frequency_penalty": 1.2, //"presence_penalty": 1.2, From dbf050c768abcbec5f0fff98d2cc3040d3323c6d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 02:35:24 +0530 Subject: [PATCH 041/187] SimpleChatTC: update descs to indicate use of web workers ie wrt the tool calls provided. --- tools/server/public_simplechat/tooljs.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 5ee8e83004b35..6aea9a5ee4d21 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -14,13 +14,13 @@ let js_meta = { "type": "function", "function": { "name": "run_javascript_function_code", - "description": "Runs given code using function constructor mechanism in a browser's javascript environment and returns the console.log outputs of the execution after few seconds", + "description": "Runs given code using eval within a web worker context in a browser's javascript environment and returns the console.log outputs of the execution after few seconds", "parameters": { "type": "object", "properties": { "code": { "type": "string", - "description": "The code belonging to the dynamic function to run in the browser's javascript interpreter environment." + "description": "The code that will be run using eval within a web worker in the browser's javascript interpreter environment." } }, "required": ["code"] @@ -44,7 +44,7 @@ let calc_meta = { "type": "function", "function": { "name": "simple_calculator", - "description": "Calculates the provided arithmatic expression using console.log of a browser's javascript interpreter and returns the output of the execution once it is done in few seconds", + "description": "Calculates the provided arithmatic expression using console.log within a web worker of a browser's javascript interpreter environment and returns the output of the execution once it is done in few seconds", "parameters": { "type": "object", "properties": { From 39c1c01cc9fd42e6f416614f090f23bc1a0bdc88 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 16:48:40 +0530 Subject: [PATCH 042/187] SimpleChatTC:ChatMessage: AssistantResponse into chat message class Modify the constructor, newFrom and clear towards this goal. --- tools/server/public_simplechat/simplechat.js | 27 ++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 9c791222188d5..4183ca87a25b9 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -38,23 +38,34 @@ class ApiEP { } -class AssistantResponse { +class ChatMessage { - constructor(content="", toolname="", toolargs="", trimmedContent="") { - /** @type {Object} */ - this.response = { content: content, toolname: toolname, toolargs: toolargs, trimmedContent: trimmedContent }; + /** + * Represent a Message in the Chat + * @param {string} role + * @param {string} content + * @param {Array} tool_calls + * @param {string} trimmedContent + */ + constructor(role = "", content="", tool_calls=[], trimmedContent="") { + /** @type {Object} */ + this.ns = { role: role, content: content, tool_calls: tool_calls } + this.trimmedContent = trimmedContent; } /** * Create a new instance from an existing instance - * @param {AssistantResponse} old + * @param {ChatMessage} old */ static newFrom(old) { - return new AssistantResponse(old.response.content, old.response.toolname, old.response.toolargs, old.response.trimmedContent) + return new ChatMessage(old.ns.role, old.ns.content, old.ns.tool_calls, old.trimmedContent) } clear() { - this.response = { content: "", toolname: "", toolargs: "", trimmedContent: "" }; + this.ns.role = ""; + this.ns.content = ""; + this.ns.tool_calls = []; + this.trimmedContent = ""; } /** @@ -71,7 +82,7 @@ class AssistantResponse { } has_toolcall() { - if (this.response.toolname.trim() == "") { + if (this.ns.tool_calls.trim() == "") { return false } return true From e9a7871f9388a4b48e8a3d32501ba7085cb140fc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 17:59:06 +0530 Subject: [PATCH 043/187] SimpleChatTC:ChatMessageEx: UpdateStream logic Rename ChatMessage to ChatMessageEx. Add typedefs for NSToolCall and NSChatMessage, they represent the way the corresponding data is structured in network hs. Add logic to build the ChatMessageEx from data got over network in streaming mode. --- tools/server/public_simplechat/simplechat.js | 69 ++++++++++++++++---- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 4183ca87a25b9..608479c8623ea 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -38,7 +38,15 @@ class ApiEP { } -class ChatMessage { +/** + * @typedef {{id: string, type: string, function: {name: string, arguments: string}}} NSToolCalls + */ + +/** + * @typedef {{role: string, content: string, tool_calls: Array}} NSChatMessage + */ + +class ChatMessageEx { /** * Represent a Message in the Chat @@ -48,17 +56,17 @@ class ChatMessage { * @param {string} trimmedContent */ constructor(role = "", content="", tool_calls=[], trimmedContent="") { - /** @type {Object} */ + /** @type {NSChatMessage} */ this.ns = { role: role, content: content, tool_calls: tool_calls } this.trimmedContent = trimmedContent; } /** * Create a new instance from an existing instance - * @param {ChatMessage} old + * @param {ChatMessageEx} old */ static newFrom(old) { - return new ChatMessage(old.ns.role, old.ns.content, old.ns.tool_calls, old.trimmedContent) + return new ChatMessageEx(old.ns.role, old.ns.content, old.ns.tool_calls, old.trimmedContent) } clear() { @@ -69,16 +77,53 @@ class ChatMessage { } /** - * Helps collate the latest response from the server/ai-model, as it is becoming available. - * This is mainly useful for the stream mode. - * @param {{key: string, value: string}} resp + * Update based on the drip by drip data got from network in streaming mode + * @param {any} nwo + * @param {string} apiEP */ - append_response(resp) { - if (resp.value == null) { - return + update_stream(nwo, apiEP) { + if (apiEP == ApiEP.Type.Chat) { + if (nwo["choices"][0]["finish_reason"] === null) { + let content = nwo["choices"][0]["delta"]["content"]; + if (content !== undefined) { + if (content !== null) { + this.ns.content += content; + } + } else { + let toolCalls = nwo["choices"][0]["delta"]["tool_calls"]; + if ( toolCalls !== undefined) { + if (toolCalls[0]["function"]["name"] !== undefined) { + this.ns.tool_calls.push(toolCalls[0]) + /* + this.ns.tool_calls[0].function.name = toolCalls[0]["function"]["name"]; + this.ns.tool_calls[0].id = toolCalls[0]["id"]; + this.ns.tool_calls[0].type = toolCalls[0]["type"]; + this.ns.tool_calls[0].function.arguments = toolCalls[0]["function"]["arguments"] + */ + } else { + if (toolCalls[0]["function"]["arguments"] !== undefined) { + this.ns.tool_calls[0].function.arguments += toolCalls[0]["function"]["arguments"]; + } + } + } + } + } + } else { + try { + this.ns.content += nwo["choices"][0]["text"]; + } catch { + this.ns.content += nwo["content"]; + } } - console.debug(resp.key, resp.value) - this.response[resp.key] += resp.value; + } + + /** + * Update based on the data got from network in oneshot mode + * @param {any} nwo + * @param {string} apiEP + */ + update_oneshot(nwo, apiEP) { + } has_toolcall() { From 340ae0c6c1c657f145be5d31a2009a98b2374c27 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 18:28:49 +0530 Subject: [PATCH 044/187] SimpleChatTC:ChatMessageEx:cleanup, HasToolCalls, ContentEquiv Update HasToolCalls and ContentEquiv to work with new structure --- tools/server/public_simplechat/simplechat.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 608479c8623ea..993094fe424bb 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -77,7 +77,8 @@ class ChatMessageEx { } /** - * Update based on the drip by drip data got from network in streaming mode + * Update based on the drip by drip data got from network in streaming mode. + * Tries to support both Chat and Completion endpoints * @param {any} nwo * @param {string} apiEP */ @@ -88,12 +89,14 @@ class ChatMessageEx { if (content !== undefined) { if (content !== null) { this.ns.content += content; + } else { + this.ns.role = nwo["choices"][0]["delta"]["role"]; } } else { let toolCalls = nwo["choices"][0]["delta"]["tool_calls"]; - if ( toolCalls !== undefined) { + if (toolCalls !== undefined) { if (toolCalls[0]["function"]["name"] !== undefined) { - this.ns.tool_calls.push(toolCalls[0]) + this.ns.tool_calls.push(toolCalls[0]); /* this.ns.tool_calls[0].function.name = toolCalls[0]["function"]["name"]; this.ns.tool_calls[0].id = toolCalls[0]["id"]; @@ -127,17 +130,17 @@ class ChatMessageEx { } has_toolcall() { - if (this.ns.tool_calls.trim() == "") { + if (this.ns.tool_calls.length == 0) { return false } return true } content_equiv() { - if (this.response.content !== "") { - return this.response.content; + if (this.ns.content !== "") { + return this.ns.content; } else if (this.has_toolcall()) { - return `\n${this.response.toolname}\n${this.response.toolargs}\n`; + return `\n${this.ns.tool_calls[0].function.name}\n${this.ns.tool_calls[0].function.arguments}\n`; } else { return "" } @@ -184,7 +187,7 @@ class SimpleChat { */ this.xchat = []; this.iLastSys = -1; - this.latestResponse = new AssistantResponse(); + this.latestResponse = new ChatMessageEx(); } clear() { From 0629f79886e96e8203980f4d65e847e0b9652825 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 18:36:25 +0530 Subject: [PATCH 045/187] SimpleChatTC:ChatMessage: remove ResponseExtractStream Use the equivalent update_stream directly added to ChatMessageEx. update_stream is also more generic to some extent and also directly implemented by the ChatMessageEx class. --- tools/server/public_simplechat/simplechat.js | 42 ++------------------ 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 993094fe424bb..33c4e4b3f72df 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -83,6 +83,7 @@ class ChatMessageEx { * @param {string} apiEP */ update_stream(nwo, apiEP) { + console.debug(nwo, apiEP) if (apiEP == ApiEP.Type.Chat) { if (nwo["choices"][0]["finish_reason"] === null) { let content = nwo["choices"][0]["delta"]["content"]; @@ -408,43 +409,6 @@ class SimpleChat { return assistant; } - /** - * Extract the ai-model/assistant's response from the http response got in streaming mode. - * @param {any} respBody - * @param {string} apiEP - */ - response_extract_stream(respBody, apiEP) { - console.debug(respBody, apiEP) - let key = "content" - let assistant = ""; - if (apiEP == ApiEP.Type.Chat) { - if (respBody["choices"][0]["finish_reason"] === null) { - if (respBody["choices"][0]["delta"]["content"] !== undefined) { - assistant = respBody["choices"][0]["delta"]["content"]; - } else { - if (respBody["choices"][0]["delta"]["tool_calls"] !== undefined) { - if (respBody["choices"][0]["delta"]["tool_calls"][0]["function"]["name"] !== undefined) { - key = "toolname"; - assistant = respBody["choices"][0]["delta"]["tool_calls"][0]["function"]["name"]; - } else { - if (respBody["choices"][0]["delta"]["tool_calls"][0]["function"]["arguments"] !== undefined) { - key = "toolargs"; - assistant = respBody["choices"][0]["delta"]["tool_calls"][0]["function"]["arguments"]; - } - } - } - } - } - } else { - try { - assistant = respBody["choices"][0]["text"]; - } catch { - assistant = respBody["content"]; - } - } - return { key: key, value: assistant }; - } - /** * Allow setting of system prompt, but only at begining. * @param {string} sysPrompt @@ -541,7 +505,7 @@ class SimpleChat { } let curJson = JSON.parse(curLine); console.debug("DBUG:SC:PART:Json:", curJson); - this.latestResponse.append_response(this.response_extract_stream(curJson, apiEP)); + this.latestResponse.update_stream(curJson, apiEP); } elP.innerText = this.latestResponse.content_equiv() elP.scrollIntoView(false); @@ -550,7 +514,7 @@ class SimpleChat { } } console.debug("DBUG:SC:PART:Full:", this.latestResponse.content_equiv()); - return AssistantResponse.newFrom(this.latestResponse); + return ChatMessageEx.newFrom(this.latestResponse); } /** From bb25aa0ea2cb41a96874bf6dca016ba1a4b4c920 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 18:52:18 +0530 Subject: [PATCH 046/187] SimpleChatTC:ChatMessageEx: add update_oneshot response_extract logic moved directly into ChatMessageEx as update oneshot, with suitable adjustments. Inturn use the same directly. --- tools/server/public_simplechat/simplechat.js | 39 ++++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 33c4e4b3f72df..9b29081a773cc 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -127,7 +127,15 @@ class ChatMessageEx { * @param {string} apiEP */ update_oneshot(nwo, apiEP) { - + if (apiEP == ApiEP.Type.Chat) { + this.ns.content = nwo["choices"][0]["message"]["content"]; + } else { + try { + this.ns.content = nwo["choices"][0]["text"]; + } catch { + this.ns.content = nwo["content"]; + } + } } has_toolcall() { @@ -389,25 +397,6 @@ class SimpleChat { } } - /** - * Extract the ai-model/assistant's response from the http response got. - * Optionally trim the message wrt any garbage at the end. - * @param {any} respBody - * @param {string} apiEP - */ - response_extract(respBody, apiEP) { - let assistant = new AssistantResponse(); - if (apiEP == ApiEP.Type.Chat) { - assistant.response.content = respBody["choices"][0]["message"]["content"]; - } else { - try { - assistant.response.content = respBody["choices"][0]["text"]; - } catch { - assistant.response.content = respBody["content"]; - } - } - return assistant; - } /** * Allow setting of system prompt, but only at begining. @@ -525,7 +514,9 @@ class SimpleChat { async handle_response_oneshot(resp, apiEP) { let respBody = await resp.json(); console.debug(`DBUG:SimpleChat:SC:${this.chatId}:HandleUserSubmit:RespBody:${JSON.stringify(respBody)}`); - return this.response_extract(respBody, apiEP); + let cm = new ChatMessageEx() + cm.update_oneshot(respBody, apiEP) + return cm } /** @@ -553,9 +544,9 @@ class SimpleChat { theResp = await this.handle_response_oneshot(resp, apiEP); } if (gMe.bTrimGarbage) { - let origMsg = theResp.response.content; - theResp.response.content = du.trim_garbage_at_end(origMsg); - theResp.response.trimmedContent = origMsg.substring(theResp.response.content.length); + let origMsg = theResp.ns.content; + theResp.ns.content = du.trim_garbage_at_end(origMsg); + theResp.trimmedContent = origMsg.substring(theResp.ns.content.length); } this.add(Roles.Assistant, theResp.content_equiv()); return theResp; From ae00cb2ef972a64e73856f73b6c0bddd99d6149f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 19:03:48 +0530 Subject: [PATCH 047/187] SimpleChatTC:ChatMessageEx: ods load, system prompt related these have been updated to work with ChatMessageEx to an extent --- tools/server/public_simplechat/simplechat.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 9b29081a773cc..b7262369bc6e5 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -179,7 +179,7 @@ let gUsageMsg = ` `; -/** @typedef {{role: string, content: string}[]} ChatMessages */ +/** @typedef {ChatMessageEx[]} ChatMessages */ /** @typedef {{iLastSys: number, xchat: ChatMessages}} SimpleChatODS */ @@ -222,7 +222,11 @@ class SimpleChat { /** @type {SimpleChatODS} */ let ods = JSON.parse(sods); this.iLastSys = ods.iLastSys; - this.xchat = ods.xchat; + this.xchat = []; + for (const cur of ods.xchat) { + // TODO: May have to account for missing fields + this.xchat.push(new ChatMessageEx(cur.ns.role, cur.ns.content, cur.ns.tool_calls, cur.trimmedContent)) + } } /** @@ -410,10 +414,10 @@ class SimpleChat { } } else { if (sysPrompt.length > 0) { - if (this.xchat[0].role !== Roles.System) { + if (this.xchat[0].ns.role !== Roles.System) { console.error(`ERRR:SimpleChat:SC:${msgTag}:You need to specify system prompt before any user query, ignoring...`); } else { - if (this.xchat[0].content !== sysPrompt) { + if (this.xchat[0].ns.content !== sysPrompt) { console.error(`ERRR:SimpleChat:SC:${msgTag}:You cant change system prompt, mid way through, ignoring...`); } } @@ -437,7 +441,7 @@ class SimpleChat { return this.add(Roles.System, sysPrompt); } - let lastSys = this.xchat[this.iLastSys].content; + let lastSys = this.xchat[this.iLastSys].ns.content; if (lastSys !== sysPrompt) { return this.add(Roles.System, sysPrompt); } @@ -451,7 +455,7 @@ class SimpleChat { if (this.iLastSys == -1) { return ""; } - let sysPrompt = this.xchat[this.iLastSys].content; + let sysPrompt = this.xchat[this.iLastSys].ns.content; return sysPrompt; } From e1e1d4297bf44ccd914bfb61d9743294efa72f6e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 19:33:24 +0530 Subject: [PATCH 048/187] SimpleChatTC:ChatMessageEx: RecentChat, GetSystemLatest GetSystemLatest and its users updated wrt ChatMessageEx. RecentChat updated wrt ChatMessageEx. Also now irrespective of whether full history is being retrieved or only a subset, both cases refer to the ChatMessageEx instances in SimpleChat.xchat without creating new instances of anything. --- tools/server/public_simplechat/simplechat.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index b7262369bc6e5..d2d8be73a0517 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -248,8 +248,8 @@ class SimpleChat { /** @type{ChatMessages} */ let rchat = []; let sysMsg = this.get_system_latest(); - if (sysMsg.length != 0) { - rchat.push({role: Roles.System, content: sysMsg}); + if (sysMsg.ns.content.length != 0) { + rchat.push(sysMsg) } let iUserCnt = 0; let iStart = this.xchat.length; @@ -258,17 +258,17 @@ class SimpleChat { break; } let msg = this.xchat[i]; - if (msg.role == Roles.User) { + if (msg.ns.role == Roles.User) { iStart = i; iUserCnt += 1; } } for(let i = iStart; i < this.xchat.length; i++) { let msg = this.xchat[i]; - if (msg.role == Roles.System) { + if (msg.ns.role == Roles.System) { continue; } - rchat.push({role: msg.role, content: msg.content}); + rchat.push(msg) } return rchat; } @@ -453,10 +453,9 @@ class SimpleChat { */ get_system_latest() { if (this.iLastSys == -1) { - return ""; + return new ChatMessageEx(Roles.System); } - let sysPrompt = this.xchat[this.iLastSys].ns.content; - return sysPrompt; + return this.xchat[this.iLastSys]; } @@ -882,7 +881,7 @@ class MultiChatUI { console.error(`ERRR:SimpleChat:MCUI:HandleSessionSwitch:${chatId} missing...`); return; } - this.elInSystem.value = chat.get_system_latest(); + this.elInSystem.value = chat.get_system_latest().ns.content; this.elInUser.value = ""; chat.show(this.elDivChat); this.elInUser.focus(); @@ -959,7 +958,7 @@ class Me { chat.load(); queueMicrotask(()=>{ chat.show(div); - this.multiChat.elInSystem.value = chat.get_system_latest(); + this.multiChat.elInSystem.value = chat.get_system_latest().ns.content; }); }); div.appendChild(btn); From 3b73b00397405d2887da1cc5d88d6b2938c7ddd3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 21:02:22 +0530 Subject: [PATCH 049/187] SimpleChatTC:ChatMessageEx: Upd Add, rm sysPromptAtBeginOnly hlpr Simplify Add semantic by expecting any validation of stuff before adding to be done by the callers of Add and not by add itself. Also update it to expect ChatMessageEx object Update all users of add to follow the new syntax and semantic. Remove the old and ununsed AddSysPromptOnlyAtBegin helper --- tools/server/public_simplechat/simplechat.js | 61 ++++++-------------- 1 file changed, 19 insertions(+), 42 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index d2d8be73a0517..3c50d4f62694a 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -274,17 +274,14 @@ class SimpleChat { } /** - * Add an entry into xchat + * Add an entry into xchat. + * NOTE: A new copy is created and added into xchat. * Also update iLastSys system prompt index tracker - * @param {string} role - * @param {string|undefined|null} content + * @param {ChatMessageEx} chatMsg */ - add(role, content) { - if ((content == undefined) || (content == null) || (content == "")) { - return false; - } - this.xchat.push( {role: role, content: content} ); - if (role == Roles.System) { + add(chatMsg) { + this.xchat.push(ChatMessageEx.newFrom(chatMsg)); + if (chatMsg.ns.role == Roles.System) { this.iLastSys = this.xchat.length - 1; } this.save(); @@ -402,30 +399,6 @@ class SimpleChat { } - /** - * Allow setting of system prompt, but only at begining. - * @param {string} sysPrompt - * @param {string} msgTag - */ - add_system_begin(sysPrompt, msgTag) { - if (this.xchat.length == 0) { - if (sysPrompt.length > 0) { - return this.add(Roles.System, sysPrompt); - } - } else { - if (sysPrompt.length > 0) { - if (this.xchat[0].ns.role !== Roles.System) { - console.error(`ERRR:SimpleChat:SC:${msgTag}:You need to specify system prompt before any user query, ignoring...`); - } else { - if (this.xchat[0].ns.content !== sysPrompt) { - console.error(`ERRR:SimpleChat:SC:${msgTag}:You cant change system prompt, mid way through, ignoring...`); - } - } - } - } - return false; - } - /** * Allow setting of system prompt, at any time. * Updates the system prompt, if one was never set or if the newly passed is different from the last set system prompt. @@ -438,12 +411,12 @@ class SimpleChat { } if (this.iLastSys < 0) { - return this.add(Roles.System, sysPrompt); + return this.add(new ChatMessageEx(Roles.System, sysPrompt)); } let lastSys = this.xchat[this.iLastSys].ns.content; if (lastSys !== sysPrompt) { - return this.add(Roles.System, sysPrompt); + return this.add(new ChatMessageEx(Roles.System, sysPrompt)); } return false; } @@ -473,6 +446,7 @@ class SimpleChat { let tdUtf8 = new TextDecoder("utf-8"); let rr = resp.body.getReader(); this.latestResponse.clear() + this.latestResponse.ns.role = Roles.Assistant let xLines = new du.NewLines(); while(true) { let { value: cur, done: done } = await rr.read(); @@ -517,7 +491,7 @@ class SimpleChat { async handle_response_oneshot(resp, apiEP) { let respBody = await resp.json(); console.debug(`DBUG:SimpleChat:SC:${this.chatId}:HandleUserSubmit:RespBody:${JSON.stringify(respBody)}`); - let cm = new ChatMessageEx() + let cm = new ChatMessageEx(Roles.Assistant) cm.update_oneshot(respBody, apiEP) return cm } @@ -532,15 +506,16 @@ class SimpleChat { * @param {HTMLDivElement} elDiv */ async handle_response(resp, apiEP, elDiv) { - let theResp = null + let theResp = null; if (gMe.bStream) { try { theResp = await this.handle_response_multipart(resp, apiEP, elDiv); - this.latestResponse.clear() + this.latestResponse.clear(); } catch (error) { theResp = this.latestResponse; - this.add(Roles.Assistant, theResp.content_equiv()); - this.latestResponse.clear() + theResp.ns.role = Roles.Assistant; + this.add(theResp); + this.latestResponse.clear(); throw error; } } else { @@ -551,7 +526,8 @@ class SimpleChat { theResp.ns.content = du.trim_garbage_at_end(origMsg); theResp.trimmedContent = origMsg.substring(theResp.ns.content.length); } - this.add(Roles.Assistant, theResp.content_equiv()); + theResp.ns.role = Roles.Assistant; + this.add(theResp); return theResp; } @@ -761,10 +737,11 @@ class MultiChatUI { chat.add_system_anytime(this.elInSystem.value, chatId); let content = this.elInUser.value; - if (!chat.add(Roles.User, content)) { + if (content.trim() == "") { console.debug(`WARN:SimpleChat:MCUI:${chatId}:HandleUserSubmit:Ignoring empty user input...`); return; } + chat.add(new ChatMessageEx(Roles.User, content)) chat.show(this.elDivChat); let theUrl = ApiEP.Url(gMe.baseURL, apiEP); From aa80bf0117940140005d8950e8645fc9d7257007 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 21:36:11 +0530 Subject: [PATCH 050/187] SimpleChatTC:ChatMessageEx: Recent chat users upd Users of recent_chat updated to work with ChatMessageEx As part of same recent_chat_ns also added, for the case where the array of chat messages can be passed as is ie in the chat mode, provided it has only the network handshake representation of the messages. --- tools/server/public_simplechat/simplechat.js | 29 +++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 3c50d4f62694a..8bd8a285db380 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -273,6 +273,21 @@ class SimpleChat { return rchat; } + + /** + * Return recent chat messages in the format, + * which can be directly sent to the ai server. + * @param {number} iRecentUserMsgCnt - look at recent_chat for semantic + */ + recent_chat_ns(iRecentUserMsgCnt) { + let xchat = this.recent_chat(iRecentUserMsgCnt); + let chat = [] + for (const msg of xchat) { + chat.push(msg.ns) + } + return chat + } + /** * Add an entry into xchat. * NOTE: A new copy is created and added into xchat. @@ -299,8 +314,8 @@ class SimpleChat { } let last = undefined; for(const x of this.recent_chat(gMe.iRecentUserMsgCnt)) { - let entry = ui.el_create_append_p(`${x.role}: ${x.content}`, div); - entry.className = `role-${x.role}`; + let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, div); + entry.className = `role-${x.ns.role}`; last = entry; } if (last !== undefined) { @@ -338,7 +353,7 @@ class SimpleChat { * The needed fields/options are picked from a global object. * Add optional stream flag, if required. * Convert the json into string. - * @param {Object} obj + * @param {Object} obj */ request_jsonstr_extend(obj) { for(let k in gMe.apiRequestOptions) { @@ -358,7 +373,7 @@ class SimpleChat { */ request_messages_jsonstr() { let req = { - messages: this.recent_chat(gMe.iRecentUserMsgCnt), + messages: this.recent_chat_ns(gMe.iRecentUserMsgCnt), } return this.request_jsonstr_extend(req); } @@ -370,15 +385,15 @@ class SimpleChat { request_prompt_jsonstr(bInsertStandardRolePrefix) { let prompt = ""; let iCnt = 0; - for(const chat of this.recent_chat(gMe.iRecentUserMsgCnt)) { + for(const msg of this.recent_chat(gMe.iRecentUserMsgCnt)) { iCnt += 1; if (iCnt > 1) { prompt += "\n"; } if (bInsertStandardRolePrefix) { - prompt += `${chat.role}: `; + prompt += `${msg.ns.role}: `; } - prompt += `${chat.content}`; + prompt += `${msg.ns.content}`; } let req = { prompt: prompt, From 755505e8ce6bcf4587310f961b3921a2f8cd931f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 22:26:48 +0530 Subject: [PATCH 051/187] SimpleChatTC:ChatMessageEx: Cleanup remaining stuff wrt ChatMessageEx related required flow as well as avoid warnings --- tools/server/public_simplechat/simplechat.js | 27 ++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 8bd8a285db380..0d338ebaa1421 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -437,7 +437,7 @@ class SimpleChat { } /** - * Retrieve the latest system prompt. + * Retrieve the latest system prompt related chat message entry. */ get_system_latest() { if (this.iLastSys == -1) { @@ -609,19 +609,20 @@ class MultiChatUI { if (el == null) { throw Error(`ERRR:SimpleChat:MCUI:${msgTag} element missing in html...`); } else { + // @ts-ignore console.debug(`INFO:SimpleChat:MCUI:${msgTag} Id[${el.id}] Name[${el["name"]}]`); } } /** * Reset/Setup Tool Call UI parts as needed - * @param {AssistantResponse} ar + * @param {ChatMessageEx} ar */ ui_reset_toolcall_as_needed(ar) { if (ar.has_toolcall()) { this.elDivTool.hidden = false - this.elInToolName.value = ar.response.toolname - this.elInToolArgs.value = ar.response.toolargs + this.elInToolName.value = ar.ns.tool_calls[0].function.name + this.elInToolArgs.value = ar.ns.tool_calls[0].function.arguments this.elBtnTool.disabled = false } else { this.elDivTool.hidden = true @@ -659,7 +660,7 @@ class MultiChatUI { this.handle_session_switch(this.curChatId); } - this.ui_reset_toolcall_as_needed(new AssistantResponse()); + this.ui_reset_toolcall_as_needed(new ChatMessageEx()); this.elBtnSettings.addEventListener("click", (ev)=>{ this.elDivChat.replaceChildren(); @@ -747,7 +748,7 @@ class MultiChatUI { chat.clear(); } - this.ui_reset_toolcall_as_needed(new AssistantResponse()); + this.ui_reset_toolcall_as_needed(new ChatMessageEx()); chat.add_system_anytime(this.elInSystem.value, chatId); @@ -775,8 +776,8 @@ class MultiChatUI { let theResp = await chat.handle_response(resp, apiEP, this.elDivChat); if (chatId == this.curChatId) { chat.show(this.elDivChat); - if (theResp.response.trimmedContent.length > 0) { - let p = ui.el_create_append_p(`TRIMMED:${theResp.response.trimmedContent}`, this.elDivChat); + if (theResp.trimmedContent.length > 0) { + let p = ui.el_create_append_p(`TRIMMED:${theResp.trimmedContent}`, this.elDivChat); p.className="role-trim"; } } else { @@ -847,6 +848,11 @@ class MultiChatUI { } } + /** + * Create session button and append to specified Div element. + * @param {HTMLDivElement} elDiv + * @param {string} cid + */ create_session_btn(elDiv, cid) { let btn = ui.el_create_button(cid, (ev)=>{ let target = /** @type{HTMLButtonElement} */(ev.target); @@ -896,6 +902,7 @@ class Me { this.bCompletionInsertStandardRolePrefix = false; this.bTrimGarbage = true; this.iRecentUserMsgCnt = 5; + /** @type {Object} */ this.sRecentUserMsgCnt = { "Full": -1, "Last0": 1, @@ -1063,6 +1070,7 @@ class Me { this.show_settings_apirequestoptions(elDiv); let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{ + // @ts-ignore this.apiEP = ApiEP.Type[val]; }); elDiv.appendChild(sel.div); @@ -1094,8 +1102,11 @@ function startme() { console.log("INFO:SimpleChat:StartMe:Starting..."); gMe = new Me(); gMe.debug_disable(); + // @ts-ignore document["gMe"] = gMe; + // @ts-ignore document["du"] = du; + // @ts-ignore document["tools"] = tools; tools.init() for (let cid of gMe.defaultChatIds) { From 7fb5526fb8901129ca4a2c5084f9c91523b43812 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 23:27:03 +0530 Subject: [PATCH 052/187] SimpleChatTC:Load allows old and new ChatMessage(Ex) formats --- tools/server/public_simplechat/simplechat.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 0d338ebaa1421..ce09e764dcc43 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -224,8 +224,13 @@ class SimpleChat { this.iLastSys = ods.iLastSys; this.xchat = []; for (const cur of ods.xchat) { - // TODO: May have to account for missing fields - this.xchat.push(new ChatMessageEx(cur.ns.role, cur.ns.content, cur.ns.tool_calls, cur.trimmedContent)) + if (cur.ns == undefined) { + /** @typedef {{role: string, content: string}} OldChatMessage */ + let tcur = /** @type {OldChatMessage} */(/** @type {unknown} */(cur)); + this.xchat.push(new ChatMessageEx(tcur.role, tcur.content)) + } else { + this.xchat.push(new ChatMessageEx(cur.ns.role, cur.ns.content, cur.ns.tool_calls, cur.trimmedContent)) + } } } From 69cbc81f97e33df1f879f07498630c39331d4e42 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 00:04:29 +0530 Subject: [PATCH 053/187] SimpleChatTC:ChatMessageEx: send tool_calls, only if needed --- tools/server/public_simplechat/simplechat.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index ce09e764dcc43..2b82a4648da85 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -11,6 +11,7 @@ class Roles { static System = "system"; static User = "user"; static Assistant = "assistant"; + static Tool = "tool"; } class ApiEP { @@ -286,9 +287,14 @@ class SimpleChat { */ recent_chat_ns(iRecentUserMsgCnt) { let xchat = this.recent_chat(iRecentUserMsgCnt); - let chat = [] + let chat = []; for (const msg of xchat) { - chat.push(msg.ns) + let tmsg = ChatMessageEx.newFrom(msg); + if (!tmsg.has_toolcall()) { + // @ts-ignore + delete(tmsg.ns.tool_calls) + } + chat.push(tmsg.ns); } return chat } From f379e651fafbf66e21daae8b77bef8bced4e2898 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 01:20:27 +0530 Subject: [PATCH 054/187] SimpleChatTC:Propogate toolcall id through tool call chain Use HTMLElement's dataset to maintain tool call id along with the element which maintains the toolname. Pass it along to the tools manager and inturn the actual tool calls and through them to the web worker handling the tool call related code and inturn returning it back as part of the obj which is used to return the tool call result. Embed the tool call id, function name and function result into the content field of chat message in terms of a xml structure Also make use of tool role to send back the tool call result. Do note that currently the id, name and content are all embedded into the content field of the tool role message sent to the ai engine on the server. NOTE: Use the user query entry area for showing tool call result in the above mentioned xml form, as well as for user to enter their own queries. Based on presence of the xml format data at beginning the logic will treat it has a tool result and if not then as a normal user query. The css has been updated to help show tool results/msgs in a lightyellow background --- tools/server/public_simplechat/simplechat.css | 3 ++ tools/server/public_simplechat/simplechat.js | 46 +++++++++++++++---- tools/server/public_simplechat/tooljs.mjs | 10 ++-- tools/server/public_simplechat/tools.mjs | 9 ++-- .../server/public_simplechat/toolsworker.mjs | 2 +- 5 files changed, 53 insertions(+), 17 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.css b/tools/server/public_simplechat/simplechat.css index 13bfb80b48be8..d4755074b77c5 100644 --- a/tools/server/public_simplechat/simplechat.css +++ b/tools/server/public_simplechat/simplechat.css @@ -21,6 +21,9 @@ .role-user { background-color: lightgray; } +.role-tool { + background-color: lightyellow; +} .role-trim { background-color: lightpink; } diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 2b82a4648da85..281b2c15b958c 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -77,6 +77,17 @@ class ChatMessageEx { this.trimmedContent = ""; } + /** + * Create a all in one tool call result string + * @param {string} toolCallId + * @param {string} toolName + * @param {string} toolResult + */ + static createToolCallResultAllInOne(toolCallId, toolName, toolResult) { + return ` ${toolCallId} ${toolName} ${toolResult} `; + } + + /** * Update based on the drip by drip data got from network in streaming mode. * Tries to support both Chat and Completion endpoints @@ -561,15 +572,16 @@ class SimpleChat { * Call the requested tool/function. * Returns undefined, if the call was placed successfully * Else some appropriate error message will be returned. + * @param {string} toolcallid * @param {string} toolname * @param {string} toolargs */ - async handle_toolcall(toolname, toolargs) { + async handle_toolcall(toolcallid, toolname, toolargs) { if (toolname === "") { return "Tool/Function call name not specified" } try { - return await tools.tool_call(toolname, toolargs) + return await tools.tool_call(toolcallid, toolname, toolargs) } catch (/** @type {any} */error) { return `Tool/Function call raised an exception:${error.name}:${error.message}` } @@ -633,11 +645,13 @@ class MultiChatUI { if (ar.has_toolcall()) { this.elDivTool.hidden = false this.elInToolName.value = ar.ns.tool_calls[0].function.name + this.elInToolName.dataset.tool_call_id = ar.ns.tool_calls[0].id this.elInToolArgs.value = ar.ns.tool_calls[0].function.arguments this.elBtnTool.disabled = false } else { this.elDivTool.hidden = true this.elInToolName.value = "" + this.elInToolName.dataset.tool_call_id = "" this.elInToolArgs.value = "" this.elBtnTool.disabled = true } @@ -697,9 +711,9 @@ class MultiChatUI { this.handle_tool_run(this.curChatId); }) - tools.setup((name, data)=>{ + tools.setup((id, name, data)=>{ clearTimeout(this.idTimeOut) - this.elInUser.value = `${data}` + this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(id, name, data); this.ui_reset_userinput(false) }) @@ -744,6 +758,14 @@ class MultiChatUI { /** * Handle user query submit request, wrt specified chat session. + * NOTE: Currently the user query entry area is used for + * * showing and allowing edits by user wrt tool call results + * in a predfined simple xml format, + * ie before they submit tool result to ai engine on server + * * as well as for user to enter their own queries. + * Based on presence of the predefined xml format data at beginning + * the logic will treat it has a tool result and if not then as a + * normal user query. * @param {string} chatId * @param {string} apiEP */ @@ -768,7 +790,11 @@ class MultiChatUI { console.debug(`WARN:SimpleChat:MCUI:${chatId}:HandleUserSubmit:Ignoring empty user input...`); return; } - chat.add(new ChatMessageEx(Roles.User, content)) + if (content.startsWith("")) { + chat.add(new ChatMessageEx(Roles.Tool, content)) + } else { + chat.add(new ChatMessageEx(Roles.User, content)) + } chat.show(this.elDivChat); let theUrl = ApiEP.Url(gMe.baseURL, apiEP); @@ -808,13 +834,17 @@ class MultiChatUI { this.elInUser.value = "toolcall in progress..."; this.elInUser.disabled = true; let toolname = this.elInToolName.value.trim() - let toolResult = await chat.handle_toolcall(toolname, this.elInToolArgs.value) + let toolCallId = this.elInToolName.dataset.tool_call_id; + if (toolCallId === undefined) { + toolCallId = "??? ToolCallId Missing ???" + } + let toolResult = await chat.handle_toolcall(toolCallId, toolname, this.elInToolArgs.value) if (toolResult !== undefined) { - this.elInUser.value = `${toolResult}` + this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, toolResult); this.ui_reset_userinput(false) } else { this.idTimeOut = setTimeout(() => { - this.elInUser.value = `Tool/Function call ${toolname} taking too much time, aborting...` + this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, `Tool/Function call ${toolname} taking too much time, aborting...`); this.ui_reset_userinput(false) }, 10000) } diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 6aea9a5ee4d21..a44333ca1b3e7 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -32,11 +32,12 @@ let js_meta = { /** * Implementation of the javascript interpretor logic. Minimal skeleton for now. * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function js_run(toolname, obj) { - gToolsWorker.postMessage({ name: toolname, code: obj["code"]}) +function js_run(toolcallid, toolname, obj) { + gToolsWorker.postMessage({ id: toolcallid, name: toolname, code: obj["code"]}) } @@ -62,11 +63,12 @@ let calc_meta = { /** * Implementation of the simple calculator logic. Minimal skeleton for now. * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function calc_run(toolname, obj) { - gToolsWorker.postMessage({ name: toolname, code: `console.log(${obj["arithexpr"]})`}) +function calc_run(toolcallid, toolname, obj) { + gToolsWorker.postMessage({ id: toolcallid, name: toolname, code: `console.log(${obj["arithexpr"]})`}) } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 75fe56e4f4e5f..8c89e965258b4 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -32,11 +32,11 @@ export function meta() { /** * Setup the callback that will be called when ever message * is recieved from the Tools Web Worker. - * @param {(name: string, data: string) => void} cb + * @param {(id: string, name: string, data: string) => void} cb */ export function setup(cb) { gToolsWorker.onmessage = function (ev) { - cb(ev.data.name, ev.data.data) + cb(ev.data.id, ev.data.name, ev.data.data) } } @@ -45,14 +45,15 @@ export function setup(cb) { * Try call the specified tool/function call. * Returns undefined, if the call was placed successfully * Else some appropriate error message will be returned. + * @param {string} toolcallid * @param {string} toolname * @param {string} toolargs */ -export async function tool_call(toolname, toolargs) { +export async function tool_call(toolcallid, toolname, toolargs) { for (const fn in tc_switch) { if (fn == toolname) { try { - tc_switch[fn]["handler"](fn, JSON.parse(toolargs)) + tc_switch[fn]["handler"](toolcallid, fn, JSON.parse(toolargs)) return undefined } catch (/** @type {any} */error) { return `Tool/Function call raised an exception:${error.name}:${error.message}` diff --git a/tools/server/public_simplechat/toolsworker.mjs b/tools/server/public_simplechat/toolsworker.mjs index e370fd0a9df34..590c45234be7b 100644 --- a/tools/server/public_simplechat/toolsworker.mjs +++ b/tools/server/public_simplechat/toolsworker.mjs @@ -21,5 +21,5 @@ self.onmessage = function (ev) { console.log(`\n\nTool/Function call "${ev.data.name}" raised an exception:${error.name}:${error.message}\n\n`) } tconsole.console_revert() - self.postMessage({ name: ev.data.name, data: tconsole.gConsoleStr}) + self.postMessage({ id: ev.data.id, name: ev.data.name, data: tconsole.gConsoleStr}) } From 4efa2327efb79f1f5a6b93839a807838b4bd00a3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 03:28:48 +0530 Subject: [PATCH 055/187] SimpleChatTC:ChatMessageEx: Build tool role result fully Expand the xml format id, name and content in content field of tool result into apropriate fields in the tool result message sent to the genai/llm engine on the server. --- tools/server/public_simplechat/simplechat.js | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 281b2c15b958c..847115cb8e7d3 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -87,6 +87,33 @@ class ChatMessageEx { return ` ${toolCallId} ${toolName} ${toolResult} `; } + /** + * Extract the elements of the all in one tool call result string + * @param {string} allInOne + */ + static extractToolCallResultAllInOne(allInOne) { + const regex = /\s*(.*?)<\/id>\s*(.*?)<\/name>\s*([\s\S]*?)<\/content>\s*<\/tool_response>/si; + const caught = allInOne.match(regex) + let data = { tool_call_id: "Error", name: "Error", content: "Error" } + if (caught) { + data = { + tool_call_id: caught[1].trim(), + name: caught[2].trim(), + content: caught[3].trim() + } + } + return data + } + + /** + * Set extra members into the ns object + * @param {string | number} key + * @param {any} value + */ + ns_set_extra(key, value) { + // @ts-ignore + this.ns[key] = value + } /** * Update based on the drip by drip data got from network in streaming mode. @@ -305,6 +332,12 @@ class SimpleChat { // @ts-ignore delete(tmsg.ns.tool_calls) } + if (tmsg.ns.role == Roles.Tool) { + let res = ChatMessageEx.extractToolCallResultAllInOne(tmsg.ns.content) + tmsg.ns.content = res.content + tmsg.ns_set_extra("tool_call_id", res.tool_call_id) + tmsg.ns_set_extra("name", res.name) + } chat.push(tmsg.ns); } return chat From a644cb3e1c514575aa102fa75637be9d0cdc12f3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 03:35:55 +0530 Subject: [PATCH 056/187] SimpleChatTC:ChatMessageEx:While at it also ns_delete these common helpers avoid needing ignore tagging to ts-check, in places where valid constructs have been used which go beyond strict structured js handling that is tried to be achieved using it, but are still valid and legal. --- tools/server/public_simplechat/simplechat.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 847115cb8e7d3..bc4403516228d 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -115,6 +115,15 @@ class ChatMessageEx { this.ns[key] = value } + /** + * Remove specified key and its value from ns object + * @param {string | number} key + */ + ns_delete(key) { + // @ts-ignore + delete(this.ns[key]) + } + /** * Update based on the drip by drip data got from network in streaming mode. * Tries to support both Chat and Completion endpoints @@ -329,8 +338,7 @@ class SimpleChat { for (const msg of xchat) { let tmsg = ChatMessageEx.newFrom(msg); if (!tmsg.has_toolcall()) { - // @ts-ignore - delete(tmsg.ns.tool_calls) + tmsg.ns_delete("tool_calls") } if (tmsg.ns.role == Roles.Tool) { let res = ChatMessageEx.extractToolCallResultAllInOne(tmsg.ns.content) From 61c231487f590eccdc6ecbed86819d13224c62dc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 04:13:34 +0530 Subject: [PATCH 057/187] SimpleChatTC:Readme: Updated wrt new relativelyProper toolCallsHS Also update the sliding window context size to last 9 chat messages so that there is a sufficiently large context for multi turn tool calls based adjusting by ai and user, without needing to go full hog, which has the issue of overflowing the currently set context window wrt the loaded ai model. --- tools/server/public_simplechat/readme.md | 20 ++++++++++++++------ tools/server/public_simplechat/simplechat.js | 5 +++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index c8cb786c3c6c8..d50588cce5e78 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -136,8 +136,9 @@ Once inside called, then the ai response might include request for tool call. * the SimpleChat client will show details of the tool call (ie tool name and args passed) requested and allow the user to trigger it as is or after modifying things as needed. + NOTE: Tool sees the original tool call only, for now * inturn returned / generated result is placed into user query entry text area with approriate tags - ie generated result + ie generated result with meta data * if user is ok with the tool response, they can click submit to send the same to the GenAi/LLM. User can even modify the response generated by the tool, if required, before submitting. @@ -193,7 +194,7 @@ It is attached to the document object. Some of these can also be updated using t sent back to the ai model, under user control. as tool calling will involve a bit of back and forth between ai assistant and end user, it is - recommended to set iRecentUserMsgCnt to 5 or more, so that enough context is retained during + recommended to set iRecentUserMsgCnt to 10 or more, so that enough context is retained during chatting with ai models with tool support. apiEP - select between /completions and /chat/completions endpoint provided by the server/ai-model. @@ -239,7 +240,7 @@ It is attached to the document object. Some of these can also be updated using t be set if needed using the settings ui. iRecentUserMsgCnt - a simple minded SlidingWindow to limit context window load at Ai Model end. - This is set to 5 by default. So in addition to latest system message, last/latest iRecentUserMsgCnt + This is set to 10 by default. So in addition to latest system message, last/latest iRecentUserMsgCnt user messages after the latest system prompt and its responses from the ai model will be sent to the ai-model, when querying for a new response. Note that if enabled, only user messages after the latest system message/prompt will be considered. @@ -325,7 +326,7 @@ work. ALERT: The simple minded way in which this is implemented, it can be dangerous in the worst case, Always remember to verify all the tool calls requested and the responses generated manually to -ensure everything is fine, during interaction with ai modles with tools support. +ensure everything is fine, during interaction with ai models with tools support. #### Builtin Tools @@ -358,9 +359,11 @@ that should be sent back to the ai model, in your constructed code. Update the tc_switch to include a object entry for the tool, which inturn includes * the meta data as well as * a reference to the handler and also + the handler should take toolCallId, toolName and toolArgs and pass these along to + web worker as needed. * the result key (was used previously, may use in future, but for now left as is) -#### Mapping tool calls and responses to normal assistant - user chat flow +#### OLD: Mapping tool calls and responses to normal assistant - user chat flow Instead of maintaining tool_call request and resultant response in logically seperate parallel channel used for requesting tool_calls by the assistant and the resulstant tool role response, @@ -375,10 +378,14 @@ NOTE: This flow tested to be ok enough with Gemma-3N-E4B-it-Q8_0 LLM ai model fo given the way current ai models work, most of them should understand things as needed, but need to test this with other ai models later. -TODO: Need to think later, whether to continue this simple flow, or atleast use tool role wrt +TODO:OLD: Need to think later, whether to continue this simple flow, or atleast use tool role wrt the tool call responses or even go further and have the logically seperate tool_calls request structures also. +DONE: rather both tool_calls structure wrt assistant messages and tool role based tool call +result messages are generated as needed. + + #### ToDo WebFetch and Local web proxy/caching server @@ -386,6 +393,7 @@ WebFetch and Local web proxy/caching server Try and trap promises based flows to ensure all generated results or errors if any are caught before responding back to the ai model. +Trap error responses. ### Debuging the handshake diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index bc4403516228d..9363063123930 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -219,7 +219,7 @@ let gUsageMsg = `
      • submit tool response placed into user query textarea
      -
    • Default ContextWindow = [System, Last4 Query+Resp, Cur Query].
    • +
    • Default ContextWindow = [System, Last9 Query+Resp, Cur Query].
      • ChatHistInCtxt, MaxTokens, ModelCtxt window to expand
      @@ -983,7 +983,7 @@ class Me { this.bCompletionFreshChatAlways = true; this.bCompletionInsertStandardRolePrefix = false; this.bTrimGarbage = true; - this.iRecentUserMsgCnt = 5; + this.iRecentUserMsgCnt = 10; /** @type {Object} */ this.sRecentUserMsgCnt = { "Full": -1, @@ -991,6 +991,7 @@ class Me { "Last1": 2, "Last2": 3, "Last4": 5, + "Last9": 10, }; this.apiEP = ApiEP.Type.Chat; /** @type {Object} */ From 75a63e3ef1e7a6c0c8408d6f430cfbf6cc7cf5a7 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 05:31:08 +0530 Subject: [PATCH 058/187] SimpleChatTC:ChatMessageEx: Better tool result extractor --- tools/server/public_simplechat/simplechat.js | 25 +++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 9363063123930..c7259fb950b1c 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -91,7 +91,7 @@ class ChatMessageEx { * Extract the elements of the all in one tool call result string * @param {string} allInOne */ - static extractToolCallResultAllInOne(allInOne) { + static extractToolCallResultAllInOneSimpleMinded(allInOne) { const regex = /\s*(.*?)<\/id>\s*(.*?)<\/name>\s*([\s\S]*?)<\/content>\s*<\/tool_response>/si; const caught = allInOne.match(regex) let data = { tool_call_id: "Error", name: "Error", content: "Error" } @@ -105,6 +105,29 @@ class ChatMessageEx { return data } + /** + * Extract the elements of the all in one tool call result string + * This should potentially account for content tag having xml content within to an extent. + * @param {string} allInOne + */ + static extractToolCallResultAllInOne(allInOne) { + const dParser = new DOMParser(); + const got = dParser.parseFromString(allInOne, 'text/xml'); + const parseErrors = got.querySelector('parseerror') + if (parseErrors) { + console.debug("WARN:ChatMessageEx:ExtractToolCallResultAllInOne:", parseErrors.textContent.trim()) + } + const id = got.querySelector('id')?.textContent.trim(); + const name = got.querySelector('name')?.textContent.trim(); + const content = got.querySelector('content')?.textContent.trim(); + let data = { + tool_call_id: id? id : "Error", + name: name? name : "Error", + content: content? content : "Error" + } + return data + } + /** * Set extra members into the ns object * @param {string | number} key From 99b04ec4388577323fe45f27c3c86fcb05e551ec Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 18:20:20 +0530 Subject: [PATCH 059/187] SimpleChatTC:ChatMessageEx: 1st go at trying to track promises --- .../server/public_simplechat/toolsworker.mjs | 9 ++-- tools/server/public_simplechat/xpromise.mjs | 46 +++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 tools/server/public_simplechat/xpromise.mjs diff --git a/tools/server/public_simplechat/toolsworker.mjs b/tools/server/public_simplechat/toolsworker.mjs index 590c45234be7b..d1c7a2e42b4b0 100644 --- a/tools/server/public_simplechat/toolsworker.mjs +++ b/tools/server/public_simplechat/toolsworker.mjs @@ -5,18 +5,19 @@ // /** - * Expects to get a message with identifier name and code to run - * Posts message with identifier name and data captured from console.log outputs + * Expects to get a message with id, name and code to run + * Posts message with id, name and data captured from console.log outputs */ import * as tconsole from "./toolsconsole.mjs" +import * as xpromise from "./xpromise.mjs" -self.onmessage = function (ev) { +self.onmessage = async function (ev) { tconsole.console_redir() try { - eval(ev.data.code) + await xpromise.evalWithPromiseTracking(ev.data.code); } catch (/** @type {any} */error) { console.log(`\n\nTool/Function call "${ev.data.name}" raised an exception:${error.name}:${error.message}\n\n`) } diff --git a/tools/server/public_simplechat/xpromise.mjs b/tools/server/public_simplechat/xpromise.mjs new file mode 100644 index 0000000000000..de3612d7649c3 --- /dev/null +++ b/tools/server/public_simplechat/xpromise.mjs @@ -0,0 +1,46 @@ +//@ts-check +// Helpers for a tracked promise land +// by Humans for All +// + + +/** + * @typedef {(resolve: (value: any) => void, reject: (reason?: any) => void) => void} PromiseExecutor + */ + + +/** + * Eval which allows promises generated by the evald code to be tracked. + * @param {string} codeToEval + */ +export function evalWithPromiseTracking(codeToEval) { + const _Promise = globalThis.Promise; + /** @type {any[]} */ + const trackedPromises = []; + + const Promise = function ( /** @type {PromiseExecutor} */ executor) { + const promise = new _Promise(executor); + trackedPromises.push(promise); + + promise.then = function (...args) { + const newPromise = _Promise.prototype.then.apply(this, args); + trackedPromises.push(newPromise); + return newPromise; + }; + + promise.catch = function (...args) { + const newPromise = _Promise.prototype.catch.apply(this, args); + trackedPromises.push(newPromise); + return newPromise; + }; + + return promise; + }; + + Promise.prototype = _Promise.prototype; + Object.assign(Promise, _Promise); + + eval(codeToEval); + + return _Promise.all(trackedPromises); +} From 7dc999f619fc1cd803a7647cade4ba46d608cb8f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 21:48:18 +0530 Subject: [PATCH 060/187] SimpleChatTC:TrapPromise: log the trapping also possible refinement wrt trapping, if needed, added as comment all or allSettled to use or not is the question. whether to wait for a round trip through the related event loop or not is also a question. --- tools/server/public_simplechat/toolsworker.mjs | 2 ++ tools/server/public_simplechat/xpromise.mjs | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/toolsworker.mjs b/tools/server/public_simplechat/toolsworker.mjs index d1c7a2e42b4b0..b85b83b33b327 100644 --- a/tools/server/public_simplechat/toolsworker.mjs +++ b/tools/server/public_simplechat/toolsworker.mjs @@ -15,6 +15,7 @@ import * as xpromise from "./xpromise.mjs" self.onmessage = async function (ev) { + console.info("DBUG:WW:OnMessage started...") tconsole.console_redir() try { await xpromise.evalWithPromiseTracking(ev.data.code); @@ -23,4 +24,5 @@ self.onmessage = async function (ev) { } tconsole.console_revert() self.postMessage({ id: ev.data.id, name: ev.data.name, data: tconsole.gConsoleStr}) + console.info("DBUG:WW:OnMessage done") } diff --git a/tools/server/public_simplechat/xpromise.mjs b/tools/server/public_simplechat/xpromise.mjs index de3612d7649c3..7134293c0b4fa 100644 --- a/tools/server/public_simplechat/xpromise.mjs +++ b/tools/server/public_simplechat/xpromise.mjs @@ -13,22 +13,25 @@ * Eval which allows promises generated by the evald code to be tracked. * @param {string} codeToEval */ -export function evalWithPromiseTracking(codeToEval) { +export async function evalWithPromiseTracking(codeToEval) { const _Promise = globalThis.Promise; /** @type {any[]} */ const trackedPromises = []; const Promise = function ( /** @type {PromiseExecutor} */ executor) { + console.info("WW:PT:Promise") const promise = new _Promise(executor); trackedPromises.push(promise); promise.then = function (...args) { + console.info("WW:PT:Then") const newPromise = _Promise.prototype.then.apply(this, args); trackedPromises.push(newPromise); return newPromise; }; promise.catch = function (...args) { + console.info("WW:PT:Catch") const newPromise = _Promise.prototype.catch.apply(this, args); trackedPromises.push(newPromise); return newPromise; @@ -42,5 +45,7 @@ export function evalWithPromiseTracking(codeToEval) { eval(codeToEval); + //await Promise(resolve=>setTimeout(resolve, 0)); + //return _Promise.allSettled(trackedPromises); return _Promise.all(trackedPromises); } From 5d764933a99bd0f1144d7598edf1d9b2b0803124 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 22:10:27 +0530 Subject: [PATCH 061/187] SimpleChatTC:Promises: trap normal fetch (dont care await or not) --- tools/server/public_simplechat/xpromise.mjs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/server/public_simplechat/xpromise.mjs b/tools/server/public_simplechat/xpromise.mjs index 7134293c0b4fa..94d33ac3538f5 100644 --- a/tools/server/public_simplechat/xpromise.mjs +++ b/tools/server/public_simplechat/xpromise.mjs @@ -15,6 +15,8 @@ */ export async function evalWithPromiseTracking(codeToEval) { const _Promise = globalThis.Promise; + const _fetch = globalThis.fetch + /** @type {any[]} */ const trackedPromises = []; @@ -43,6 +45,13 @@ export async function evalWithPromiseTracking(codeToEval) { Promise.prototype = _Promise.prototype; Object.assign(Promise, _Promise); + const fetch = function(/** @type {any[]} */ ...args) { + console.info("WW:PT:Fetch") + const fpromise = _fetch(args); + trackedPromises.push(fpromise) + return fpromise; + } + eval(codeToEval); //await Promise(resolve=>setTimeout(resolve, 0)); From 788a9e489262e3823b5da0dc5b975a4c29307b8d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 22:52:07 +0530 Subject: [PATCH 062/187] SimpleChatTC: Allow await in generated code that will be evald --- tools/server/public_simplechat/xpromise.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/xpromise.mjs b/tools/server/public_simplechat/xpromise.mjs index 94d33ac3538f5..801ef7adc6c48 100644 --- a/tools/server/public_simplechat/xpromise.mjs +++ b/tools/server/public_simplechat/xpromise.mjs @@ -25,6 +25,7 @@ export async function evalWithPromiseTracking(codeToEval) { const promise = new _Promise(executor); trackedPromises.push(promise); + // @ts-ignore promise.then = function (...args) { console.info("WW:PT:Then") const newPromise = _Promise.prototype.then.apply(this, args); @@ -47,12 +48,15 @@ export async function evalWithPromiseTracking(codeToEval) { const fetch = function(/** @type {any[]} */ ...args) { console.info("WW:PT:Fetch") + // @ts-ignore const fpromise = _fetch(args); trackedPromises.push(fpromise) return fpromise; } - eval(codeToEval); + //let tf = new Function(codeToEval); + //await tf() + await eval(`(async () => { ${codeToEval} })()`); //await Promise(resolve=>setTimeout(resolve, 0)); //return _Promise.allSettled(trackedPromises); From c155bd313cd74a34a965d0aefc9c5a38b932e9f1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 23:13:02 +0530 Subject: [PATCH 063/187] SimpleChatTC:Ensure fetch's promise chain is also trapped Dont forget to map members of got entity from fetch to things from saved original promise, bcas remember what is got is a promise. also add some comments around certain decisions and needed exploration --- tools/server/public_simplechat/xpromise.mjs | 25 ++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/xpromise.mjs b/tools/server/public_simplechat/xpromise.mjs index 801ef7adc6c48..6f001ef9de6e7 100644 --- a/tools/server/public_simplechat/xpromise.mjs +++ b/tools/server/public_simplechat/xpromise.mjs @@ -1,5 +1,6 @@ //@ts-check // Helpers for a tracked promise land +// Traps regular promise as well as promise by fetch // by Humans for All // @@ -51,14 +52,36 @@ export async function evalWithPromiseTracking(codeToEval) { // @ts-ignore const fpromise = _fetch(args); trackedPromises.push(fpromise) + + // @ts-ignore + fpromise.then = function (...args) { + console.info("WW:PT:FThen") + const newPromise = _Promise.prototype.then.apply(this, args); + trackedPromises.push(newPromise); + return newPromise; + }; + + fpromise.catch = function (...args) { + console.info("WW:PT:FCatch") + const newPromise = _Promise.prototype.catch.apply(this, args); + trackedPromises.push(newPromise); + return newPromise; + }; + return fpromise; } + fetch.prototype = _fetch.prototype; + Object.assign(fetch, _fetch); + //let tf = new Function(codeToEval); //await tf() await eval(`(async () => { ${codeToEval} })()`); + // Should I allow things to go back to related event loop once //await Promise(resolve=>setTimeout(resolve, 0)); - //return _Promise.allSettled(trackedPromises); + + // Need and prefer promise failures to be trapped using reject/catch logic + // so using all instead of allSettled. return _Promise.all(trackedPromises); } From 1e1bb9a712405a13840aaa4af2fbec821a57e37a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 23:31:07 +0530 Subject: [PATCH 064/187] SimpleChatTC: update readme wrt promise related trapping --- tools/server/public_simplechat/readme.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index d50588cce5e78..0ae62a3a7b079 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -385,16 +385,22 @@ structures also. DONE: rather both tool_calls structure wrt assistant messages and tool role based tool call result messages are generated as needed. +#### Related stuff + +Promise as well as users of promise (for now fetch) have been trapped wrt their then and catch flow, +so that any scheduled asynchronous code or related async error handling using promise mechanism also +gets executed, before tool calling returns and thus data / error generated by those async code also +get incorporated in result sent to ai engine on the server side. #### ToDo WebFetch and Local web proxy/caching server -Try and trap promises based flows to ensure all generated results or errors if any are caught -before responding back to the ai model. +Is the promise land trap deep enough, need to think through and explore around this once later. Trap error responses. + ### Debuging the handshake When working with llama.cpp server based GenAi/LLM running locally From 9b1ed1fbf71153d62a4992b74a3cf1165c564d57 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 11:52:21 +0530 Subject: [PATCH 065/187] SimpleChatTC:WebFetchThroughProxy:Initial go creating request --- tools/server/public_simplechat/tooljs.mjs | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index a44333ca1b3e7..9591aeef4b8a3 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -72,6 +72,48 @@ function calc_run(toolcallid, toolname, obj) { } +let weburlfetch_meta = { + "type": "function", + "function": { + "name": "web_url_fetch", + "description": "Fetch the requested web url through a proxy server in few seconds", + "parameters": { + "type": "object", + "properties": { + "url":{ + "type":"string", + "description":"the url of the page / content to fetch from the internet" + } + }, + "required": ["url"] + } + } + } + + +/** + * Implementation of the web url logic. Dumb initial go. + * Expects a simple minded proxy server to be running locally + * * listening on port 3128 + * * expecting http requests + * * with a query token named path which gives the actual url to fetch + * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + */ +function weburlfetch_run(toolcallid, toolname, obj) { + if (gToolsWorker.onmessage != null) { + let newUrl = `http://127.0.0.1:3128/?path=${obj.url}` + fetch(newUrl).then(resp=>resp.text()).then(data => { + gToolsWorker.onmessage(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) + }).catch((err)=>{ + gToolsWorker.onmessage(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) + }) + } +} + + /** * @type {Object>} */ @@ -85,6 +127,11 @@ export let tc_switch = { "handler": calc_run, "meta": calc_meta, "result": "" + }, + "web_url_fetch": { + "handler": weburlfetch_run, + "meta": weburlfetch_meta, + "result": "" } } From 1b361ee9eb9426fb9b4fde3d5b37324bcc0dbc3b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 17:45:33 +0530 Subject: [PATCH 066/187] SimpleChatTC:SimpleProxy:Process args --port --- .../local.tools/simpleproxy.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tools/server/public_simplechat/local.tools/simpleproxy.py diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py new file mode 100644 index 0000000000000..85940e60d86d3 --- /dev/null +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -0,0 +1,37 @@ +# A simple proxy server +# by Humans for All +# +# Listens on the specified port (defaults to squids 3128) +# * if a url query is got (http://localhost:3128/?url=http://site.of.interest/path/of/interest) +# fetches the contents of the specified url and returns the same to the requester +# + +import sys + +gMe = {} + +def process_args(args: list[str]): + global gMe + gMe['INTERNAL.ProcessArgs.Malformed'] = [] + gMe['INTERNAL.ProcessArgs.Unknown'] = [] + iArg = 1 + while iArg < len(args): + cArg = args[iArg] + if (not cArg.startswith("--")): + gMe['INTERNAL.ProcessArgs.Malformed'].append(cArg) + print(f"WARN:ProcessArgs:{iArg}:IgnoringMalformedCommandOr???:{cArg}") + iArg += 1 + continue + match cArg: + case '--port': + iArg += 1 + gMe[cArg] = args[iArg] + iArg += 1 + case _: + gMe['INTERNAL.ProcessArgs.Unknown'].append(cArg) + print(f"WARN:ProcessArgs:{iArg}:IgnoringUnknownCommand:{cArg}") + iArg += 1 + + +if __name__ == "__main__": + process_args(sys.argv) From 9ec3d4514119e0d6da3cafbc1b01812ca97736eb Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:06:56 +0530 Subject: [PATCH 067/187] SimpleChatTC:SimpleProxy: Start server, Show requested path --- .../local.tools/simpleproxy.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 85940e60d86d3..c9d7264b7ae89 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -6,10 +6,20 @@ # fetches the contents of the specified url and returns the same to the requester # + import sys +import http.server + gMe = {} + +class ProxyHandler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + print(self.path) + + + def process_args(args: list[str]): global gMe gMe['INTERNAL.ProcessArgs.Malformed'] = [] @@ -33,5 +43,15 @@ def process_args(args: list[str]): iArg += 1 +def run(): + gMe['server'] = http.server.HTTPServer(('',gMe['--port']), ProxyHandler) + try: + gMe['server'].serve_forever() + except KeyboardInterrupt: + print("INFO:Run:Shuting down...") + gMe['server'].server_close() + sys.exit(0) + if __name__ == "__main__": process_args(sys.argv) + run() From 620da4533b174724d5936ff57f6640cc5a8682c8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:18:45 +0530 Subject: [PATCH 068/187] SimpleChatTC:SimpleProxy: Cleanup for basic run --- .../local.tools/simpleproxy.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index c9d7264b7ae89..ce8c0c5718d37 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -11,7 +11,10 @@ import http.server -gMe = {} +gMe = { + '--port': 3128, + 'server': None +} class ProxyHandler(http.server.BaseHTTPRequestHandler): @@ -35,7 +38,7 @@ def process_args(args: list[str]): match cArg: case '--port': iArg += 1 - gMe[cArg] = args[iArg] + gMe[cArg] = int(args[iArg]) iArg += 1 case _: gMe['INTERNAL.ProcessArgs.Unknown'].append(cArg) @@ -44,13 +47,20 @@ def process_args(args: list[str]): def run(): - gMe['server'] = http.server.HTTPServer(('',gMe['--port']), ProxyHandler) try: + gMe['server'] = http.server.HTTPServer(('',gMe['--port']), ProxyHandler) gMe['server'].serve_forever() except KeyboardInterrupt: print("INFO:Run:Shuting down...") - gMe['server'].server_close() + if (gMe['server']): + gMe['server'].server_close() sys.exit(0) + except Exception as exc: + print(f"ERRR:Run:Exception:{exc}") + if (gMe['server']): + gMe['server'].server_close() + sys.exit(1) + if __name__ == "__main__": process_args(sys.argv) From bdd054bbb7bda54fa4303b5fc68ec5668539c447 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:39:02 +0530 Subject: [PATCH 069/187] SimpleChatTC:SimpleProxy: Extract and check path, route to handlers --- .../local.tools/simpleproxy.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index ce8c0c5718d37..105ba794db0b6 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -9,6 +9,7 @@ import sys import http.server +import urllib.parse gMe = { @@ -19,8 +20,29 @@ class ProxyHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): - print(self.path) + print(f"DBUG:ProxyHandler:{self.path}") + pr = urllib.parse.urlparse(self.path) + print(f"DBUG:ProxyHandler:{pr}") + match pr.path: + case '/urlraw': + handle_urlraw(self, pr) + case '/urltext': + handle_urltext(self, pr) + case _: + print(f"WARN:ProxyHandler:UnknownPath{pr.path}") + self.send_response(400) + self.send_header('Content-Type', 'plain/text') + self.end_headers() + self.wfile.write(f"WARN:UnknownPath:{pr.path}") + + +def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): + print(f"DBUG:HandleUrlRaw:{pr}") + queryParams = urllib.parse.parse_qs(pr.query) + +def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): + print(f"DBUG:HandleUrlText:{pr}") def process_args(args: list[str]): From 5ac60a87d4c75ab5964db0e3533c920144abcdeb Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:54:20 +0530 Subject: [PATCH 070/187] SimpleChatTC:SimpleProxy:implement handle_urlraw A basic go at it --- .../local.tools/simpleproxy.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 105ba794db0b6..5b41b37f0c9d7 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -19,6 +19,7 @@ class ProxyHandler(http.server.BaseHTTPRequestHandler): + def do_GET(self): print(f"DBUG:ProxyHandler:{self.path}") pr = urllib.parse.urlparse(self.path) @@ -30,19 +31,36 @@ def do_GET(self): handle_urltext(self, pr) case _: print(f"WARN:ProxyHandler:UnknownPath{pr.path}") - self.send_response(400) - self.send_header('Content-Type', 'plain/text') - self.end_headers() - self.wfile.write(f"WARN:UnknownPath:{pr.path}") + self.send_error(400, f"WARN:UnknownPath:{pr.path}") def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): print(f"DBUG:HandleUrlRaw:{pr}") queryParams = urllib.parse.parse_qs(pr.query) + url = queryParams['url'] + if (not url) or (len(url) == 0): + ph.send_error(400, "WARN:UrlRaw:MissingUrl") + return + try: + # Get requested url + with urllib.request.urlopen(url, timeout=10) as response: + contentData = response.read() + statusCode = response.status or 200 + contentType = response.getheader('Content-Type') or 'text/html' + # Send back to client + ph.send_response(statusCode) + ph.send_header('Content-Type', contentType) + # Add CORS for browser fetch, just inc ase + ph.send_header('Access-Control-Allow-Origin', '*') + ph.end_headers() + ph.wfile.write(contentData) + except Exception as exc: + ph.send_error(502, f"WARN:UrlFetchFailed:{exc}") def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): print(f"DBUG:HandleUrlText:{pr}") + ph.send_error(400, "WARN:UrlText:Not implemented") def process_args(args: list[str]): From ac448c34225efcb824b4c385bad7e57f7211f649 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:58:06 +0530 Subject: [PATCH 071/187] SimpleChatTC:SimpleProxy:UrlRaw: Fixup basic oversight wrt 1st go --- tools/server/public_simplechat/local.tools/simpleproxy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 5b41b37f0c9d7..d1b229f5cf24a 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -10,6 +10,7 @@ import sys import http.server import urllib.parse +import urllib.request gMe = { @@ -38,6 +39,8 @@ def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): print(f"DBUG:HandleUrlRaw:{pr}") queryParams = urllib.parse.parse_qs(pr.query) url = queryParams['url'] + print(f"DBUG:HandleUrlRaw:Url:{url}") + url = url[0] if (not url) or (len(url) == 0): ph.send_error(400, "WARN:UrlRaw:MissingUrl") return From 2ca2b980540f35ba818716f87a50581273ed7296 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 19:02:55 +0530 Subject: [PATCH 072/187] SimpleChatTC:WebFetch: Update to use internal SimpleProxy.py --- tools/server/public_simplechat/tooljs.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 9591aeef4b8a3..9fb306ccb65e8 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -104,7 +104,7 @@ let weburlfetch_meta = { */ function weburlfetch_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { - let newUrl = `http://127.0.0.1:3128/?path=${obj.url}` + let newUrl = `http://127.0.0.1:3128/urlraw?url=${obj.url}` fetch(newUrl).then(resp=>resp.text()).then(data => { gToolsWorker.onmessage(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) }).catch((err)=>{ From 04a5f95ed3a9a5d310c0d35fde6227dc80c66c63 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 22:47:21 +0530 Subject: [PATCH 073/187] SimpleChatTC:SimpleProxy: Cleanup few messages --- tools/server/public_simplechat/local.tools/simpleproxy.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index d1b229f5cf24a..f1322913d745f 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -53,7 +53,7 @@ def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): # Send back to client ph.send_response(statusCode) ph.send_header('Content-Type', contentType) - # Add CORS for browser fetch, just inc ase + # Add CORS for browser fetch, just in case ph.send_header('Access-Control-Allow-Origin', '*') ph.end_headers() ph.wfile.write(contentData) @@ -91,7 +91,9 @@ def process_args(args: list[str]): def run(): try: - gMe['server'] = http.server.HTTPServer(('',gMe['--port']), ProxyHandler) + gMe['serverAddr'] = ('', gMe['--port']) + gMe['server'] = http.server.HTTPServer(gMe['serverAddr'], ProxyHandler) + print(f"INFO:Run:Starting on {gMe['serverAddr']}") gMe['server'].serve_forever() except KeyboardInterrupt: print("INFO:Run:Shuting down...") @@ -99,7 +101,7 @@ def run(): gMe['server'].server_close() sys.exit(0) except Exception as exc: - print(f"ERRR:Run:Exception:{exc}") + print(f"ERRR:Run:Exiting:Exception:{exc}") if (gMe['server']): gMe['server'].server_close() sys.exit(1) From 295893cf8dad553e8bfe190016a8e96626aef9c9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 00:11:01 +0530 Subject: [PATCH 074/187] SimpleChatTC:SimpleProxy:Common UrlReq helper for UrlRaw & UrlText Declare the result of UrlReq as a DataClass, so that one doesnt goof up wrt updating and accessing members. Duplicate UrlRaw into UrlText, need to add Text extracting from html next for UrlText --- .../local.tools/simpleproxy.py | 55 +++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index f1322913d745f..d1f4cb3ec594a 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -11,6 +11,7 @@ import http.server import urllib.parse import urllib.request +from dataclasses import dataclass gMe = { @@ -35,35 +36,69 @@ def do_GET(self): self.send_error(400, f"WARN:UnknownPath:{pr.path}") -def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): - print(f"DBUG:HandleUrlRaw:{pr}") +@dataclass(frozen=True) +class UrlReqResp: + callOk: bool + httpStatus: int + httpStatusMsg: str = "" + contentType: str = "" + contentData: urllib.request._UrlopenRet = "" + + +def handle_urlreq(pr: urllib.parse.ParseResult, tag: str): + print(f"DBUG:{tag}:{pr}") queryParams = urllib.parse.parse_qs(pr.query) url = queryParams['url'] - print(f"DBUG:HandleUrlRaw:Url:{url}") + print(f"DBUG:{tag}:Url:{url}") url = url[0] if (not url) or (len(url) == 0): - ph.send_error(400, "WARN:UrlRaw:MissingUrl") - return + return UrlReqResp(False, 400, f"WARN:{tag}:MissingUrl") try: # Get requested url with urllib.request.urlopen(url, timeout=10) as response: contentData = response.read() statusCode = response.status or 200 contentType = response.getheader('Content-Type') or 'text/html' + return UrlReqResp(True, statusCode, "", contentType, contentData) + except Exception as exc: + return UrlReqResp(False, 502, f"WARN:UrlFetchFailed:{exc}") + + +def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): + try: + # Get requested url + got = handle_urlreq(pr, "HandleUrlRaw") + if not got.callOk: + ph.send_error(got.httpStatus, got.httpStatusMsg) + return # Send back to client - ph.send_response(statusCode) - ph.send_header('Content-Type', contentType) + ph.send_response(got.httpStatus) + ph.send_header('Content-Type', got.contentType) # Add CORS for browser fetch, just in case ph.send_header('Access-Control-Allow-Origin', '*') ph.end_headers() - ph.wfile.write(contentData) + ph.wfile.write(got.contentData) except Exception as exc: ph.send_error(502, f"WARN:UrlFetchFailed:{exc}") def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): - print(f"DBUG:HandleUrlText:{pr}") - ph.send_error(400, "WARN:UrlText:Not implemented") + try: + # Get requested url + got = handle_urlreq(pr, "HandleUrlText") + if not got.callOk: + ph.send_error(got.httpStatus, got.httpStatusMsg) + return + # Extract Text + # Send back to client + ph.send_response(got.httpStatus) + ph.send_header('Content-Type', got.contentType) + # Add CORS for browser fetch, just in case + ph.send_header('Access-Control-Allow-Origin', '*') + ph.end_headers() + ph.wfile.write(got.contentData) + except Exception as exc: + ph.send_error(502, f"WARN:UrlFetchFailed:{exc}") def process_args(args: list[str]): From 469453759cc1f07c5731f467b3216afdcc4b7153 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 00:42:04 +0530 Subject: [PATCH 075/187] SimpleChatTC:SimpleProxy: ElementTree, No _UrlopenRet As _UrlopenRet not exposed for use outside urllib, so decode and encode the data. Add skeleton to try get the html/xml tree top elements --- .../public_simplechat/local.tools/simpleproxy.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index d1f4cb3ec594a..0014b0219b995 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -12,6 +12,7 @@ import urllib.parse import urllib.request from dataclasses import dataclass +import xml.etree.ElementTree as xmlET gMe = { @@ -42,7 +43,7 @@ class UrlReqResp: httpStatus: int httpStatusMsg: str = "" contentType: str = "" - contentData: urllib.request._UrlopenRet = "" + contentData: str = "" def handle_urlreq(pr: urllib.parse.ParseResult, tag: str): @@ -56,7 +57,7 @@ def handle_urlreq(pr: urllib.parse.ParseResult, tag: str): try: # Get requested url with urllib.request.urlopen(url, timeout=10) as response: - contentData = response.read() + contentData = response.read().decode('utf-8') statusCode = response.status or 200 contentType = response.getheader('Content-Type') or 'text/html' return UrlReqResp(True, statusCode, "", contentType, contentData) @@ -77,7 +78,7 @@ def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): # Add CORS for browser fetch, just in case ph.send_header('Access-Control-Allow-Origin', '*') ph.end_headers() - ph.wfile.write(got.contentData) + ph.wfile.write(got.contentData.encode('utf-8')) except Exception as exc: ph.send_error(502, f"WARN:UrlFetchFailed:{exc}") @@ -90,13 +91,16 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.send_error(got.httpStatus, got.httpStatusMsg) return # Extract Text + html = xmlET.fromstring(got.contentData) + for el in html.iter(): + print(el) # Send back to client ph.send_response(got.httpStatus) ph.send_header('Content-Type', got.contentType) # Add CORS for browser fetch, just in case ph.send_header('Access-Control-Allow-Origin', '*') ph.end_headers() - ph.wfile.write(got.contentData) + ph.wfile.write(got.contentData.encode('utf-8')) except Exception as exc: ph.send_error(502, f"WARN:UrlFetchFailed:{exc}") From 696d5604ea52e77030ba9e61411528a70ca72439 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 01:30:46 +0530 Subject: [PATCH 076/187] SimpleChatTC:SimpleProxy: Switch to html.parser As html can be malformed, xml ElementTree XMLParser cant handle the same properly, so switch to the HtmlParser helper class that is provided by python and try extend it. Currently a minimal skeleton to just start it out, which captures only the body contents. --- .../local.tools/simpleproxy.py | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 0014b0219b995..4ac26b6b22d89 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -12,7 +12,7 @@ import urllib.parse import urllib.request from dataclasses import dataclass -import xml.etree.ElementTree as xmlET +import html.parser gMe = { @@ -83,6 +83,26 @@ def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.send_error(502, f"WARN:UrlFetchFailed:{exc}") +class TextHtmlParser(html.parser.HTMLParser): + + def __init__(self): + super().__init__() + self.bBody = False + self.text = "" + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]): + if tag == 'body': + self.bBody = True + + def handle_endtag(self, tag: str): + if tag == 'body': + self.bBody = False + + def handle_data(self, data: str): + if self.bBody: + self.text += f"{data}\n" + + def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): try: # Get requested url @@ -91,16 +111,15 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.send_error(got.httpStatus, got.httpStatusMsg) return # Extract Text - html = xmlET.fromstring(got.contentData) - for el in html.iter(): - print(el) + textHtml = TextHtmlParser() + textHtml.feed(got.contentData) # Send back to client ph.send_response(got.httpStatus) ph.send_header('Content-Type', got.contentType) # Add CORS for browser fetch, just in case ph.send_header('Access-Control-Allow-Origin', '*') ph.end_headers() - ph.wfile.write(got.contentData.encode('utf-8')) + ph.wfile.write(textHtml.text.encode('utf-8')) except Exception as exc: ph.send_error(502, f"WARN:UrlFetchFailed:{exc}") From 2e6a386eb6c87d8ecfb7e9641ff47f30dddc7b8b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 01:39:35 +0530 Subject: [PATCH 077/187] SimpleChatTC:SimpleProxy:UrlText: Capture body except for scripts --- .../server/public_simplechat/local.tools/simpleproxy.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 4ac26b6b22d89..242e644648d98 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -88,18 +88,25 @@ class TextHtmlParser(html.parser.HTMLParser): def __init__(self): super().__init__() self.bBody = False + self.bCapture = False self.text = "" def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]): if tag == 'body': self.bBody = True + self.bCapture = True + if tag == 'script': + self.bCapture = False def handle_endtag(self, tag: str): if tag == 'body': self.bBody = False + if tag == 'script': + if self.bBody: + self.bCapture = True def handle_data(self, data: str): - if self.bBody: + if self.bCapture: self.text += f"{data}\n" From 0a3b4c236d5198892ae452951c721d3e6b919a99 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 01:50:26 +0530 Subject: [PATCH 078/187] SimpleChatTC:SimpleProxy:UrlText: Avoid style blocks also --- tools/server/public_simplechat/local.tools/simpleproxy.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 242e644648d98..3076c0d4aeb80 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -97,11 +97,13 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]): self.bCapture = True if tag == 'script': self.bCapture = False + if tag == 'style': + self.bCapture = False def handle_endtag(self, tag: str): if tag == 'body': self.bBody = False - if tag == 'script': + if tag == 'script' or tag == 'style': if self.bBody: self.bCapture = True From 61b937104c108e96c882db8eba7994ed921d0a46 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 02:08:38 +0530 Subject: [PATCH 079/187] SimpleChatTC:WebUrl FetchStrip through simple proxy --- tools/server/public_simplechat/tooljs.mjs | 67 +++++++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 9fb306ccb65e8..e9dc3c6f90a8c 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -72,6 +72,16 @@ function calc_run(toolcallid, toolname, obj) { } +/** + * Send a message to Tools WebWorker's monitor in main thread directly + * @param {MessageEvent} mev + */ +function message_toolsworker(mev) { + // @ts-ignore + gToolsWorker.onmessage(mev) +} + + let weburlfetch_meta = { "type": "function", "function": { @@ -96,8 +106,8 @@ let weburlfetch_meta = { * Expects a simple minded proxy server to be running locally * * listening on port 3128 * * expecting http requests - * * with a query token named path which gives the actual url to fetch - * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * * with a query token named url which gives the actual url to fetch + * ALERT: Accesses a external web proxy/caching server be aware and careful * @param {string} toolcallid * @param {string} toolname * @param {any} obj @@ -106,9 +116,53 @@ function weburlfetch_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { let newUrl = `http://127.0.0.1:3128/urlraw?url=${obj.url}` fetch(newUrl).then(resp=>resp.text()).then(data => { - gToolsWorker.onmessage(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) + }).catch((err)=>{ + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) + }) + } +} + + +let weburlfetchstrip_meta = { + "type": "function", + "function": { + "name": "web_url_fetch_strip", + "description": "Fetch the requested web url through a proxy server and strip away head, script, styles blocks before sending remaining body in few seconds", + "parameters": { + "type": "object", + "properties": { + "url":{ + "type":"string", + "description":"the url of the page that will be fetched from the internet and inturn contents stripped to some extent" + } + }, + "required": ["url"] + } + } + } + + +/** + * Implementation of the web url logic. Dumb initial go. + * Expects a simple minded proxy server to be running locally + * * listening on port 3128 + * * expecting http requests + * * with a query token named url which gives the actual url to fetch + * * strips out head as well as any script and style blocks in body + * before returning remaining body contents. + * ALERT: Accesses a external web proxy/caching server be aware and careful + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + */ +function weburlfetchstrip_run(toolcallid, toolname, obj) { + if (gToolsWorker.onmessage != null) { + let newUrl = `http://127.0.0.1:3128/urltext?url=${obj.url}` + fetch(newUrl).then(resp=>resp.text()).then(data => { + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) }).catch((err)=>{ - gToolsWorker.onmessage(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) }) } } @@ -132,6 +186,11 @@ export let tc_switch = { "handler": weburlfetch_run, "meta": weburlfetch_meta, "result": "" + }, + "web_url_fetch_strip": { + "handler": weburlfetchstrip_run, + "meta": weburlfetchstrip_meta, + "result": "" } } From 378bde5fc82df9b05f32d4c18ee00c25e05d9ec9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 02:52:37 +0530 Subject: [PATCH 080/187] SimpleChatTC:SimpleProxy:UrlText: Try strip empty lines some what --- .../public_simplechat/local.tools/simpleproxy.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 3076c0d4aeb80..ad21cb3dc4df1 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -111,6 +111,16 @@ def handle_data(self, data: str): if self.bCapture: self.text += f"{data}\n" + def get_stripped_text(self): + oldLen = -99 + newLen = len(self.text) + aStripped = self.text; + while oldLen != newLen: + oldLen = newLen + aStripped = aStripped.replace("\n\n\n","\n") + newLen = len(aStripped) + return aStripped + def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): try: @@ -128,7 +138,7 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): # Add CORS for browser fetch, just in case ph.send_header('Access-Control-Allow-Origin', '*') ph.end_headers() - ph.wfile.write(textHtml.text.encode('utf-8')) + ph.wfile.write(textHtml.get_stripped_text().encode('utf-8')) except Exception as exc: ph.send_error(502, f"WARN:UrlFetchFailed:{exc}") From e7277cbb6c1c05b23a7c8be18d00d3b3b2624b44 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 03:02:53 +0530 Subject: [PATCH 081/187] SimpleChatTC:SimpleProxy:UrlText: Slightly better trimming First identify lines which have only whitespace and replace them with lines with only newline char in them. Next strip out adjacent lines, if they have only newlines --- .../local.tools/simpleproxy.py | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index ad21cb3dc4df1..ad85b1b809425 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -90,6 +90,7 @@ def __init__(self): self.bBody = False self.bCapture = False self.text = "" + self.textStripped = "" def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]): if tag == 'body': @@ -111,15 +112,33 @@ def handle_data(self, data: str): if self.bCapture: self.text += f"{data}\n" - def get_stripped_text(self): + def syncup(self): + self.textStripped = self.text + + def strip_adjacent_newlines(self): oldLen = -99 - newLen = len(self.text) - aStripped = self.text; + newLen = len(self.textStripped) + aStripped = self.textStripped; while oldLen != newLen: oldLen = newLen aStripped = aStripped.replace("\n\n\n","\n") newLen = len(aStripped) - return aStripped + self.textStripped = aStripped + + def strip_whitespace_lines(self): + aLines = self.textStripped.splitlines() + self.textStripped = "" + for line in aLines: + if (len(line.strip())==0): + self.textStripped += "\n" + continue + self.textStripped += f"{line}\n" + + def get_stripped_text(self): + self.syncup() + self.strip_whitespace_lines() + self.strip_adjacent_newlines() + return self.textStripped def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): From 9db2f278c0844aedfcb9ed126d97465017ef458e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 03:56:35 +0530 Subject: [PATCH 082/187] SimpleChatTC:WebUrlText:Update name and desc to see if prefered --- tools/server/public_simplechat/tooljs.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index e9dc3c6f90a8c..cb75dc019cf29 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -127,14 +127,14 @@ function weburlfetch_run(toolcallid, toolname, obj) { let weburlfetchstrip_meta = { "type": "function", "function": { - "name": "web_url_fetch_strip", - "description": "Fetch the requested web url through a proxy server and strip away head, script, styles blocks before sending remaining body in few seconds", + "name": "web_url_fetch_strip_htmltags_and_some_useless", + "description": "Fetch the requested web url through a proxy server and strip away html tags as well as head, script, styles blocks before returning the remaining body in few seconds", "parameters": { "type": "object", "properties": { "url":{ "type":"string", - "description":"the url of the page that will be fetched from the internet and inturn contents stripped to some extent" + "description":"the url of the page that will be fetched from the internet and inturn unwanted stuff stripped from its contents to some extent" } }, "required": ["url"] @@ -187,7 +187,7 @@ export let tc_switch = { "meta": weburlfetch_meta, "result": "" }, - "web_url_fetch_strip": { + "web_url_fetch_strip_htmltags_and_some_useless": { "handler": weburlfetchstrip_run, "meta": weburlfetchstrip_meta, "result": "" From c5a602f25b1fa1e9ce217606e3c7fab30e48486b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 04:13:46 +0530 Subject: [PATCH 083/187] SimpleChatTC:SimpleProxy:Options just in case --- .../public_simplechat/local.tools/simpleproxy.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index ad85b1b809425..4e69c1cf61550 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -23,19 +23,29 @@ class ProxyHandler(http.server.BaseHTTPRequestHandler): + # Handle GET requests def do_GET(self): - print(f"DBUG:ProxyHandler:{self.path}") + print(f"DBUG:ProxyHandler:GET:{self.path}") pr = urllib.parse.urlparse(self.path) - print(f"DBUG:ProxyHandler:{pr}") + print(f"DBUG:ProxyHandler:GET:{pr}") match pr.path: case '/urlraw': handle_urlraw(self, pr) case '/urltext': handle_urltext(self, pr) case _: - print(f"WARN:ProxyHandler:UnknownPath{pr.path}") + print(f"WARN:ProxyHandler:GET:UnknownPath{pr.path}") self.send_error(400, f"WARN:UnknownPath:{pr.path}") + # Handle OPTIONS for CORS preflights (just in case from browser) + def do_OPTIONS(self): + print(f"DBUG:ProxyHandler:OPTIONS:{self.path}") + self.send_response(200) + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS') + self.send_header('Access-Control-Allow-Headers', '*') + self.end_headers() + @dataclass(frozen=True) class UrlReqResp: From 6efbd126060816ab7d7f820e833b55ad291f8ce2 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 05:22:13 +0530 Subject: [PATCH 084/187] SimpleChatTC:WebFetch:UrlEnc url2fetch b4Passing toProxy asQuery Ensures that if the url being requested as any query strings in them then things dont get messed up, when the url to get inc its query is extracted from the proxy request's query string --- tools/server/public_simplechat/tooljs.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index cb75dc019cf29..5b7979f0ab5a0 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -114,7 +114,7 @@ let weburlfetch_meta = { */ function weburlfetch_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { - let newUrl = `http://127.0.0.1:3128/urlraw?url=${obj.url}` + let newUrl = `http://127.0.0.1:3128/urlraw?url=${encodeURIComponent(obj.url)}` fetch(newUrl).then(resp=>resp.text()).then(data => { message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) }).catch((err)=>{ @@ -158,7 +158,7 @@ let weburlfetchstrip_meta = { */ function weburlfetchstrip_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { - let newUrl = `http://127.0.0.1:3128/urltext?url=${obj.url}` + let newUrl = `http://127.0.0.1:3128/urltext?url=${encodeURIComponent(obj.url)}` fetch(newUrl).then(resp=>resp.text()).then(data => { message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) }).catch((err)=>{ From 8f0700a462b83699b01ee5dc7218d315e7eed193 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 05:36:43 +0530 Subject: [PATCH 085/187] SimpleChatTC: Update readme wrt web fetch and related simple proxy --- tools/server/public_simplechat/readme.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 0ae62a3a7b079..d90261ed380c9 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -334,16 +334,21 @@ The following tools/functions are currently provided by default * simple_calculator - which can solve simple arithmatic expressions * run_javascript_function_code - which can be used to run some javascript code in the browser context. +* web_url_fetch - fetch requested url through a proxy server +* web_url_fetch_strip_htmltags_and_some_useless - fetch requested url through a proxy server + and also try strip the html respose of html tags and also head, script & style blocks. Currently the generated code / expression is run through a simple minded eval inside a web worker mechanism. Use of WebWorker helps avoid exposing browser global scope to the generated code directly. However any shared web worker scope isnt isolated. Either way always remember to cross check the tool requests and generated responses when using tool calling. -May add -* web_fetch along with a corresponding simple local web proxy/caching server logic that can bypass - the CORS restrictions applied if trying to directly fetch from the browser js runtime environment. - Inturn maybe with a white list of allowed sites to access or so. +web_url_fetch and family works along with a corresponding simple local web proxy/caching server logic +that can bypass the CORS restrictions applied if trying to directly fetch from the browser js runtime +environment. Depending on the path specified on the proxy server, if urltext, it additionally tries +to convert html content into equivalent text to some extent. May add support for white list of allowed +sites to access or so. +* tools/server/public_simplechat/local.tools/simpleproxy.py #### Extending with new tools @@ -394,8 +399,6 @@ get incorporated in result sent to ai engine on the server side. #### ToDo -WebFetch and Local web proxy/caching server - Is the promise land trap deep enough, need to think through and explore around this once later. Trap error responses. From 514cec05bda92cadc8403814693ca6b5f2f7b6d5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 18:01:25 +0530 Subject: [PATCH 086/187] SimpleChatTC:SimpleProxy:HtmlParser more generic and flexible also now track header, footer and nav so that they arent captured --- .../local.tools/simpleproxy.py | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 4e69c1cf61550..78cf6d6767ee2 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -97,29 +97,34 @@ class TextHtmlParser(html.parser.HTMLParser): def __init__(self): super().__init__() - self.bBody = False + self.inside = { + 'body': False, + 'script': False, + 'style': False, + 'header': False, + 'footer': False, + 'nav': False + } + self.monitored = [ 'body', 'script', 'style', 'header', 'footer', 'nav' ] self.bCapture = False self.text = "" self.textStripped = "" + def do_capture(self): + if self.inside['body'] and not (self.inside['script'] or self.inside['style'] or self.inside['header'] or self.inside['footer'] or self.inside['nav']): + return True + return False + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]): - if tag == 'body': - self.bBody = True - self.bCapture = True - if tag == 'script': - self.bCapture = False - if tag == 'style': - self.bCapture = False + if tag in self.monitored: + self.inside[tag] = True def handle_endtag(self, tag: str): - if tag == 'body': - self.bBody = False - if tag == 'script' or tag == 'style': - if self.bBody: - self.bCapture = True + if tag in self.monitored: + self.inside[tag] = False def handle_data(self, data: str): - if self.bCapture: + if self.do_capture(): self.text += f"{data}\n" def syncup(self): From e12431786b194669f98ea694dbdceafc9bbb6748 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 18:25:50 +0530 Subject: [PATCH 087/187] SimpleChatTC:WebFetch: Cleanup the names and descriptions a bit --- tools/server/public_simplechat/readme.md | 11 +++--- tools/server/public_simplechat/tooljs.mjs | 48 ++++++++++++----------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index d90261ed380c9..6c1d55132b624 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -343,11 +343,12 @@ mechanism. Use of WebWorker helps avoid exposing browser global scope to the gen However any shared web worker scope isnt isolated. Either way always remember to cross check the tool requests and generated responses when using tool calling. -web_url_fetch and family works along with a corresponding simple local web proxy/caching server logic -that can bypass the CORS restrictions applied if trying to directly fetch from the browser js runtime -environment. Depending on the path specified on the proxy server, if urltext, it additionally tries -to convert html content into equivalent text to some extent. May add support for white list of allowed -sites to access or so. +fetch_web_url_raw/text and family works along with a corresponding simple local web proxy/caching +server logic, this helps bypass the CORS restrictions applied if trying to directly fetch from the +browser js runtime environment. Depending on the path specified wrt the proxy server, if urltext +(and not urlraw), it additionally tries to convert html content into equivalent text to some extent +in a simple minded manner by dropping head block as well as all scripts/styles/footers/headers/nav. +May add support for white list of allowed sites to access or so. The simple proxy can be found at * tools/server/public_simplechat/local.tools/simpleproxy.py diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 5b7979f0ab5a0..da09d9013a06c 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -82,17 +82,17 @@ function message_toolsworker(mev) { } -let weburlfetch_meta = { +let fetchweburlraw_meta = { "type": "function", "function": { - "name": "web_url_fetch", - "description": "Fetch the requested web url through a proxy server in few seconds", + "name": "fetch_web_url_raw", + "description": "Fetch the requested web url through a proxy server and return the got content as is, in few seconds", "parameters": { "type": "object", "properties": { "url":{ "type":"string", - "description":"the url of the page / content to fetch from the internet" + "description":"url of the web page to fetch from the internet" } }, "required": ["url"] @@ -102,17 +102,18 @@ let weburlfetch_meta = { /** - * Implementation of the web url logic. Dumb initial go. + * Implementation of the fetch web url raw logic. Dumb initial go. * Expects a simple minded proxy server to be running locally * * listening on port 3128 * * expecting http requests - * * with a query token named url which gives the actual url to fetch - * ALERT: Accesses a external web proxy/caching server be aware and careful + * * with a query token named url wrt the path urlraw + * which gives the actual url to fetch + * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function weburlfetch_run(toolcallid, toolname, obj) { +function fetchweburlraw_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { let newUrl = `http://127.0.0.1:3128/urlraw?url=${encodeURIComponent(obj.url)}` fetch(newUrl).then(resp=>resp.text()).then(data => { @@ -124,17 +125,17 @@ function weburlfetch_run(toolcallid, toolname, obj) { } -let weburlfetchstrip_meta = { +let fetchweburltext_meta = { "type": "function", "function": { - "name": "web_url_fetch_strip_htmltags_and_some_useless", - "description": "Fetch the requested web url through a proxy server and strip away html tags as well as head, script, styles blocks before returning the remaining body in few seconds", + "name": "fetch_web_url_text", + "description": "Fetch the requested web url through a proxy server and return its text content after stripping away the html tags as well as head, script, style, header, footer, nav blocks, in few seconds", "parameters": { "type": "object", "properties": { "url":{ "type":"string", - "description":"the url of the page that will be fetched from the internet and inturn unwanted stuff stripped from its contents to some extent" + "description":"url of the page that will be fetched from the internet and inturn unwanted stuff stripped from its contents to some extent" } }, "required": ["url"] @@ -144,19 +145,20 @@ let weburlfetchstrip_meta = { /** - * Implementation of the web url logic. Dumb initial go. + * Implementation of the fetch web url text logic. Dumb initial go. * Expects a simple minded proxy server to be running locally * * listening on port 3128 * * expecting http requests - * * with a query token named url which gives the actual url to fetch - * * strips out head as well as any script and style blocks in body + * * with a query token named url wrt urltext path, + * which gives the actual url to fetch + * * strips out head as well as any script, style, header, footer, nav and so blocks in body * before returning remaining body contents. - * ALERT: Accesses a external web proxy/caching server be aware and careful + * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function weburlfetchstrip_run(toolcallid, toolname, obj) { +function fetchweburltext_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { let newUrl = `http://127.0.0.1:3128/urltext?url=${encodeURIComponent(obj.url)}` fetch(newUrl).then(resp=>resp.text()).then(data => { @@ -182,14 +184,14 @@ export let tc_switch = { "meta": calc_meta, "result": "" }, - "web_url_fetch": { - "handler": weburlfetch_run, - "meta": weburlfetch_meta, + "fetch_web_url_raw": { + "handler": fetchweburlraw_run, + "meta": fetchweburlraw_meta, "result": "" }, - "web_url_fetch_strip_htmltags_and_some_useless": { - "handler": weburlfetchstrip_run, - "meta": weburlfetchstrip_meta, + "fetch_web_url_text": { + "handler": fetchweburltext_run, + "meta": fetchweburltext_meta, "result": "" } } From 910995ef1b3640d439a331ce1ec0633b949d7464 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 22:16:30 +0530 Subject: [PATCH 088/187] SimpleChatTC:SimpleProxy: Ensure CORS related headers sent always Add a new send headers common helper and use the same wrt the overridden send_error as well as do_OPTIONS This ensures that if there is any error during proxy opertions, the send_error propogates to the fetch from any browser properly without browser intercepting it with a CORS error --- .../local.tools/simpleproxy.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 78cf6d6767ee2..ce638dfa15cda 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -23,6 +23,20 @@ class ProxyHandler(http.server.BaseHTTPRequestHandler): + # Common headers to include in responses from this server + def send_headers_common(self): + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS') + self.send_header('Access-Control-Allow-Headers', '*') + self.end_headers() + + # overrides the SendError helper + # so that the common headers mentioned above can get added to them + # else CORS failure will be triggered by the browser on fetch from browser. + def send_error(self, code: int, message: str | None = None, explain: str | None = None) -> None: + self.send_response(code, message) + self.send_headers_common() + # Handle GET requests def do_GET(self): print(f"DBUG:ProxyHandler:GET:{self.path}") @@ -41,10 +55,7 @@ def do_GET(self): def do_OPTIONS(self): print(f"DBUG:ProxyHandler:OPTIONS:{self.path}") self.send_response(200) - self.send_header('Access-Control-Allow-Origin', '*') - self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS') - self.send_header('Access-Control-Allow-Headers', '*') - self.end_headers() + self.send_headers_common() @dataclass(frozen=True) From 4ffed9476f3b7e8e196f8b65b3f0479be33c51c4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 22:46:08 +0530 Subject: [PATCH 089/187] SimpleChatTC:WebFetch:Trap Non Ok status and raise error So that the same error path is used for logical error wrt http req also, without needing a different path for it. Dont forget to return the resp text/json/..., so that the contents are passed along the promise then chain --- tools/server/public_simplechat/tooljs.mjs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index da09d9013a06c..a60f283e7f4e8 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -116,7 +116,12 @@ let fetchweburlraw_meta = { function fetchweburlraw_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { let newUrl = `http://127.0.0.1:3128/urlraw?url=${encodeURIComponent(obj.url)}` - fetch(newUrl).then(resp=>resp.text()).then(data => { + fetch(newUrl).then(resp => { + if (!resp.ok) { + throw new Error(`${resp.status}:${resp.statusText}`); + } + return resp.text() + }).then(data => { message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) }).catch((err)=>{ message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) @@ -161,7 +166,12 @@ let fetchweburltext_meta = { function fetchweburltext_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { let newUrl = `http://127.0.0.1:3128/urltext?url=${encodeURIComponent(obj.url)}` - fetch(newUrl).then(resp=>resp.text()).then(data => { + fetch(newUrl).then(resp => { + if (!resp.ok) { + throw new Error(`${resp.status}:${resp.statusText}`); + } + return resp.text() + }).then(data => { message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) }).catch((err)=>{ message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) From a2a89a0b05b2f749e0570cfc5add5cbe01687724 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 18 Oct 2025 01:27:53 +0530 Subject: [PATCH 090/187] SimpleChatTC:WebFetch: Update readme to reflect the new names --- tools/server/public_simplechat/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 6c1d55132b624..79f1097606cae 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -334,9 +334,9 @@ The following tools/functions are currently provided by default * simple_calculator - which can solve simple arithmatic expressions * run_javascript_function_code - which can be used to run some javascript code in the browser context. -* web_url_fetch - fetch requested url through a proxy server -* web_url_fetch_strip_htmltags_and_some_useless - fetch requested url through a proxy server - and also try strip the html respose of html tags and also head, script & style blocks. +* fetch_web_url_raw - fetch requested url through a proxy server +* fetch_web_url_text - fetch requested url through a proxy server + and also try strip the html respose of html tags and also head, script, style, header,footer,... blocks. Currently the generated code / expression is run through a simple minded eval inside a web worker mechanism. Use of WebWorker helps avoid exposing browser global scope to the generated code directly. From 7dc5929687b46fe3cf5bbba881161db76cb5b2f1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 18 Oct 2025 18:40:09 +0530 Subject: [PATCH 091/187] SimpleChatTC:Tools: Pick proxy server address from document[gMe] --- tools/server/public_simplechat/simplechat.js | 1 + tools/server/public_simplechat/tooljs.mjs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index c7259fb950b1c..893c91d68fdce 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1035,6 +1035,7 @@ class Me { //"frequency_penalty": 1.2, //"presence_penalty": 1.2, }; + this.proxyUrl = "http://127.0.0.1:3128" } /** diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index a60f283e7f4e8..ffaab5de2e4a6 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -115,7 +115,8 @@ let fetchweburlraw_meta = { */ function fetchweburlraw_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { - let newUrl = `http://127.0.0.1:3128/urlraw?url=${encodeURIComponent(obj.url)}` + // @ts-ignore + let newUrl = `${document['gMe'].proxyUrl}/urlraw?url=${encodeURIComponent(obj.url)}` fetch(newUrl).then(resp => { if (!resp.ok) { throw new Error(`${resp.status}:${resp.statusText}`); @@ -165,7 +166,8 @@ let fetchweburltext_meta = { */ function fetchweburltext_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { - let newUrl = `http://127.0.0.1:3128/urltext?url=${encodeURIComponent(obj.url)}` + // @ts-ignore + let newUrl = `${document['gMe'].proxyUrl}/urltext?url=${encodeURIComponent(obj.url)}` fetch(newUrl).then(resp => { if (!resp.ok) { throw new Error(`${resp.status}:${resp.statusText}`); From aa1c43d2d7c0a88f74833240416f965a28a245d4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 00:41:14 +0530 Subject: [PATCH 092/187] SimpleChatTC:UI: el_get/el_set to avoid warnings --- tools/server/public_simplechat/ui.mjs | 37 +++++++++++++++++++++------ 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index b2d5b9aeab76c..eb0ce888758c7 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -4,6 +4,27 @@ // +/** + * Insert key-value pairs into passed element object. + * @param {HTMLElement} el + * @param {string} key + * @param {any} value + */ +function el_set(el, key, value) { + // @ts-ignore + el[key] = value +} + +/** + * Retrieve the value corresponding to given key from passed element object. + * @param {HTMLElement} el + * @param {string} key + */ +function el_get(el, key) { + // @ts-ignore + return el[key] +} + /** * Set the class of the children, based on whether it is the idSelected or not. * @param {HTMLDivElement} elBase @@ -72,16 +93,16 @@ export function el_create_append_p(text, elParent=undefined, id=undefined) { */ export function el_create_boolbutton(id, texts, defaultValue, cb) { let el = document.createElement("button"); - el["xbool"] = defaultValue; - el["xtexts"] = structuredClone(texts); - el.innerText = el["xtexts"][String(defaultValue)]; + el_set(el, "xbool", defaultValue) + el_set(el, "xtexts", structuredClone(texts)) + el.innerText = el_get(el, "xtexts")[String(defaultValue)]; if (id) { el.id = id; } el.addEventListener('click', (ev)=>{ - el["xbool"] = !el["xbool"]; - el.innerText = el["xtexts"][String(el["xbool"])]; - cb(el["xbool"]); + el_set(el, "xbool", !el_get(el, "xbool")); + el.innerText = el_get(el, "xtexts")[String(el_get(el, "xbool"))]; + cb(el_get(el, "xbool")); }) return el; } @@ -121,8 +142,8 @@ export function el_creatediv_boolbutton(id, label, texts, defaultValue, cb, clas */ export function el_create_select(id, options, defaultOption, cb) { let el = document.createElement("select"); - el["xselected"] = defaultOption; - el["xoptions"] = structuredClone(options); + el_set(el, "xselected", defaultOption); + el_set(el, "xoptions", structuredClone(options)); for(let cur of Object.keys(options)) { let op = document.createElement("option"); op.value = cur; From 8a68ccbe3e4c5acc0ff3c2ccb2d04d29d3e676bf Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 13:58:27 +0530 Subject: [PATCH 093/187] SimpleChatTC:UI:Common helper to edit obj members of few types Make the previously relatively generic flow wrt apiRequestOptions settings into a fully generic reusable by others flow. Rather had stopped short of it, when previously moved onto other things at that time. --- tools/server/public_simplechat/simplechat.js | 2 +- tools/server/public_simplechat/ui.mjs | 39 ++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 893c91d68fdce..073d95f89bd1e 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1174,7 +1174,7 @@ class Me { }); elDiv.appendChild(bb.div); - this.show_settings_apirequestoptions(elDiv); + ui.ui_show_obj_props_edit(elDiv, this.apiRequestOptions, Object.keys(this.apiRequestOptions), "ApiRequestOptions") let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{ // @ts-ignore diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index eb0ce888758c7..dcf1fba66db9d 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -230,3 +230,42 @@ export function el_creatediv_input(id, label, type, defaultValue, cb, className= div.appendChild(el); return { div: div, el: el }; } + + +/** + * Auto create ui input elements for fields in apiRequestOptions + * Currently supports text and number field types. + * @param {HTMLDivElement} elDiv + * @param {any} oObj + * @param {Array} lProps + * @param {string} sLegend + */ +export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend) { + let typeDict = { + "string": "text", + "number": "number", + }; + let fs = document.createElement("fieldset"); + let legend = document.createElement("legend"); + legend.innerText = sLegend; + fs.appendChild(legend); + elDiv.appendChild(fs); + for(const k in lProps) { + let val = oObj[k]; + let type = typeof(val); + if (((type == "string") || (type == "number"))) { + let inp = el_creatediv_input(`Set${k}`, k, typeDict[type], oObj[k], (val)=>{ + if (type == "number") { + val = Number(val); + } + oObj[k] = val; + }); + fs.appendChild(inp.div); + } else if (type == "boolean") { + let bbtn = el_creatediv_boolbutton(`Set{k}`, k, {true: "true", false: "false"}, val, (userVal)=>{ + oObj[k] = userVal; + }); + fs.appendChild(bbtn.div); + } + } +} From 60b2bfda07abd29cbac30fc08669a91b0801b299 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 14:55:13 +0530 Subject: [PATCH 094/187] SimpleChatTC:UI:ObjPropEdits handle objects, use for gMe --- tools/server/public_simplechat/simplechat.js | 2 +- tools/server/public_simplechat/ui.mjs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 073d95f89bd1e..4ce17316effdf 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1174,7 +1174,7 @@ class Me { }); elDiv.appendChild(bb.div); - ui.ui_show_obj_props_edit(elDiv, this.apiRequestOptions, Object.keys(this.apiRequestOptions), "ApiRequestOptions") + ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "bTools", "apiRequestOptions", "iRecentUserMsgCnt"], "Settings") let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{ // @ts-ignore diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index dcf1fba66db9d..6d9cc9a1ad9f6 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -235,7 +235,7 @@ export function el_creatediv_input(id, label, type, defaultValue, cb, className= /** * Auto create ui input elements for fields in apiRequestOptions * Currently supports text and number field types. - * @param {HTMLDivElement} elDiv + * @param {HTMLDivElement|HTMLFieldSetElement} elDiv * @param {any} oObj * @param {Array} lProps * @param {string} sLegend @@ -250,7 +250,7 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend) { legend.innerText = sLegend; fs.appendChild(legend); elDiv.appendChild(fs); - for(const k in lProps) { + for(const k of lProps) { let val = oObj[k]; let type = typeof(val); if (((type == "string") || (type == "number"))) { @@ -266,6 +266,8 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend) { oObj[k] = userVal; }); fs.appendChild(bbtn.div); + } else if (type == "object") { + ui_show_obj_props_edit(fs, val, Object.keys(val), k) } } } From 9b486181f6c3f692af97e27ed0bc1dd209168d2e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 15:05:55 +0530 Subject: [PATCH 095/187] SimpleChatTC:Use generic obj props edit for settings in general Bring more user controllable properties into this new settings ui --- tools/server/public_simplechat/simplechat.js | 38 +------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 4ce17316effdf..23bb561287244 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1148,33 +1148,7 @@ class Me { */ show_settings(elDiv) { - let inp = ui.el_creatediv_input("SetBaseURL", "BaseURL", "text", this.baseURL, (val)=>{ - this.baseURL = val; - }); - elDiv.appendChild(inp.div); - - inp = ui.el_creatediv_input("SetAuthorization", "Authorization", "text", this.headers["Authorization"], (val)=>{ - this.headers["Authorization"] = val; - }); - inp.el.placeholder = "Bearer OPENAI_API_KEY"; - elDiv.appendChild(inp.div); - - let bb = ui.el_creatediv_boolbutton("SetStream", "Stream", {true: "[+] yes stream", false: "[-] do oneshot"}, this.bStream, (val)=>{ - this.bStream = val; - }); - elDiv.appendChild(bb.div); - - bb = ui.el_creatediv_boolbutton("SetTools", "Tools", {true: "[+] yes tools", false: "[-] no tools"}, this.bTools, (val)=>{ - this.bTools = val; - }); - elDiv.appendChild(bb.div); - - bb = ui.el_creatediv_boolbutton("SetTrimGarbage", "TrimGarbage", {true: "[+] yes trim", false: "[-] dont trim"}, this.bTrimGarbage, (val)=>{ - this.bTrimGarbage = val; - }); - elDiv.appendChild(bb.div); - - ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "bTools", "apiRequestOptions", "iRecentUserMsgCnt"], "Settings") + ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "bTools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings") let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{ // @ts-ignore @@ -1187,16 +1161,6 @@ class Me { }); elDiv.appendChild(sel.div); - bb = ui.el_creatediv_boolbutton("SetCompletionFreshChatAlways", "CompletionFreshChatAlways", {true: "[+] yes fresh", false: "[-] no, with history"}, this.bCompletionFreshChatAlways, (val)=>{ - this.bCompletionFreshChatAlways = val; - }); - elDiv.appendChild(bb.div); - - bb = ui.el_creatediv_boolbutton("SetCompletionInsertStandardRolePrefix", "CompletionInsertStandardRolePrefix", {true: "[+] yes insert", false: "[-] dont insert"}, this.bCompletionInsertStandardRolePrefix, (val)=>{ - this.bCompletionInsertStandardRolePrefix = val; - }); - elDiv.appendChild(bb.div); - } } From e8972763233c20300c7ec8431f01c5eb9f888eba Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 15:35:34 +0530 Subject: [PATCH 096/187] SimpleChatTC:Trappable UiShowObjPropsEdit for custom handling Use it to handle apiEP and iRecentUserMsgCnt in more user friendly way, where they get a selection to choose from. --- tools/server/public_simplechat/simplechat.js | 63 +++++--------------- tools/server/public_simplechat/ui.mjs | 21 ++++++- 2 files changed, 33 insertions(+), 51 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 23bb561287244..637cff74bfa4c 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1107,60 +1107,27 @@ class Me { } - /** - * Auto create ui input elements for fields in apiRequestOptions - * Currently supports text and number field types. - * @param {HTMLDivElement} elDiv - */ - show_settings_apirequestoptions(elDiv) { - let typeDict = { - "string": "text", - "number": "number", - }; - let fs = document.createElement("fieldset"); - let legend = document.createElement("legend"); - legend.innerText = "ApiRequestOptions"; - fs.appendChild(legend); - elDiv.appendChild(fs); - for(const k in this.apiRequestOptions) { - let val = this.apiRequestOptions[k]; - let type = typeof(val); - if (((type == "string") || (type == "number"))) { - let inp = ui.el_creatediv_input(`Set${k}`, k, typeDict[type], this.apiRequestOptions[k], (val)=>{ - if (type == "number") { - val = Number(val); - } - this.apiRequestOptions[k] = val; - }); - fs.appendChild(inp.div); - } else if (type == "boolean") { - let bbtn = ui.el_creatediv_boolbutton(`Set{k}`, k, {true: "true", false: "false"}, val, (userVal)=>{ - this.apiRequestOptions[k] = userVal; - }); - fs.appendChild(bbtn.div); - } - } - } - /** * Show settings ui for configurable parameters, in the passed Div element. * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "bTools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings") - - let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{ - // @ts-ignore - this.apiEP = ApiEP.Type[val]; - }); - elDiv.appendChild(sel.div); - - sel = ui.el_creatediv_select("SetChatHistoryInCtxt", "ChatHistoryInCtxt", this.sRecentUserMsgCnt, this.iRecentUserMsgCnt, (val)=>{ - this.iRecentUserMsgCnt = this.sRecentUserMsgCnt[val]; - }); - elDiv.appendChild(sel.div); - + ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "bTools", "apiRequestOptions", "TRAPME-apiEP", "TRAPME-iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", "TRAPME-", (tag, elParent)=>{ + if (tag == "TRAPME-apiEP") { + let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{ + // @ts-ignore + this.apiEP = ApiEP.Type[val]; + }); + elParent.appendChild(sel.div); + } + if (tag == "TRAPME-iRecentUserMsgCnt") { + let sel = ui.el_creatediv_select("SetChatHistoryInCtxt", "ChatHistoryInCtxt", this.sRecentUserMsgCnt, this.iRecentUserMsgCnt, (val)=>{ + this.iRecentUserMsgCnt = this.sRecentUserMsgCnt[val]; + }); + elParent.appendChild(sel.div); + } + }) } } diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index 6d9cc9a1ad9f6..352d4895e03d9 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -233,14 +233,21 @@ export function el_creatediv_input(id, label, type, defaultValue, cb, className= /** - * Auto create ui input elements for fields in apiRequestOptions - * Currently supports text and number field types. + * Auto create ui input elements for specified fields/properties in given object + * Currently supports text, number, boolean field types. + * Also supports recursing if a object type field is found. + * For some reason if caller wants to handle certain properties on their own + * * prefix the prop name in lProps with sTrapTag + * * fTrapper will be called with the parent ui element + * into which the new ui elements created for editting the prop, if any, should be attached * @param {HTMLDivElement|HTMLFieldSetElement} elDiv * @param {any} oObj * @param {Array} lProps * @param {string} sLegend + * @param {string | undefined} sTrapTag + * @param {((tagPlusProp: string, elParent: HTMLFieldSetElement)=>void) | undefined} fTrapper */ -export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend) { +export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, sTrapTag=undefined, fTrapper=undefined) { let typeDict = { "string": "text", "number": "number", @@ -251,6 +258,14 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend) { fs.appendChild(legend); elDiv.appendChild(fs); for(const k of lProps) { + if (sTrapTag) { + if (k.startsWith(sTrapTag)) { + if (fTrapper) { + fTrapper(k, fs) + } + continue + } + } let val = oObj[k]; let type = typeof(val); if (((type == "string") || (type == "number"))) { From 97af8f685f5b1de430b71aae1f357f5876a39ab8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 15:48:58 +0530 Subject: [PATCH 097/187] SimpleChatTC:UiShowObjPropsEdit allow refining --- tools/server/public_simplechat/ui.mjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index 352d4895e03d9..295b14b9b00b8 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -244,10 +244,11 @@ export function el_creatediv_input(id, label, type, defaultValue, cb, className= * @param {any} oObj * @param {Array} lProps * @param {string} sLegend + * @param {((prop:string, elUI: HTMLElement)=>void)| undefined} fRefiner * @param {string | undefined} sTrapTag * @param {((tagPlusProp: string, elParent: HTMLFieldSetElement)=>void) | undefined} fTrapper */ -export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, sTrapTag=undefined, fTrapper=undefined) { +export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, fRefiner=undefined, sTrapTag=undefined, fTrapper=undefined) { let typeDict = { "string": "text", "number": "number", @@ -275,11 +276,17 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, sTrapTag=un } oObj[k] = val; }); + if (fRefiner) { + fRefiner(k, inp.el) + } fs.appendChild(inp.div); } else if (type == "boolean") { let bbtn = el_creatediv_boolbutton(`Set{k}`, k, {true: "true", false: "false"}, val, (userVal)=>{ oObj[k] = userVal; }); + if (fRefiner) { + fRefiner(k, bbtn.el) + } fs.appendChild(bbtn.div); } else if (type == "object") { ui_show_obj_props_edit(fs, val, Object.keys(val), k) From a3e424daf4c485e3ed8dae1462e547a92d154ac8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 16:08:19 +0530 Subject: [PATCH 098/187] SimpleChatTC:ObjPropsEdit: Obj within Obj aware fRefiner Use same to set a placeholder for Authorization entry in headers --- tools/server/public_simplechat/simplechat.js | 8 ++++++-- tools/server/public_simplechat/ui.mjs | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 637cff74bfa4c..d1692c3844bcb 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1112,8 +1112,12 @@ class Me { * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - - ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "bTools", "apiRequestOptions", "TRAPME-apiEP", "TRAPME-iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", "TRAPME-", (tag, elParent)=>{ + ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "bTools", "apiRequestOptions", "TRAPME-apiEP", "TRAPME-iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ + if (prop == "headers:Authorization") { + // @ts-ignore + elProp.placeholder = "Bearer OPENAI_API_KEY"; + } + }, "TRAPME-", (tag, elParent)=>{ if (tag == "TRAPME-apiEP") { let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{ // @ts-ignore diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index 295b14b9b00b8..b16a19ba77139 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -236,6 +236,11 @@ export function el_creatediv_input(id, label, type, defaultValue, cb, className= * Auto create ui input elements for specified fields/properties in given object * Currently supports text, number, boolean field types. * Also supports recursing if a object type field is found. + * + * If for any reason the caller wants to refine the created ui element for a specific prop, + * they can define a fRefiner callback, which will be called back with prop name and ui element. + * The fRefiner callback even helps work with Obj with-in Obj scenarios. + * * For some reason if caller wants to handle certain properties on their own * * prefix the prop name in lProps with sTrapTag * * fTrapper will be called with the parent ui element @@ -244,7 +249,7 @@ export function el_creatediv_input(id, label, type, defaultValue, cb, className= * @param {any} oObj * @param {Array} lProps * @param {string} sLegend - * @param {((prop:string, elUI: HTMLElement)=>void)| undefined} fRefiner + * @param {((prop:string, elProp: HTMLElement)=>void)| undefined} fRefiner * @param {string | undefined} sTrapTag * @param {((tagPlusProp: string, elParent: HTMLFieldSetElement)=>void) | undefined} fTrapper */ @@ -289,7 +294,12 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, fRefiner=un } fs.appendChild(bbtn.div); } else if (type == "object") { - ui_show_obj_props_edit(fs, val, Object.keys(val), k) + ui_show_obj_props_edit(fs, val, Object.keys(val), k, (prop, elProp)=>{ + if (fRefiner) { + let theProp = `${k}:${prop}` + fRefiner(theProp, elProp) + } + }) } } } From 94982cf79d5667880a2f4c8d2a44fe3cd235b849 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 16:59:32 +0530 Subject: [PATCH 099/187] SimpleChatTC:NonStreaming: Update oneshot mode wrt tool calls Take care of the possibility of content not being there as well as take care of retrieving the tool calls for further processing. With this tool calls should work in non streaming mode also --- tools/server/public_simplechat/simplechat.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index d1692c3844bcb..eaea16223f951 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -199,7 +199,16 @@ class ChatMessageEx { */ update_oneshot(nwo, apiEP) { if (apiEP == ApiEP.Type.Chat) { - this.ns.content = nwo["choices"][0]["message"]["content"]; + let curContent = nwo["choices"][0]["message"]["content"]; + if (curContent != undefined) { + if (curContent != null) { + this.ns.content = curContent; + } + } + let curTCs = nwo["choices"][0]["message"]["tool_calls"]; + if (curTCs != undefined) { + this.ns.tool_calls = curTCs; + } } else { try { this.ns.content = nwo["choices"][0]["text"]; From 8be6d8e2d47c27f01a391fb00ebd36b671832430 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 17:28:46 +0530 Subject: [PATCH 100/187] SimpleChatTC: Update/Cleanup readme --- tools/server/public_simplechat/readme.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 79f1097606cae..a856b21b82289 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -94,7 +94,7 @@ Once inside * oneshot or streamed mode. * use built in tool calling or not -* In completion mode +* In completion mode >> note: most recent work has been in chat mode << * one normally doesnt use a system prompt in completion mode. * logic by default doesnt insert any role specific "ROLE: " prefix wrt each role's message. If the model requires any prefix wrt user role messages, then the end user has to @@ -263,9 +263,9 @@ matter clearing site data, dont directly override site caching in all cases. Wor have to change port. Or in dev tools of browser, you may be able to disable caching fully. -Currently the server to communicate with is maintained globally and not as part of a specific -chat session. So if one changes the server ip/url in setting, then all chat sessions will auto -switch to this new server, when you try using those sessions. +Currently the settings are maintained globally and not as part of a specific chat session, including +the server to communicate with. So if one changes the server ip/url in setting, then all chat sessions +will auto switch to this new server, when you try using those sessions. By switching between chat.add_system_begin/anytime, one can control whether one can change @@ -298,7 +298,9 @@ wrt the set of fields sent to server along with the user query, to check how the wrt repeatations in general in the generated text response. A end-user can change these behaviour by editing gMe from browser's devel-tool/console or by -using the provided settings ui (for settings exposed through the ui). +using the provided settings ui (for settings exposed through the ui). The logic uses a generic +helper which autocreates property edit ui elements for the specified set of properties. If the +new property is a number or text or boolean, the autocreate logic will handle it. ### OpenAi / Equivalent API WebService @@ -404,6 +406,8 @@ Is the promise land trap deep enough, need to think through and explore around t Trap error responses. +Handle reasoning/thinking responses from ai models. + ### Debuging the handshake From 7888a8887ebf14c438be6eb6d814110eebed91a0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 18:59:18 +0530 Subject: [PATCH 101/187] SimpleChatTC:WebFetch: Enable only if something at proxyUrl NOTE: not a robust check, just tries to establish a http connection for now and doesnt really check if it is the specific proxy srvr of interest or not. --- tools/server/public_simplechat/readme.md | 7 +++- tools/server/public_simplechat/tooljs.mjs | 49 ++++++++++++++++++----- tools/server/public_simplechat/tools.mjs | 9 +++-- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index a856b21b82289..3a1b5e104cb4c 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -350,8 +350,11 @@ server logic, this helps bypass the CORS restrictions applied if trying to direc browser js runtime environment. Depending on the path specified wrt the proxy server, if urltext (and not urlraw), it additionally tries to convert html content into equivalent text to some extent in a simple minded manner by dropping head block as well as all scripts/styles/footers/headers/nav. -May add support for white list of allowed sites to access or so. The simple proxy can be found at -* tools/server/public_simplechat/local.tools/simpleproxy.py +May add support for white list of allowed sites to access or so. +* the logic does a simple dumb check to see if there is something running at specified proxyUrl + before enabling fetch web related tool calls. +* The bundled simple proxy can be found at + * tools/server/public_simplechat/local.tools/simpleproxy.py #### Extending with new tools diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index ffaab5de2e4a6..f013cac70a0f5 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -131,6 +131,23 @@ function fetchweburlraw_run(toolcallid, toolname, obj) { } +/** + * Setup fetch_web_url_raw for tool calling + * NOTE: Currently it just checks there is something at given proxyUrl + * @param {Object>} tcs + */ +async function fetchweburlraw_setup(tcs) { + // @ts-ignore + let got = await fetch(document["gMe"].proxyUrl).then(resp=>{ + tcs["fetch_web_url_raw"] = { + "handler": fetchweburlraw_run, + "meta": fetchweburlraw_meta, + "result": "" + }; + }).catch(err=>console.log(`WARN:ToolJS:FetchWebUrlRaw:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) +} + + let fetchweburltext_meta = { "type": "function", "function": { @@ -182,6 +199,23 @@ function fetchweburltext_run(toolcallid, toolname, obj) { } +/** + * Setup fetch_web_url_text for tool calling + * NOTE: Currently it just checks there is something at given proxyUrl + * @param {Object>} tcs + */ +async function fetchweburltext_setup(tcs) { + // @ts-ignore + let got = await fetch(document["gMe"].proxyUrl).then(resp=>{ + tcs["fetch_web_url_text"] = { + "handler": fetchweburltext_run, + "meta": fetchweburltext_meta, + "result": "" + }; + }).catch(err=>console.log(`WARN:ToolJS:FetchWebUrlText:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) +} + + /** * @type {Object>} */ @@ -196,23 +230,16 @@ export let tc_switch = { "meta": calc_meta, "result": "" }, - "fetch_web_url_raw": { - "handler": fetchweburlraw_run, - "meta": fetchweburlraw_meta, - "result": "" - }, - "fetch_web_url_text": { - "handler": fetchweburltext_run, - "meta": fetchweburltext_meta, - "result": "" - } } /** * Used to get hold of the web worker to use for running tool/function call related code + * Also to setup tool calls, which need to cross check things at runtime * @param {Worker} toolsWorker */ -export function init(toolsWorker) { +export async function init(toolsWorker) { gToolsWorker = toolsWorker + await fetchweburlraw_setup(tc_switch) + await fetchweburltext_setup(tc_switch) } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 8c89e965258b4..14249b517a83d 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -15,10 +15,11 @@ let gToolsWorker = new Worker('./toolsworker.mjs', { type: 'module' }); export let tc_switch = {} export function init() { - tjs.init(gToolsWorker) - for (const key in tjs.tc_switch) { - tc_switch[key] = tjs.tc_switch[key] - } + tjs.init(gToolsWorker).then(()=>{ + for (const key in tjs.tc_switch) { + tc_switch[key] = tjs.tc_switch[key] + } + }) } export function meta() { From d667e2d8e1ade369e700a82f4c695af51c2735f1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 19:45:12 +0530 Subject: [PATCH 102/187] SimpleChatTC:WebFetch: Check for the specific proxy paths --- tools/server/public_simplechat/tooljs.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index f013cac70a0f5..b195288aa6b2d 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -138,7 +138,7 @@ function fetchweburlraw_run(toolcallid, toolname, obj) { */ async function fetchweburlraw_setup(tcs) { // @ts-ignore - let got = await fetch(document["gMe"].proxyUrl).then(resp=>{ + let got = await fetch(`${document["gMe"].proxyUrl}/urlraw?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ tcs["fetch_web_url_raw"] = { "handler": fetchweburlraw_run, "meta": fetchweburlraw_meta, @@ -206,7 +206,7 @@ function fetchweburltext_run(toolcallid, toolname, obj) { */ async function fetchweburltext_setup(tcs) { // @ts-ignore - let got = await fetch(document["gMe"].proxyUrl).then(resp=>{ + let got = await fetch(`${document["gMe"].proxyUrl}/urltext?url=jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ tcs["fetch_web_url_text"] = { "handler": fetchweburltext_run, "meta": fetchweburltext_meta, From f8e80875e3acbe4b46f670086b763fedb4359a33 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 19:55:12 +0530 Subject: [PATCH 103/187] SimpleChatTC:WebFetch: Try confirm simpleproxy before enabling --- .../local.tools/simpleproxy.py | 8 ++++++++ tools/server/public_simplechat/readme.md | 2 +- tools/server/public_simplechat/tooljs.mjs | 20 +++++++++++++++---- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index ce638dfa15cda..8185382297775 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -47,6 +47,8 @@ def do_GET(self): handle_urlraw(self, pr) case '/urltext': handle_urltext(self, pr) + case '/aum': + handle_aum(self, pr) case _: print(f"WARN:ProxyHandler:GET:UnknownPath{pr.path}") self.send_error(400, f"WARN:UnknownPath:{pr.path}") @@ -58,6 +60,12 @@ def do_OPTIONS(self): self.send_headers_common() +def handle_aum(ph: ProxyHandler, pr: urllib.parse.ParseResult): + ph.send_response_only(200, "bharatavarshe") + ph.send_header('Access-Control-Allow-Origin', '*') + ph.end_headers() + + @dataclass(frozen=True) class UrlReqResp: callOk: bool diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 3a1b5e104cb4c..63b9a43eadf43 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -351,7 +351,7 @@ browser js runtime environment. Depending on the path specified wrt the proxy se (and not urlraw), it additionally tries to convert html content into equivalent text to some extent in a simple minded manner by dropping head block as well as all scripts/styles/footers/headers/nav. May add support for white list of allowed sites to access or so. -* the logic does a simple dumb check to see if there is something running at specified proxyUrl +* the logic does a simple check to see if the bundled simpleproxy is running at specified proxyUrl before enabling fetch web related tool calls. * The bundled simple proxy can be found at * tools/server/public_simplechat/local.tools/simpleproxy.py diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index b195288aa6b2d..755d9eae75843 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -133,12 +133,18 @@ function fetchweburlraw_run(toolcallid, toolname, obj) { /** * Setup fetch_web_url_raw for tool calling - * NOTE: Currently it just checks there is something at given proxyUrl + * NOTE: Currently the logic is setup for the bundled simpleproxy.py * @param {Object>} tcs */ async function fetchweburlraw_setup(tcs) { // @ts-ignore - let got = await fetch(`${document["gMe"].proxyUrl}/urlraw?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ + let got = await fetch(`${document["gMe"].proxyUrl}/aum?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ + if (resp.statusText != 'bharatavarshe') { + console.log("WARN:ToolJS:FetchWebUrlRaw:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") + return + } else { + console.log("INFO:ToolJS:FetchWebUrlRaw:Enabling...") + } tcs["fetch_web_url_raw"] = { "handler": fetchweburlraw_run, "meta": fetchweburlraw_meta, @@ -201,12 +207,18 @@ function fetchweburltext_run(toolcallid, toolname, obj) { /** * Setup fetch_web_url_text for tool calling - * NOTE: Currently it just checks there is something at given proxyUrl + * NOTE: Currently the logic is setup for the bundled simpleproxy.py * @param {Object>} tcs */ async function fetchweburltext_setup(tcs) { // @ts-ignore - let got = await fetch(`${document["gMe"].proxyUrl}/urltext?url=jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ + let got = await fetch(`${document["gMe"].proxyUrl}/aum?url=jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ + if (resp.statusText != 'bharatavarshe') { + console.log("WARN:ToolJS:FetchWebUrlText:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") + return + } else { + console.log("INFO:ToolJS:FetchWebUrlText:Enabling...") + } tcs["fetch_web_url_text"] = { "handler": fetchweburltext_run, "meta": fetchweburltext_meta, From 38fd796a7d599ca86c01c3221b70bd25c2b56e0f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 21:01:23 +0530 Subject: [PATCH 104/187] SimpleChatTC:Fetch:Proxy URL rename and in settings --- tools/server/public_simplechat/simplechat.js | 4 ++-- tools/server/public_simplechat/tooljs.mjs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index eaea16223f951..89f8ce435ba66 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1044,7 +1044,7 @@ class Me { //"frequency_penalty": 1.2, //"presence_penalty": 1.2, }; - this.proxyUrl = "http://127.0.0.1:3128" + this.toolFetchProxyUrl = "http://127.0.0.1:3128" } /** @@ -1121,7 +1121,7 @@ class Me { * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "bTools", "apiRequestOptions", "TRAPME-apiEP", "TRAPME-iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ + ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "bTools", "apiRequestOptions", "TRAPME-apiEP", "TRAPME-iRecentUserMsgCnt", "toolFetchProxyUrl", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ if (prop == "headers:Authorization") { // @ts-ignore elProp.placeholder = "Bearer OPENAI_API_KEY"; diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 755d9eae75843..7a217e2ef6012 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -116,7 +116,7 @@ let fetchweburlraw_meta = { function fetchweburlraw_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { // @ts-ignore - let newUrl = `${document['gMe'].proxyUrl}/urlraw?url=${encodeURIComponent(obj.url)}` + let newUrl = `${document['gMe'].toolFetchProxyUrl}/urlraw?url=${encodeURIComponent(obj.url)}` fetch(newUrl).then(resp => { if (!resp.ok) { throw new Error(`${resp.status}:${resp.statusText}`); @@ -138,7 +138,7 @@ function fetchweburlraw_run(toolcallid, toolname, obj) { */ async function fetchweburlraw_setup(tcs) { // @ts-ignore - let got = await fetch(`${document["gMe"].proxyUrl}/aum?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ + let got = await fetch(`${document["gMe"].toolFetchProxyUrl}/aum?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ if (resp.statusText != 'bharatavarshe') { console.log("WARN:ToolJS:FetchWebUrlRaw:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") return @@ -190,7 +190,7 @@ let fetchweburltext_meta = { function fetchweburltext_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { // @ts-ignore - let newUrl = `${document['gMe'].proxyUrl}/urltext?url=${encodeURIComponent(obj.url)}` + let newUrl = `${document['gMe'].toolFetchProxyUrl}/urltext?url=${encodeURIComponent(obj.url)}` fetch(newUrl).then(resp => { if (!resp.ok) { throw new Error(`${resp.status}:${resp.statusText}`); @@ -212,7 +212,7 @@ function fetchweburltext_run(toolcallid, toolname, obj) { */ async function fetchweburltext_setup(tcs) { // @ts-ignore - let got = await fetch(`${document["gMe"].proxyUrl}/aum?url=jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ + let got = await fetch(`${document["gMe"].toolFetchProxyUrl}/aum?url=jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ if (resp.statusText != 'bharatavarshe') { console.log("WARN:ToolJS:FetchWebUrlText:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") return From 4e73683ba5702ac26e8c37ae5b96379e1bade40c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 01:57:57 +0530 Subject: [PATCH 105/187] SimpleChatTC:ShowInfo: Create and use common automated info show Also fetch info from ai-server, and place path and ctx size into current Me instance and include in show info. --- tools/server/public_simplechat/simplechat.js | 43 +++++--------------- tools/server/public_simplechat/ui.mjs | 23 +++++++++++ 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 89f8ce435ba66..1ea8dd62e29c7 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -402,8 +402,9 @@ class SimpleChat { * Show the contents in the specified div * @param {HTMLDivElement} div * @param {boolean} bClear + * @param {boolean} bShowInfoAll */ - show(div, bClear=true) { + show(div, bClear=true, bShowInfoAll=false) { if (bClear) { div.replaceChildren(); } @@ -419,7 +420,7 @@ class SimpleChat { if (bClear) { div.innerHTML = gUsageMsg; gMe.setup_load(div, this); - gMe.show_info(div); + gMe.show_info(div, bShowInfoAll); } } return last; @@ -1072,7 +1073,7 @@ class Me { console.log("DBUG:SimpleChat:SC:Load", chat); chat.load(); queueMicrotask(()=>{ - chat.show(div); + chat.show(div, true, true); this.multiChat.elInSystem.value = chat.get_system_latest().ns.content; }); }); @@ -1085,35 +1086,13 @@ class Me { * @param {boolean} bAll */ show_info(elDiv, bAll=false) { - - let p = ui.el_create_append_p("Settings (devel-tools-console document[gMe])", elDiv); - p.className = "role-system"; - - if (bAll) { - - ui.el_create_append_p(`baseURL:${this.baseURL}`, elDiv); - - ui.el_create_append_p(`Authorization:${this.headers["Authorization"]}`, elDiv); - - ui.el_create_append_p(`bStream:${this.bStream}`, elDiv); - - ui.el_create_append_p(`bTools:${this.bTools}`, elDiv); - - ui.el_create_append_p(`bTrimGarbage:${this.bTrimGarbage}`, elDiv); - - ui.el_create_append_p(`ApiEndPoint:${this.apiEP}`, elDiv); - - ui.el_create_append_p(`iRecentUserMsgCnt:${this.iRecentUserMsgCnt}`, elDiv); - - ui.el_create_append_p(`bCompletionFreshChatAlways:${this.bCompletionFreshChatAlways}`, elDiv); - - ui.el_create_append_p(`bCompletionInsertStandardRolePrefix:${this.bCompletionInsertStandardRolePrefix}`, elDiv); - - } - - ui.el_create_append_p(`apiRequestOptions:${JSON.stringify(this.apiRequestOptions, null, " - ")}`, elDiv); - ui.el_create_append_p(`headers:${JSON.stringify(this.headers, null, " - ")}`, elDiv); - + fetch(`${this.baseURL}/props`).then(resp=>resp.json()).then(json=>{ + this.modelInfo = { + modelPath: json["model_path"], + ctxSize: json["default_generation_settings"]["n_ctx"] + } + ui.ui_show_obj_props_info(elDiv, this, ["baseURL", "modelInfo","headers", "bStream", "bTools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "toolFetchProxyUrl", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings/Info (devel-tools-console document[gMe])") + }).catch(err=>console.log(`WARN:ShowInfo:${err}`)) } /** diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index b16a19ba77139..23ec676315f40 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -303,3 +303,26 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, fRefiner=un } } } + + +/** + * Show the specified properties and their values wrt the given object. + * @param {HTMLElement | undefined} elDiv + * @param {any} oObj + * @param {Array} lProps + * @param {string} sLegend + */ +export function ui_show_obj_props_info(elDiv, oObj, lProps, sLegend) { + let p = el_create_append_p(`${sLegend}`, elDiv); + p.className = "role-system"; + + for (const k of lProps) { + let val = oObj[k]; + let vtype = typeof(val) + if (vtype != 'object') { + el_create_append_p(`${k}:${oObj[k]}`, elDiv) + } else { + el_create_append_p(`${k}:${JSON.stringify(oObj[k], null, " - ")}`, elDiv); + } + } +} From 7f3ac59532fca971e0080b912ef445ecf0963e74 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 02:14:17 +0530 Subject: [PATCH 106/187] SimpleChatTC:ShowInfo: Make logic recursive, avoid JSON.stringify --- tools/server/public_simplechat/ui.mjs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index 23ec676315f40..b051c6a71339b 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -312,17 +312,21 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, fRefiner=un * @param {Array} lProps * @param {string} sLegend */ -export function ui_show_obj_props_info(elDiv, oObj, lProps, sLegend) { +export function ui_show_obj_props_info(elDiv, oObj, lProps, sLegend, sOffset="") { let p = el_create_append_p(`${sLegend}`, elDiv); - p.className = "role-system"; + if (sOffset.length == 0) { + p.className = "role-system"; + } for (const k of lProps) { + let kPrint = `${sOffset}${k}` let val = oObj[k]; let vtype = typeof(val) if (vtype != 'object') { - el_create_append_p(`${k}:${oObj[k]}`, elDiv) + el_create_append_p(`${kPrint}:${oObj[k]}`, elDiv) } else { - el_create_append_p(`${k}:${JSON.stringify(oObj[k], null, " - ")}`, elDiv); + ui_show_obj_props_info(elDiv, val, Object.keys(val), kPrint, `>${sOffset}`) + //el_create_append_p(`${k}:${JSON.stringify(oObj[k], null, " - ")}`, elDiv); } } } From e00f0af1cc3d9e6009f7a04f255f101e742e4e5b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 18:53:49 +0530 Subject: [PATCH 107/187] SimpleChatTC:ShowObjPropsInfo: Use sections to indicate relations Also create a top level div wrt whole. And allow class to be specified for the same as well as the top level legend, optionally --- tools/server/public_simplechat/simplechat.js | 2 +- tools/server/public_simplechat/ui.mjs | 32 +++++++++++++++----- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 1ea8dd62e29c7..c352be0300fd7 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1091,7 +1091,7 @@ class Me { modelPath: json["model_path"], ctxSize: json["default_generation_settings"]["n_ctx"] } - ui.ui_show_obj_props_info(elDiv, this, ["baseURL", "modelInfo","headers", "bStream", "bTools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "toolFetchProxyUrl", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings/Info (devel-tools-console document[gMe])") + ui.ui_show_obj_props_info(elDiv, this, ["baseURL", "modelInfo","headers", "bStream", "bTools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "toolFetchProxyUrl", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings/Info (devel-tools-console document[gMe])", "", { legend: 'role-system' }) }).catch(err=>console.log(`WARN:ShowInfo:${err}`)) } diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index b051c6a71339b..24756c441461d 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -306,26 +306,44 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, fRefiner=un /** - * Show the specified properties and their values wrt the given object. - * @param {HTMLElement | undefined} elDiv + * Show the specified properties and their values wrt the given object, + * with in the elParent provided. + * @param {HTMLDivElement | HTMLElement} elParent * @param {any} oObj * @param {Array} lProps * @param {string} sLegend + * @param {string} sOffset - can be used to prefix each of the prop entries + * @param {any | undefined} dClassNames - can specify class for top level div and legend */ -export function ui_show_obj_props_info(elDiv, oObj, lProps, sLegend, sOffset="") { - let p = el_create_append_p(`${sLegend}`, elDiv); +export function ui_show_obj_props_info(elParent, oObj, lProps, sLegend, sOffset="", dClassNames=undefined) { if (sOffset.length == 0) { - p.className = "role-system"; + let div = document.createElement("div"); + div.classList.add(`DivObjPropsInfoL${sOffset.length}`) + elParent.appendChild(div) + elParent = div } + let elPLegend = el_create_append_p(sLegend, elParent) + if (dClassNames) { + if (dClassNames['div']) { + elParent.className = dClassNames['div'] + } + if (dClassNames['legend']) { + elPLegend.className = dClassNames['legend'] + } + } + let elS = document.createElement("section"); + elS.classList.add(`SectionObjPropsInfoL${sOffset.length}`) + elParent.appendChild(elPLegend); + elParent.appendChild(elS); for (const k of lProps) { let kPrint = `${sOffset}${k}` let val = oObj[k]; let vtype = typeof(val) if (vtype != 'object') { - el_create_append_p(`${kPrint}:${oObj[k]}`, elDiv) + el_create_append_p(`${kPrint}: ${oObj[k]}`, elS) } else { - ui_show_obj_props_info(elDiv, val, Object.keys(val), kPrint, `>${sOffset}`) + ui_show_obj_props_info(elS, val, Object.keys(val), kPrint, `>${sOffset}`) //el_create_append_p(`${k}:${JSON.stringify(oObj[k], null, " - ")}`, elDiv); } } From ba0f0e33567b27366ebfa0f27fda18263e7cacea Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 19:37:18 +0530 Subject: [PATCH 108/187] SimpleChatTC:ShowInfo: Allow showing minimal info set, if needed --- tools/server/public_simplechat/simplechat.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index c352be0300fd7..03791f4455fb4 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -399,7 +399,11 @@ class SimpleChat { } /** - * Show the contents in the specified div + * Show the chat contents in the specified div. + * If requested to clear prev stuff and inturn no chat content then show + * * usage info + * * option to load prev saved chat if any + * * as well as settings/info. * @param {HTMLDivElement} div * @param {boolean} bClear * @param {boolean} bShowInfoAll @@ -1086,12 +1090,16 @@ class Me { * @param {boolean} bAll */ show_info(elDiv, bAll=false) { + let props = ["baseURL", "modelInfo","headers", "bStream", "bTools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "toolFetchProxyUrl", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"]; + if (!bAll) { + props = [ "baseURL", "modelInfo", "headers", "bStream", "bTools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt" ]; + } fetch(`${this.baseURL}/props`).then(resp=>resp.json()).then(json=>{ this.modelInfo = { modelPath: json["model_path"], ctxSize: json["default_generation_settings"]["n_ctx"] } - ui.ui_show_obj_props_info(elDiv, this, ["baseURL", "modelInfo","headers", "bStream", "bTools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "toolFetchProxyUrl", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings/Info (devel-tools-console document[gMe])", "", { legend: 'role-system' }) + ui.ui_show_obj_props_info(elDiv, this, props, "Settings/Info (devel-tools-console document[gMe])", "", { legend: 'role-system' }) }).catch(err=>console.log(`WARN:ShowInfo:${err}`)) } From bd3e9bd84536d4466fd7763464858db6a5e128dc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 19:57:53 +0530 Subject: [PATCH 109/187] SimpleChatTC:ShowInfo:Clean up layout of showing of props data Also ensure when switching between sessions, the full set of props info is shown. --- tools/server/public_simplechat/simplechat.css | 10 ++++++++++ tools/server/public_simplechat/simplechat.js | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.css b/tools/server/public_simplechat/simplechat.css index d4755074b77c5..98e88d99fb4a7 100644 --- a/tools/server/public_simplechat/simplechat.css +++ b/tools/server/public_simplechat/simplechat.css @@ -69,10 +69,20 @@ button { padding-inline-start: 2vw; } + +.DivObjPropsInfoL0 { + margin: 0%; +} +[class^=SectionObjPropsInfoL] { + margin-left: 2vmin; +} + + * { margin: 0.6vmin; } + @media print { #fullbody { diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 03791f4455fb4..20a13c65bd498 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1000,7 +1000,7 @@ class MultiChatUI { } this.elInSystem.value = chat.get_system_latest().ns.content; this.elInUser.value = ""; - chat.show(this.elDivChat); + chat.show(this.elDivChat, true, true); this.elInUser.focus(); this.curChatId = chatId; console.log(`INFO:SimpleChat:MCUI:HandleSessionSwitch:${chatId} entered...`); From f3a1fc7e373cd78d0a403235495669f08ef1cb0e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 21:44:30 +0530 Subject: [PATCH 110/187] SimpleChatTC:Cleanup: Move bTools and toolFetchProxyUrl into tools Also update the readme wrt same and related --- tools/server/public_simplechat/readme.md | 17 +++++++++++++---- tools/server/public_simplechat/simplechat.js | 14 ++++++++------ tools/server/public_simplechat/tooljs.mjs | 8 ++++---- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 63b9a43eadf43..4ed51a0f6d0ad 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -74,10 +74,15 @@ remember to * pass --jinja to llama-server to enable tool calling support from the server ai engine end. -* enable bTools in the settings page of the client side gui. +* set tools.enabled to true in the settings page of the client side gui. * use a GenAi/LLM model which supports tool calling. +* if fetch web url / page tool call is needed, remember to run the bundled local.tools/simpleproxy.py helper + + * remember that this is a relatively dumb proxy logic along with optional stripping of scripts/styles/headers/footers/..., + Be careful if trying to fetch web pages, and use it only with known safe sites. + ### using the front end Open this simple web front end from your local browser @@ -184,9 +189,13 @@ It is attached to the document object. Some of these can also be updated using t inturn the machine goes into power saving mode or so, the platform may stop network connection, leading to exception. - bTools - control whether tool calling is enabled or not + tools - contains controls related to tool calling + + enabled - control whether tool calling is enabled or not + + remember to enable this only for GenAi/LLM models which support tool/function calling. - remember to enable this only for GenAi/LLM models which support tool/function calling. + fetchProxyUrl - specify the address for the running instance of bundled local.tools/simpleproxy.py the builtin tools' meta data is sent to the ai model in the requests sent to it. @@ -351,7 +360,7 @@ browser js runtime environment. Depending on the path specified wrt the proxy se (and not urlraw), it additionally tries to convert html content into equivalent text to some extent in a simple minded manner by dropping head block as well as all scripts/styles/footers/headers/nav. May add support for white list of allowed sites to access or so. -* the logic does a simple check to see if the bundled simpleproxy is running at specified proxyUrl +* the logic does a simple check to see if the bundled simpleproxy is running at specified fetchProxyUrl before enabling fetch web related tool calls. * The bundled simple proxy can be found at * tools/server/public_simplechat/local.tools/simpleproxy.py diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 20a13c65bd498..9a2321690a108 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -462,7 +462,7 @@ class SimpleChat { if (gMe.bStream) { obj["stream"] = true; } - if (gMe.bTools) { + if (gMe.tools.enabled) { obj["tools"] = tools.meta(); } return JSON.stringify(obj); @@ -1016,7 +1016,10 @@ class Me { this.defaultChatIds = [ "Default", "Other" ]; this.multiChat = new MultiChatUI(); this.bStream = true; - this.bTools = false; + this.tools = { + enabled: false, + fetchProxyUrl: "http://127.0.0.1:3128" + }; this.bCompletionFreshChatAlways = true; this.bCompletionInsertStandardRolePrefix = false; this.bTrimGarbage = true; @@ -1049,7 +1052,6 @@ class Me { //"frequency_penalty": 1.2, //"presence_penalty": 1.2, }; - this.toolFetchProxyUrl = "http://127.0.0.1:3128" } /** @@ -1090,9 +1092,9 @@ class Me { * @param {boolean} bAll */ show_info(elDiv, bAll=false) { - let props = ["baseURL", "modelInfo","headers", "bStream", "bTools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "toolFetchProxyUrl", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"]; + let props = ["baseURL", "modelInfo","headers", "bStream", "tools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"]; if (!bAll) { - props = [ "baseURL", "modelInfo", "headers", "bStream", "bTools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt" ]; + props = [ "baseURL", "modelInfo", "headers", "bStream", "tools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt" ]; } fetch(`${this.baseURL}/props`).then(resp=>resp.json()).then(json=>{ this.modelInfo = { @@ -1108,7 +1110,7 @@ class Me { * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "bTools", "apiRequestOptions", "TRAPME-apiEP", "TRAPME-iRecentUserMsgCnt", "toolFetchProxyUrl", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ + ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "tools", "apiRequestOptions", "TRAPME-apiEP", "TRAPME-iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ if (prop == "headers:Authorization") { // @ts-ignore elProp.placeholder = "Bearer OPENAI_API_KEY"; diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 7a217e2ef6012..3943ca453488b 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -116,7 +116,7 @@ let fetchweburlraw_meta = { function fetchweburlraw_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { // @ts-ignore - let newUrl = `${document['gMe'].toolFetchProxyUrl}/urlraw?url=${encodeURIComponent(obj.url)}` + let newUrl = `${document['gMe'].tools.fetchProxyUrl}/urlraw?url=${encodeURIComponent(obj.url)}` fetch(newUrl).then(resp => { if (!resp.ok) { throw new Error(`${resp.status}:${resp.statusText}`); @@ -138,7 +138,7 @@ function fetchweburlraw_run(toolcallid, toolname, obj) { */ async function fetchweburlraw_setup(tcs) { // @ts-ignore - let got = await fetch(`${document["gMe"].toolFetchProxyUrl}/aum?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ + let got = await fetch(`${document["gMe"].tools.fetchProxyUrl}/aum?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ if (resp.statusText != 'bharatavarshe') { console.log("WARN:ToolJS:FetchWebUrlRaw:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") return @@ -190,7 +190,7 @@ let fetchweburltext_meta = { function fetchweburltext_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { // @ts-ignore - let newUrl = `${document['gMe'].toolFetchProxyUrl}/urltext?url=${encodeURIComponent(obj.url)}` + let newUrl = `${document['gMe'].tools.fetchProxyUrl}/urltext?url=${encodeURIComponent(obj.url)}` fetch(newUrl).then(resp => { if (!resp.ok) { throw new Error(`${resp.status}:${resp.statusText}`); @@ -212,7 +212,7 @@ function fetchweburltext_run(toolcallid, toolname, obj) { */ async function fetchweburltext_setup(tcs) { // @ts-ignore - let got = await fetch(`${document["gMe"].toolFetchProxyUrl}/aum?url=jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ + let got = await fetch(`${document["gMe"].tools.fetchProxyUrl}/aum?url=jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ if (resp.statusText != 'bharatavarshe') { console.log("WARN:ToolJS:FetchWebUrlText:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") return From fb36d1e0d2d85590047c6eb2a35bf77a30b68770 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 21:55:23 +0530 Subject: [PATCH 111/187] SimpleChatTC:Cleanup:EditObjProps: rename vars followingConvention Part 1 - add el prefix wrt the element handle related vars --- tools/server/public_simplechat/ui.mjs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index 24756c441461d..3317c6c82a5bb 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -258,16 +258,16 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, fRefiner=un "string": "text", "number": "number", }; - let fs = document.createElement("fieldset"); - let legend = document.createElement("legend"); - legend.innerText = sLegend; - fs.appendChild(legend); - elDiv.appendChild(fs); + let elFS = document.createElement("fieldset"); + let elLegend = document.createElement("legend"); + elLegend.innerText = sLegend; + elFS.appendChild(elLegend); + elDiv.appendChild(elFS); for(const k of lProps) { if (sTrapTag) { if (k.startsWith(sTrapTag)) { if (fTrapper) { - fTrapper(k, fs) + fTrapper(k, elFS) } continue } @@ -284,7 +284,7 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, fRefiner=un if (fRefiner) { fRefiner(k, inp.el) } - fs.appendChild(inp.div); + elFS.appendChild(inp.div); } else if (type == "boolean") { let bbtn = el_creatediv_boolbutton(`Set{k}`, k, {true: "true", false: "false"}, val, (userVal)=>{ oObj[k] = userVal; @@ -292,9 +292,9 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, fRefiner=un if (fRefiner) { fRefiner(k, bbtn.el) } - fs.appendChild(bbtn.div); + elFS.appendChild(bbtn.div); } else if (type == "object") { - ui_show_obj_props_edit(fs, val, Object.keys(val), k, (prop, elProp)=>{ + ui_show_obj_props_edit(elFS, val, Object.keys(val), k, (prop, elProp)=>{ if (fRefiner) { let theProp = `${k}:${prop}` fRefiner(theProp, elProp) From d34dfc1056c1995959b8f66c0edb694d4cdbe35b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 22:00:12 +0530 Subject: [PATCH 112/187] SimpleChatTC:Cleanup:Rename func arg to match semantic better --- tools/server/public_simplechat/ui.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index 3317c6c82a5bb..761e0571c50cc 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -245,7 +245,7 @@ export function el_creatediv_input(id, label, type, defaultValue, cb, className= * * prefix the prop name in lProps with sTrapTag * * fTrapper will be called with the parent ui element * into which the new ui elements created for editting the prop, if any, should be attached - * @param {HTMLDivElement|HTMLFieldSetElement} elDiv + * @param {HTMLDivElement|HTMLFieldSetElement} elParent * @param {any} oObj * @param {Array} lProps * @param {string} sLegend @@ -253,7 +253,7 @@ export function el_creatediv_input(id, label, type, defaultValue, cb, className= * @param {string | undefined} sTrapTag * @param {((tagPlusProp: string, elParent: HTMLFieldSetElement)=>void) | undefined} fTrapper */ -export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, fRefiner=undefined, sTrapTag=undefined, fTrapper=undefined) { +export function ui_show_obj_props_edit(elParent, oObj, lProps, sLegend, fRefiner=undefined, sTrapTag=undefined, fTrapper=undefined) { let typeDict = { "string": "text", "number": "number", @@ -262,7 +262,7 @@ export function ui_show_obj_props_edit(elDiv, oObj, lProps, sLegend, fRefiner=un let elLegend = document.createElement("legend"); elLegend.innerText = sLegend; elFS.appendChild(elLegend); - elDiv.appendChild(elFS); + elParent.appendChild(elFS); for(const k of lProps) { if (sTrapTag) { if (k.startsWith(sTrapTag)) { @@ -344,7 +344,7 @@ export function ui_show_obj_props_info(elParent, oObj, lProps, sLegend, sOffset= el_create_append_p(`${kPrint}: ${oObj[k]}`, elS) } else { ui_show_obj_props_info(elS, val, Object.keys(val), kPrint, `>${sOffset}`) - //el_create_append_p(`${k}:${JSON.stringify(oObj[k], null, " - ")}`, elDiv); + //el_create_append_p(`${k}:${JSON.stringify(oObj[k], null, " - ")}`, elS); } } } From dcbe8374168839cac091e640f278ffb92a1ed6fa Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 22:15:58 +0530 Subject: [PATCH 113/187] SimpleChatTC:ShowObjPropsEdit:Any depth trapping of ui setup Maintain the current property hierarchy to its root over recursive calls. Allow callers to specify the props to be trapped using the prop hierarchy. Pass the prop hierarchy to the fTrapper. This should allow one to trap any prop wrt its editing ui setup, irrespective of whether it is a prop of the main object passed, or a member of a child prop of the main object passed or so ... Update the setting up of ChatHistoryInCtxt and ApiEndPoint to follow the new semantic/flow. --- tools/server/public_simplechat/simplechat.js | 8 ++++---- tools/server/public_simplechat/ui.mjs | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 9a2321690a108..91e6b4647c52f 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1110,20 +1110,20 @@ class Me { * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - ui.ui_show_obj_props_edit(elDiv, this, ["baseURL", "headers", "bStream", "tools", "apiRequestOptions", "TRAPME-apiEP", "TRAPME-iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ + ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "bStream", "tools", "apiRequestOptions", "TRAPME-apiEP", "TRAPME-iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ if (prop == "headers:Authorization") { // @ts-ignore elProp.placeholder = "Bearer OPENAI_API_KEY"; } - }, "TRAPME-", (tag, elParent)=>{ - if (tag == "TRAPME-apiEP") { + }, ["apiEP", "iRecentUserMsgCnt"], (propWithPath, prop, elParent)=>{ + if (propWithPath == "apiEP") { let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{ // @ts-ignore this.apiEP = ApiEP.Type[val]; }); elParent.appendChild(sel.div); } - if (tag == "TRAPME-iRecentUserMsgCnt") { + if (propWithPath == "iRecentUserMsgCnt") { let sel = ui.el_creatediv_select("SetChatHistoryInCtxt", "ChatHistoryInCtxt", this.sRecentUserMsgCnt, this.iRecentUserMsgCnt, (val)=>{ this.iRecentUserMsgCnt = this.sRecentUserMsgCnt[val]; }); diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index 761e0571c50cc..0497924581d13 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -246,14 +246,15 @@ export function el_creatediv_input(id, label, type, defaultValue, cb, className= * * fTrapper will be called with the parent ui element * into which the new ui elements created for editting the prop, if any, should be attached * @param {HTMLDivElement|HTMLFieldSetElement} elParent + * @param {string} propsTreeRoot * @param {any} oObj * @param {Array} lProps * @param {string} sLegend * @param {((prop:string, elProp: HTMLElement)=>void)| undefined} fRefiner - * @param {string | undefined} sTrapTag - * @param {((tagPlusProp: string, elParent: HTMLFieldSetElement)=>void) | undefined} fTrapper + * @param {Array | undefined} lTrapThese + * @param {((propWithPath: string, prop: string, elParent: HTMLFieldSetElement)=>void) | undefined} fTrapper */ -export function ui_show_obj_props_edit(elParent, oObj, lProps, sLegend, fRefiner=undefined, sTrapTag=undefined, fTrapper=undefined) { +export function ui_show_obj_props_edit(elParent, propsTreeRoot, oObj, lProps, sLegend, fRefiner=undefined, lTrapThese=undefined, fTrapper=undefined) { let typeDict = { "string": "text", "number": "number", @@ -264,10 +265,11 @@ export function ui_show_obj_props_edit(elParent, oObj, lProps, sLegend, fRefiner elFS.appendChild(elLegend); elParent.appendChild(elFS); for(const k of lProps) { - if (sTrapTag) { - if (k.startsWith(sTrapTag)) { + let propsTreeRootNew = `${propsTreeRoot}:${k}` + if (lTrapThese) { + if (propsTreeRootNew in lTrapThese) { if (fTrapper) { - fTrapper(k, elFS) + fTrapper(propsTreeRootNew, k, elFS) } continue } @@ -294,12 +296,12 @@ export function ui_show_obj_props_edit(elParent, oObj, lProps, sLegend, fRefiner } elFS.appendChild(bbtn.div); } else if (type == "object") { - ui_show_obj_props_edit(elFS, val, Object.keys(val), k, (prop, elProp)=>{ + ui_show_obj_props_edit(elFS, propsTreeRootNew, val, Object.keys(val), k, (prop, elProp)=>{ if (fRefiner) { let theProp = `${k}:${prop}` fRefiner(theProp, elProp) } - }) + }, lTrapThese, fTrapper) } } } From 064c42341dbcdac86bc6b6dd7c7125a9e23c0220 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 23:06:44 +0530 Subject: [PATCH 114/187] SimpleChatTC:ShowObjPropsEdit:Any depth trapping of ui setup - t2 Fix up the oversights wrt any depth trapping flow Remember to start the propWithTree being checked/trapped with : to indicate the root of the prop hierarchy and also use : as sep between the elements of the props hierarchy tree Also had forgotten about the goof up possible with using in in a condition statement to check for array to contain a entry of interest in JS, fixed it now. --- tools/server/public_simplechat/simplechat.js | 8 ++++---- tools/server/public_simplechat/ui.mjs | 10 +++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 91e6b4647c52f..daa5b5052f95e 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1110,20 +1110,20 @@ class Me { * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "bStream", "tools", "apiRequestOptions", "TRAPME-apiEP", "TRAPME-iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ + ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "bStream", "tools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ if (prop == "headers:Authorization") { // @ts-ignore elProp.placeholder = "Bearer OPENAI_API_KEY"; } - }, ["apiEP", "iRecentUserMsgCnt"], (propWithPath, prop, elParent)=>{ - if (propWithPath == "apiEP") { + }, [":apiEP", ":iRecentUserMsgCnt"], (propWithPath, prop, elParent)=>{ + if (propWithPath == ":apiEP") { let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{ // @ts-ignore this.apiEP = ApiEP.Type[val]; }); elParent.appendChild(sel.div); } - if (propWithPath == "iRecentUserMsgCnt") { + if (propWithPath == ":iRecentUserMsgCnt") { let sel = ui.el_creatediv_select("SetChatHistoryInCtxt", "ChatHistoryInCtxt", this.sRecentUserMsgCnt, this.iRecentUserMsgCnt, (val)=>{ this.iRecentUserMsgCnt = this.sRecentUserMsgCnt[val]; }); diff --git a/tools/server/public_simplechat/ui.mjs b/tools/server/public_simplechat/ui.mjs index 0497924581d13..fb447d3e6e7e2 100644 --- a/tools/server/public_simplechat/ui.mjs +++ b/tools/server/public_simplechat/ui.mjs @@ -242,9 +242,13 @@ export function el_creatediv_input(id, label, type, defaultValue, cb, className= * The fRefiner callback even helps work with Obj with-in Obj scenarios. * * For some reason if caller wants to handle certain properties on their own - * * prefix the prop name in lProps with sTrapTag + * * specify the prop name of interest along with its prop-tree-hierarchy in lTrapThese + * * always start with : when ever refering to propWithPath, + * as it indirectly signifies root of properties tree + * * remember to seperate the properties tree hierarchy members using : * * fTrapper will be called with the parent ui element - * into which the new ui elements created for editting the prop, if any, should be attached + * into which the new ui elements created for editting the prop, if any, should be attached, + * along with the current prop of interest and its full propWithPath representation. * @param {HTMLDivElement|HTMLFieldSetElement} elParent * @param {string} propsTreeRoot * @param {any} oObj @@ -267,7 +271,7 @@ export function ui_show_obj_props_edit(elParent, propsTreeRoot, oObj, lProps, sL for(const k of lProps) { let propsTreeRootNew = `${propsTreeRoot}:${k}` if (lTrapThese) { - if (propsTreeRootNew in lTrapThese) { + if (lTrapThese.indexOf(propsTreeRootNew) != -1) { if (fTrapper) { fTrapper(propsTreeRootNew, k, elFS) } From 99ae47627ada19eec229cdb610ef6287c4a08379 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 23:37:33 +0530 Subject: [PATCH 115/187] SimpleChatTC:Cleanup:ChatProps: Move bStream into it --- tools/server/public_simplechat/readme.md | 18 ++++++++++-------- tools/server/public_simplechat/simplechat.js | 14 ++++++++------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 4ed51a0f6d0ad..418a765972587 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -177,17 +177,19 @@ It is attached to the document object. Some of these can also be updated using t baseURL - the domain-name/ip-address and inturn the port to send the request. - bStream - control between oneshot-at-end and live-stream-as-its-generated collating and showing - of the generated response. + chatProps - maintain a set of properties which manipulate chatting with ai engine - the logic assumes that the text sent from the server follows utf-8 encoding. + stream - control between oneshot-at-end and live-stream-as-its-generated collating and showing + of the generated response. - in streaming mode - if there is any exception, the logic traps the same and tries to ensure - that text generated till then is not lost. + the logic assumes that the text sent from the server follows utf-8 encoding. - if a very long text is being generated, which leads to no user interaction for sometime and - inturn the machine goes into power saving mode or so, the platform may stop network connection, - leading to exception. + in streaming mode - if there is any exception, the logic traps the same and tries to ensure + that text generated till then is not lost. + + if a very long text is being generated, which leads to no user interaction for sometime and + inturn the machine goes into power saving mode or so, the platform may stop network connection, + leading to exception. tools - contains controls related to tool calling diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index daa5b5052f95e..8696227193c28 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -459,7 +459,7 @@ class SimpleChat { for(let k in gMe.apiRequestOptions) { obj[k] = gMe.apiRequestOptions[k]; } - if (gMe.bStream) { + if (gMe.chatProps.stream) { obj["stream"] = true; } if (gMe.tools.enabled) { @@ -622,7 +622,7 @@ class SimpleChat { */ async handle_response(resp, apiEP, elDiv) { let theResp = null; - if (gMe.bStream) { + if (gMe.chatProps.stream) { try { theResp = await this.handle_response_multipart(resp, apiEP, elDiv); this.latestResponse.clear(); @@ -1015,11 +1015,13 @@ class Me { this.baseURL = "http://127.0.0.1:8080"; this.defaultChatIds = [ "Default", "Other" ]; this.multiChat = new MultiChatUI(); - this.bStream = true; this.tools = { enabled: false, fetchProxyUrl: "http://127.0.0.1:3128" }; + this.chatProps = { + stream: true, + } this.bCompletionFreshChatAlways = true; this.bCompletionInsertStandardRolePrefix = false; this.bTrimGarbage = true; @@ -1092,9 +1094,9 @@ class Me { * @param {boolean} bAll */ show_info(elDiv, bAll=false) { - let props = ["baseURL", "modelInfo","headers", "bStream", "tools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"]; + let props = ["baseURL", "modelInfo","headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"]; if (!bAll) { - props = [ "baseURL", "modelInfo", "headers", "bStream", "tools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt" ]; + props = [ "baseURL", "modelInfo", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "iRecentUserMsgCnt" ]; } fetch(`${this.baseURL}/props`).then(resp=>resp.json()).then(json=>{ this.modelInfo = { @@ -1110,7 +1112,7 @@ class Me { * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "bStream", "tools", "apiRequestOptions", "apiEP", "iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ + ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ if (prop == "headers:Authorization") { // @ts-ignore elProp.placeholder = "Bearer OPENAI_API_KEY"; From fc4d708f4715bc37c73e69ec30576497314fb7a9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 23:44:39 +0530 Subject: [PATCH 116/187] SimpleChatTC:Cleanup:ChatProps: iRecentUserMsgCnt Update Me class Update show settings Update show props info Update readme --- tools/server/public_simplechat/readme.md | 35 ++++++++++---------- tools/server/public_simplechat/simplechat.js | 24 +++++++------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 418a765972587..8e6c180738dda 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -191,6 +191,17 @@ It is attached to the document object. Some of these can also be updated using t inturn the machine goes into power saving mode or so, the platform may stop network connection, leading to exception. + iRecentUserMsgCnt - a simple minded SlidingWindow to limit context window load at Ai Model end. + This is set to 10 by default. So in addition to latest system message, last/latest iRecentUserMsgCnt + user messages after the latest system prompt and its responses from the ai model will be sent + to the ai-model, when querying for a new response. Note that if enabled, only user messages after + the latest system message/prompt will be considered. + + This specified sliding window user message count also includes the latest user query. + <0 : Send entire chat history to server + 0 : Send only the system message if any to the server + >0 : Send the latest chat history from the latest system prompt, limited to specified cnt. + tools - contains controls related to tool calling enabled - control whether tool calling is enabled or not @@ -250,22 +261,12 @@ It is attached to the document object. Some of these can also be updated using t Content-Type is set to application/json. Additionally Authorization entry is provided, which can be set if needed using the settings ui. - iRecentUserMsgCnt - a simple minded SlidingWindow to limit context window load at Ai Model end. - This is set to 10 by default. So in addition to latest system message, last/latest iRecentUserMsgCnt - user messages after the latest system prompt and its responses from the ai model will be sent - to the ai-model, when querying for a new response. Note that if enabled, only user messages after - the latest system message/prompt will be considered. - - This specified sliding window user message count also includes the latest user query. - <0 : Send entire chat history to server - 0 : Send only the system message if any to the server - >0 : Send the latest chat history from the latest system prompt, limited to specified cnt. - -By using gMe's iRecentUserMsgCnt and apiRequestOptions.max_tokens/n_predict one can try to control -the implications of loading of the ai-model's context window by chat history, wrt chat response to -some extent in a simple crude way. You may also want to control the context size enabled when the -server loads ai-model, on the server end. +By using gMe's chatProps.iRecentUserMsgCnt and apiRequestOptions.max_tokens/n_predict one can try to +control the implications of loading of the ai-model's context window by chat history, wrt chat response +to some extent in a simple crude way. You may also want to control the context size enabled when the +server loads ai-model, on the server end. One can look at the current context size set on the server +end by looking at the settings/info block shown when ever one switches-to/is-shown a new session. Sometimes the browser may be stuborn with caching of the file, so your updates to html/css/js @@ -288,8 +289,8 @@ the system prompt, anytime during the conversation or only at the beginning. By default things are setup to try and make the user experience a bit better, if possible. However a developer when testing the server of ai-model may want to change these value. -Using iRecentUserMsgCnt reduce chat history context sent to the server/ai-model to be -just the system-prompt, prev-user-request-and-ai-response and cur-user-request, instead of +Using chatProps.iRecentUserMsgCnt reduce chat history context sent to the server/ai-model to be +just the system-prompt, few prev-user-requests-and-ai-responses and cur-user-request, instead of full chat history. This way if there is any response with garbage/repeatation, it doesnt mess with things beyond the next question/request/query, in some ways. The trim garbage option also tries to help avoid issues with garbage in the context to an extent. diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 8696227193c28..05e619032182c 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -413,7 +413,7 @@ class SimpleChat { div.replaceChildren(); } let last = undefined; - for(const x of this.recent_chat(gMe.iRecentUserMsgCnt)) { + for(const x of this.recent_chat(gMe.chatProps.iRecentUserMsgCnt)) { let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, div); entry.className = `role-${x.ns.role}`; last = entry; @@ -473,7 +473,7 @@ class SimpleChat { */ request_messages_jsonstr() { let req = { - messages: this.recent_chat_ns(gMe.iRecentUserMsgCnt), + messages: this.recent_chat_ns(gMe.chatProps.iRecentUserMsgCnt), } return this.request_jsonstr_extend(req); } @@ -485,7 +485,7 @@ class SimpleChat { request_prompt_jsonstr(bInsertStandardRolePrefix) { let prompt = ""; let iCnt = 0; - for(const msg of this.recent_chat(gMe.iRecentUserMsgCnt)) { + for(const msg of this.recent_chat(gMe.chatProps.iRecentUserMsgCnt)) { iCnt += 1; if (iCnt > 1) { prompt += "\n"; @@ -1021,11 +1021,11 @@ class Me { }; this.chatProps = { stream: true, - } + iRecentUserMsgCnt: 10, + }; this.bCompletionFreshChatAlways = true; this.bCompletionInsertStandardRolePrefix = false; this.bTrimGarbage = true; - this.iRecentUserMsgCnt = 10; /** @type {Object} */ this.sRecentUserMsgCnt = { "Full": -1, @@ -1094,9 +1094,9 @@ class Me { * @param {boolean} bAll */ show_info(elDiv, bAll=false) { - let props = ["baseURL", "modelInfo","headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"]; + let props = ["baseURL", "modelInfo","headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"]; if (!bAll) { - props = [ "baseURL", "modelInfo", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "iRecentUserMsgCnt" ]; + props = [ "baseURL", "modelInfo", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps" ]; } fetch(`${this.baseURL}/props`).then(resp=>resp.json()).then(json=>{ this.modelInfo = { @@ -1112,12 +1112,12 @@ class Me { * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "iRecentUserMsgCnt", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ + ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ if (prop == "headers:Authorization") { // @ts-ignore elProp.placeholder = "Bearer OPENAI_API_KEY"; } - }, [":apiEP", ":iRecentUserMsgCnt"], (propWithPath, prop, elParent)=>{ + }, [":apiEP", ":chatProps:iRecentUserMsgCnt"], (propWithPath, prop, elParent)=>{ if (propWithPath == ":apiEP") { let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{ // @ts-ignore @@ -1125,9 +1125,9 @@ class Me { }); elParent.appendChild(sel.div); } - if (propWithPath == ":iRecentUserMsgCnt") { - let sel = ui.el_creatediv_select("SetChatHistoryInCtxt", "ChatHistoryInCtxt", this.sRecentUserMsgCnt, this.iRecentUserMsgCnt, (val)=>{ - this.iRecentUserMsgCnt = this.sRecentUserMsgCnt[val]; + if (propWithPath == ":chatProps:iRecentUserMsgCnt") { + let sel = ui.el_creatediv_select("SetChatHistoryInCtxt", "ChatHistoryInCtxt", this.sRecentUserMsgCnt, this.chatProps.iRecentUserMsgCnt, (val)=>{ + this.chatProps.iRecentUserMsgCnt = this.sRecentUserMsgCnt[val]; }); elParent.appendChild(sel.div); } From a9153f4644ed7f9225edb32e954f28f56b9c8ad4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 00:07:22 +0530 Subject: [PATCH 117/187] SimpleChatTC:Cleanup:ChatProps: bCompletionFreshChatAlways Moved into Me.chatProps --- tools/server/public_simplechat/readme.md | 6 +++--- tools/server/public_simplechat/simplechat.js | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 8e6c180738dda..287685be0bf96 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -202,6 +202,9 @@ It is attached to the document object. Some of these can also be updated using t 0 : Send only the system message if any to the server >0 : Send the latest chat history from the latest system prompt, limited to specified cnt. + bCompletionFreshChatAlways - whether Completion mode collates complete/sliding-window history when + communicating with the server or only sends the latest user query/message. + tools - contains controls related to tool calling enabled - control whether tool calling is enabled or not @@ -221,9 +224,6 @@ It is attached to the document object. Some of these can also be updated using t apiEP - select between /completions and /chat/completions endpoint provided by the server/ai-model. - bCompletionFreshChatAlways - whether Completion mode collates complete/sliding-window history when - communicating with the server or only sends the latest user query/message. - bCompletionInsertStandardRolePrefix - whether Completion mode inserts role related prefix wrt the messages that get inserted into prompt field wrt /Completion endpoint. diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 05e619032182c..48aa6737422df 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -855,7 +855,7 @@ class MultiChatUI { // So if user wants to simulate a multi-chat based completion query, // they will have to enter the full thing, as a suitable multiline // user input/query. - if ((apiEP == ApiEP.Type.Completion) && (gMe.bCompletionFreshChatAlways)) { + if ((apiEP == ApiEP.Type.Completion) && (gMe.chatProps.bCompletionFreshChatAlways)) { chat.clear(); } @@ -1022,8 +1022,8 @@ class Me { this.chatProps = { stream: true, iRecentUserMsgCnt: 10, + bCompletionFreshChatAlways: true, }; - this.bCompletionFreshChatAlways = true; this.bCompletionInsertStandardRolePrefix = false; this.bTrimGarbage = true; /** @type {Object} */ @@ -1094,7 +1094,7 @@ class Me { * @param {boolean} bAll */ show_info(elDiv, bAll=false) { - let props = ["baseURL", "modelInfo","headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"]; + let props = ["baseURL", "modelInfo","headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage", "bCompletionInsertStandardRolePrefix"]; if (!bAll) { props = [ "baseURL", "modelInfo", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps" ]; } @@ -1112,7 +1112,7 @@ class Me { * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage", "bCompletionFreshChatAlways", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ + ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ if (prop == "headers:Authorization") { // @ts-ignore elProp.placeholder = "Bearer OPENAI_API_KEY"; From ee2ef6d9b246435a86f9b55ecfe63f4b0a6bbe96 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 00:11:14 +0530 Subject: [PATCH 118/187] SimpleChatTC:Cleanup:ChatProps: bCompletionInsertStandardRolePrefix --- tools/server/public_simplechat/readme.md | 6 +++--- tools/server/public_simplechat/simplechat.js | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 287685be0bf96..e9fc991cf3c0c 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -205,6 +205,9 @@ It is attached to the document object. Some of these can also be updated using t bCompletionFreshChatAlways - whether Completion mode collates complete/sliding-window history when communicating with the server or only sends the latest user query/message. + bCompletionInsertStandardRolePrefix - whether Completion mode inserts role related prefix wrt the + messages that get inserted into prompt field wrt /Completion endpoint. + tools - contains controls related to tool calling enabled - control whether tool calling is enabled or not @@ -224,9 +227,6 @@ It is attached to the document object. Some of these can also be updated using t apiEP - select between /completions and /chat/completions endpoint provided by the server/ai-model. - bCompletionInsertStandardRolePrefix - whether Completion mode inserts role related prefix wrt the - messages that get inserted into prompt field wrt /Completion endpoint. - bTrimGarbage - whether garbage repeatation at the end of the generated ai response, should be trimmed or left as is. If enabled, it will be trimmed so that it wont be sent back as part of subsequent chat history. At the same time the actual trimmed text is shown to the user, once diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 48aa6737422df..e26750fdd9428 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -509,7 +509,7 @@ class SimpleChat { if (apiEP == ApiEP.Type.Chat) { return this.request_messages_jsonstr(); } else { - return this.request_prompt_jsonstr(gMe.bCompletionInsertStandardRolePrefix); + return this.request_prompt_jsonstr(gMe.chatProps.bCompletionInsertStandardRolePrefix); } } @@ -1023,8 +1023,8 @@ class Me { stream: true, iRecentUserMsgCnt: 10, bCompletionFreshChatAlways: true, + bCompletionInsertStandardRolePrefix: false, }; - this.bCompletionInsertStandardRolePrefix = false; this.bTrimGarbage = true; /** @type {Object} */ this.sRecentUserMsgCnt = { @@ -1094,7 +1094,7 @@ class Me { * @param {boolean} bAll */ show_info(elDiv, bAll=false) { - let props = ["baseURL", "modelInfo","headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage", "bCompletionInsertStandardRolePrefix"]; + let props = ["baseURL", "modelInfo","headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage"]; if (!bAll) { props = [ "baseURL", "modelInfo", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps" ]; } @@ -1112,7 +1112,7 @@ class Me { * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage", "bCompletionInsertStandardRolePrefix"], "Settings", (prop, elProp)=>{ + ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage"], "Settings", (prop, elProp)=>{ if (prop == "headers:Authorization") { // @ts-ignore elProp.placeholder = "Bearer OPENAI_API_KEY"; From e3bc0dc529c16d4855cc2f6906f987ae500023a2 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 00:20:41 +0530 Subject: [PATCH 119/187] SimpleChatTC:Cleanup:ChatProps: bTrimGarbage Also remove more inner/detailed stuff from show info in not bAll mode, given that many of the previous differentiated stuff have been moved into chatProps and inturn shown for now --- tools/server/public_simplechat/readme.md | 26 ++++++++++---------- tools/server/public_simplechat/simplechat.js | 10 ++++---- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index e9fc991cf3c0c..bf2dc98b6c148 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -208,6 +208,19 @@ It is attached to the document object. Some of these can also be updated using t bCompletionInsertStandardRolePrefix - whether Completion mode inserts role related prefix wrt the messages that get inserted into prompt field wrt /Completion endpoint. + bTrimGarbage - whether garbage repeatation at the end of the generated ai response, should be + trimmed or left as is. If enabled, it will be trimmed so that it wont be sent back as part of + subsequent chat history. At the same time the actual trimmed text is shown to the user, once + when it was generated, so user can check if any useful info/data was there in the response. + + One may be able to request the ai-model to continue (wrt the last response) (if chat-history + is enabled as part of the chat-history-in-context setting), and chances are the ai-model will + continue starting from the trimmed part, thus allows long response to be recovered/continued + indirectly, in many cases. + + The histogram/freq based trimming logic is currently tuned for english language wrt its + is-it-a-alpabetic|numeral-char regex match logic. + tools - contains controls related to tool calling enabled - control whether tool calling is enabled or not @@ -227,19 +240,6 @@ It is attached to the document object. Some of these can also be updated using t apiEP - select between /completions and /chat/completions endpoint provided by the server/ai-model. - bTrimGarbage - whether garbage repeatation at the end of the generated ai response, should be - trimmed or left as is. If enabled, it will be trimmed so that it wont be sent back as part of - subsequent chat history. At the same time the actual trimmed text is shown to the user, once - when it was generated, so user can check if any useful info/data was there in the response. - - One may be able to request the ai-model to continue (wrt the last response) (if chat-history - is enabled as part of the chat-history-in-context setting), and chances are the ai-model will - continue starting from the trimmed part, thus allows long response to be recovered/continued - indirectly, in many cases. - - The histogram/freq based trimming logic is currently tuned for english language wrt its - is-it-a-alpabetic|numeral-char regex match logic. - apiRequestOptions - maintains the list of options/fields to send along with api request, irrespective of whether /chat/completions or /completions endpoint. diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index e26750fdd9428..557c5f8d00f8d 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -636,7 +636,7 @@ class SimpleChat { } else { theResp = await this.handle_response_oneshot(resp, apiEP); } - if (gMe.bTrimGarbage) { + if (gMe.chatProps.bTrimGarbage) { let origMsg = theResp.ns.content; theResp.ns.content = du.trim_garbage_at_end(origMsg); theResp.trimmedContent = origMsg.substring(theResp.ns.content.length); @@ -1024,8 +1024,8 @@ class Me { iRecentUserMsgCnt: 10, bCompletionFreshChatAlways: true, bCompletionInsertStandardRolePrefix: false, + bTrimGarbage: true, }; - this.bTrimGarbage = true; /** @type {Object} */ this.sRecentUserMsgCnt = { "Full": -1, @@ -1094,9 +1094,9 @@ class Me { * @param {boolean} bAll */ show_info(elDiv, bAll=false) { - let props = ["baseURL", "modelInfo","headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage"]; + let props = ["baseURL", "modelInfo","headers", "tools", "apiRequestOptions", "apiEP", "chatProps"]; if (!bAll) { - props = [ "baseURL", "modelInfo", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps" ]; + props = [ "baseURL", "modelInfo", "tools", "apiEP", "chatProps" ]; } fetch(`${this.baseURL}/props`).then(resp=>resp.json()).then(json=>{ this.modelInfo = { @@ -1112,7 +1112,7 @@ class Me { * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps", "bTrimGarbage"], "Settings", (prop, elProp)=>{ + ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps"], "Settings", (prop, elProp)=>{ if (prop == "headers:Authorization") { // @ts-ignore elProp.placeholder = "Bearer OPENAI_API_KEY"; From 1793872b7efdc2664cde89bb316ee1d15cfe71b7 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 08:54:15 +0530 Subject: [PATCH 120/187] SimpleChatTC:Cleanup:ChatProps: apiEP --- tools/server/public_simplechat/readme.md | 4 ++-- tools/server/public_simplechat/simplechat.js | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index bf2dc98b6c148..779a48adaf9f8 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -179,6 +179,8 @@ It is attached to the document object. Some of these can also be updated using t chatProps - maintain a set of properties which manipulate chatting with ai engine + apiEP - select between /completions and /chat/completions endpoint provided by the server/ai-model. + stream - control between oneshot-at-end and live-stream-as-its-generated collating and showing of the generated response. @@ -238,8 +240,6 @@ It is attached to the document object. Some of these can also be updated using t recommended to set iRecentUserMsgCnt to 10 or more, so that enough context is retained during chatting with ai models with tool support. - apiEP - select between /completions and /chat/completions endpoint provided by the server/ai-model. - apiRequestOptions - maintains the list of options/fields to send along with api request, irrespective of whether /chat/completions or /completions endpoint. diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 557c5f8d00f8d..b58b1f196d626 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -774,7 +774,7 @@ class MultiChatUI { if (this.elInUser.disabled) { return; } - this.handle_user_submit(this.curChatId, gMe.apiEP).catch((/** @type{Error} */reason)=>{ + this.handle_user_submit(this.curChatId, gMe.chatProps.apiEP).catch((/** @type{Error} */reason)=>{ let msg = `ERRR:SimpleChat\nMCUI:HandleUserSubmit:${this.curChatId}\n${reason.name}:${reason.message}`; console.error(msg.replace("\n", ":")); alert(msg); @@ -1020,6 +1020,7 @@ class Me { fetchProxyUrl: "http://127.0.0.1:3128" }; this.chatProps = { + apiEP: ApiEP.Type.Chat, stream: true, iRecentUserMsgCnt: 10, bCompletionFreshChatAlways: true, @@ -1035,7 +1036,6 @@ class Me { "Last4": 5, "Last9": 10, }; - this.apiEP = ApiEP.Type.Chat; /** @type {Object} */ this.headers = { "Content-Type": "application/json", @@ -1094,9 +1094,9 @@ class Me { * @param {boolean} bAll */ show_info(elDiv, bAll=false) { - let props = ["baseURL", "modelInfo","headers", "tools", "apiRequestOptions", "apiEP", "chatProps"]; + let props = ["baseURL", "modelInfo","headers", "tools", "apiRequestOptions", "chatProps"]; if (!bAll) { - props = [ "baseURL", "modelInfo", "tools", "apiEP", "chatProps" ]; + props = [ "baseURL", "modelInfo", "tools", "chatProps" ]; } fetch(`${this.baseURL}/props`).then(resp=>resp.json()).then(json=>{ this.modelInfo = { @@ -1112,16 +1112,16 @@ class Me { * @param {HTMLDivElement} elDiv */ show_settings(elDiv) { - ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "tools", "apiRequestOptions", "apiEP", "chatProps"], "Settings", (prop, elProp)=>{ + ui.ui_show_obj_props_edit(elDiv, "", this, ["baseURL", "headers", "tools", "apiRequestOptions", "chatProps"], "Settings", (prop, elProp)=>{ if (prop == "headers:Authorization") { // @ts-ignore elProp.placeholder = "Bearer OPENAI_API_KEY"; } - }, [":apiEP", ":chatProps:iRecentUserMsgCnt"], (propWithPath, prop, elParent)=>{ - if (propWithPath == ":apiEP") { - let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.apiEP, (val)=>{ + }, [":chatProps:apiEP", ":chatProps:iRecentUserMsgCnt"], (propWithPath, prop, elParent)=>{ + if (propWithPath == ":chatProps:apiEP") { + let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.chatProps.apiEP, (val)=>{ // @ts-ignore - this.apiEP = ApiEP.Type[val]; + this.chatProps.apiEP = ApiEP.Type[val]; }); elParent.appendChild(sel.div); } From 250bf08451c5ce3eeca052a3f6e04b5618432d72 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 09:43:34 +0530 Subject: [PATCH 121/187] SimpleChatTC:Tools: Show available tool names Dont allow tool names to be changed in settings page --- tools/server/public_simplechat/simplechat.js | 8 ++++++-- tools/server/public_simplechat/tools.mjs | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index b58b1f196d626..e58f6daddc86b 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1017,7 +1017,8 @@ class Me { this.multiChat = new MultiChatUI(); this.tools = { enabled: false, - fetchProxyUrl: "http://127.0.0.1:3128" + fetchProxyUrl: "http://127.0.0.1:3128", + toolNames: /** @type {Array} */([]) }; this.chatProps = { apiEP: ApiEP.Type.Chat, @@ -1117,6 +1118,9 @@ class Me { // @ts-ignore elProp.placeholder = "Bearer OPENAI_API_KEY"; } + if (prop.startsWith("tools:toolName")) { + /** @type {HTMLInputElement} */(elProp).disabled = true + } }, [":chatProps:apiEP", ":chatProps:iRecentUserMsgCnt"], (propWithPath, prop, elParent)=>{ if (propWithPath == ":chatProps:apiEP") { let sel = ui.el_creatediv_select("SetApiEP", "ApiEndPoint", ApiEP.Type, this.chatProps.apiEP, (val)=>{ @@ -1150,7 +1154,7 @@ function startme() { document["du"] = du; // @ts-ignore document["tools"] = tools; - tools.init() + tools.init().then((toolNames)=>gMe.tools.toolNames=toolNames) for (let cid of gMe.defaultChatIds) { gMe.multiChat.new_chat_session(cid); } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 14249b517a83d..2b4237258e332 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -14,11 +14,14 @@ let gToolsWorker = new Worker('./toolsworker.mjs', { type: 'module' }); */ export let tc_switch = {} -export function init() { - tjs.init(gToolsWorker).then(()=>{ +export async function init() { + return tjs.init(gToolsWorker).then(()=>{ + let toolNames = [] for (const key in tjs.tc_switch) { tc_switch[key] = tjs.tc_switch[key] + toolNames.push(key) } + return toolNames }) } From 832d613e4bb23c074b085ad810d66e244802c512 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 17:33:54 +0530 Subject: [PATCH 122/187] SimpleChatTC:SimpleProxy:Allow for loading json based config file The config entries should be named same as their equivalent cmdline argument entries but without the -- prefix --- .../local.tools/simpleproxy.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 8185382297775..d15be10fafa64 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -17,6 +17,7 @@ gMe = { '--port': 3128, + '--config': '/dev/null', 'server': None } @@ -196,6 +197,25 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.send_error(502, f"WARN:UrlFetchFailed:{exc}") +def load_config(): + """ + Allow loading of a json based config file + + The config entries should be named same as their equivalent cmdline argument + entries but without the -- prefix. They will be loaded into gMe after adding + -- prefix. + + As far as the program is concerned the entries could either come from cmdline + or from a json based config file. + """ + global gMe + import json + with open(gMe['--config']) as f: + cfg = json.load(f) + for k in cfg: + gMe[f"--{k}"] = cfg[k] + + def process_args(args: list[str]): global gMe gMe['INTERNAL.ProcessArgs.Malformed'] = [] @@ -213,10 +233,16 @@ def process_args(args: list[str]): iArg += 1 gMe[cArg] = int(args[iArg]) iArg += 1 + case '--config': + iArg += 1 + gMe[cArg] = args[iArg] + iArg += 1 + load_config() case _: gMe['INTERNAL.ProcessArgs.Unknown'].append(cArg) print(f"WARN:ProcessArgs:{iArg}:IgnoringUnknownCommand:{cArg}") iArg += 1 + print(gMe) def run(): From a415180325ef1c5075bb9c95961d21e2d2681075 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 18:02:53 +0530 Subject: [PATCH 123/187] SimpleChatTC:SimpleProxy: Update doc following python convention --- .../local.tools/simpleproxy.py | 60 ++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index d15be10fafa64..2787a4420bca3 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -2,8 +2,16 @@ # by Humans for All # # Listens on the specified port (defaults to squids 3128) -# * if a url query is got (http://localhost:3128/?url=http://site.of.interest/path/of/interest) +# * if a url query is got wrt urlraw path +# http://localhost:3128/urlraw?url=http://site.of.interest/path/of/interest # fetches the contents of the specified url and returns the same to the requester +# * if a url query is got wrt urltext path +# http://localhost:3128/urltext?url=http://site.of.interest/path/of/interest +# fetches the contents of the specified url and returns the same to the requester +# after removing html tags in general as well as contents of tags like style +# script, header, footer, nav ... +# * any request to aum path is used to respond with a predefined text response +# which can help identify this server, in a simple way. # @@ -23,23 +31,32 @@ class ProxyHandler(http.server.BaseHTTPRequestHandler): + """ + Implements the logic for handling requests sent to this server. + """ - # Common headers to include in responses from this server def send_headers_common(self): + """ + Common headers to include in responses from this server + """ self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS') self.send_header('Access-Control-Allow-Headers', '*') self.end_headers() - # overrides the SendError helper - # so that the common headers mentioned above can get added to them - # else CORS failure will be triggered by the browser on fetch from browser. def send_error(self, code: int, message: str | None = None, explain: str | None = None) -> None: + """ + Overrides the SendError helper + so that the common headers mentioned above can get added to them + else CORS failure will be triggered by the browser on fetch from browser. + """ self.send_response(code, message) self.send_headers_common() - # Handle GET requests def do_GET(self): + """ + Handle GET requests + """ print(f"DBUG:ProxyHandler:GET:{self.path}") pr = urllib.parse.urlparse(self.path) print(f"DBUG:ProxyHandler:GET:{pr}") @@ -54,14 +71,20 @@ def do_GET(self): print(f"WARN:ProxyHandler:GET:UnknownPath{pr.path}") self.send_error(400, f"WARN:UnknownPath:{pr.path}") - # Handle OPTIONS for CORS preflights (just in case from browser) def do_OPTIONS(self): + """ + Handle OPTIONS for CORS preflights (just in case from browser) + """ print(f"DBUG:ProxyHandler:OPTIONS:{self.path}") self.send_response(200) self.send_headers_common() def handle_aum(ph: ProxyHandler, pr: urllib.parse.ParseResult): + """ + Handle requests to aum path, which is used in a simple way to + verify that one is communicating with this proxy server + """ ph.send_response_only(200, "bharatavarshe") ph.send_header('Access-Control-Allow-Origin', '*') ph.end_headers() @@ -69,6 +92,9 @@ def handle_aum(ph: ProxyHandler, pr: urllib.parse.ParseResult): @dataclass(frozen=True) class UrlReqResp: + """ + Used to return result wrt urlreq helper below. + """ callOk: bool httpStatus: int httpStatusMsg: str = "" @@ -77,6 +103,9 @@ class UrlReqResp: def handle_urlreq(pr: urllib.parse.ParseResult, tag: str): + """ + Common part of the url request handling used by both urlraw and urltext. + """ print(f"DBUG:{tag}:{pr}") queryParams = urllib.parse.parse_qs(pr.query) url = queryParams['url'] @@ -114,6 +143,17 @@ def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): class TextHtmlParser(html.parser.HTMLParser): + """ + A simple minded logic used to strip html content of + * all the html tags as well as + * all the contents belonging to below predefined tags like script, style, header, ... + + NOTE: if the html content/page uses any javascript for client side manipulation/generation of + html content, that logic wont be triggered, so also such client side dynamic content wont be + got. + + This helps return a relatively clean textual representation of the html file/content being parsed. + """ def __init__(self): super().__init__() @@ -131,6 +171,9 @@ def __init__(self): self.textStripped = "" def do_capture(self): + """ + Helps decide whether to capture contents or discard them. + """ if self.inside['body'] and not (self.inside['script'] or self.inside['style'] or self.inside['header'] or self.inside['footer'] or self.inside['nav']): return True return False @@ -217,6 +260,9 @@ def load_config(): def process_args(args: list[str]): + """ + Helper to process command line arguments + """ global gMe gMe['INTERNAL.ProcessArgs.Malformed'] = [] gMe['INTERNAL.ProcessArgs.Unknown'] = [] From 289e9e0ab0119ff6ad8c6010cde13751fef44c13 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 18:30:42 +0530 Subject: [PATCH 124/187] SimpleChatTC:SimpleProxy: AllowedDomains based filtering Allow fetching from only specified allowed.domains --- .../local.tools/simpleproxy.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 2787a4420bca3..85ab012827c20 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -21,6 +21,7 @@ import urllib.request from dataclasses import dataclass import html.parser +import re gMe = { @@ -102,6 +103,23 @@ class UrlReqResp: contentData: str = "" +def validate_url(url: str, tag: str): + """ + Implement a re based filter logic on the specified url. + """ + urlParts = urllib.parse.urlparse(url) + urlHName = urlParts.hostname + if not urlHName: + return UrlReqResp(False, 400, f"WARN:{tag}:Missing hostname in Url") + bMatched = False + for filter in gMe['--allowed.domains']: + if re.match(filter, urlHName): + bMatched = True + if not bMatched: + return UrlReqResp(False, 400, f"WARN:{tag}:requested hostname not allowed") + return UrlReqResp(True, 200) + + def handle_urlreq(pr: urllib.parse.ParseResult, tag: str): """ Common part of the url request handling used by both urlraw and urltext. @@ -113,6 +131,11 @@ def handle_urlreq(pr: urllib.parse.ParseResult, tag: str): url = url[0] if (not url) or (len(url) == 0): return UrlReqResp(False, 400, f"WARN:{tag}:MissingUrl") + if (not gMe['--allowed.domains']): + return UrlReqResp(False, 400, f"DBUG:{tag}:MissingAllowedDomains") + gotVU = validate_url(url, tag) + if not gotVU.callOk: + return gotVU try: # Get requested url with urllib.request.urlopen(url, timeout=10) as response: @@ -260,6 +283,7 @@ def load_config(): def process_args(args: list[str]): + import ast """ Helper to process command line arguments """ @@ -284,6 +308,10 @@ def process_args(args: list[str]): gMe[cArg] = args[iArg] iArg += 1 load_config() + case '--allowed.domains': + iArg += 1 + gMe[cArg] = ast.literal_eval(args[iArg]) + iArg += 1 case _: gMe['INTERNAL.ProcessArgs.Unknown'].append(cArg) print(f"WARN:ProcessArgs:{iArg}:IgnoringUnknownCommand:{cArg}") From 7f00709385c839f53ebf1a0402ccb8ce24cc6557 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 19:01:50 +0530 Subject: [PATCH 125/187] SimpleChatTC:SimpleProxy: Cleanup domain filtering and general Had confused between js and python wrt accessing dictionary contents and its consequence on non existent key. Fixed it. Use different error ids to distinguish between failure in common urlreq and the specific urltext and urlraw helpers. --- tools/server/public_simplechat/local.tools/simpleproxy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 85ab012827c20..9d1a3287028d9 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -131,7 +131,7 @@ def handle_urlreq(pr: urllib.parse.ParseResult, tag: str): url = url[0] if (not url) or (len(url) == 0): return UrlReqResp(False, 400, f"WARN:{tag}:MissingUrl") - if (not gMe['--allowed.domains']): + if (not gMe.get('--allowed.domains')): return UrlReqResp(False, 400, f"DBUG:{tag}:MissingAllowedDomains") gotVU = validate_url(url, tag) if not gotVU.callOk: @@ -144,7 +144,7 @@ def handle_urlreq(pr: urllib.parse.ParseResult, tag: str): contentType = response.getheader('Content-Type') or 'text/html' return UrlReqResp(True, statusCode, "", contentType, contentData) except Exception as exc: - return UrlReqResp(False, 502, f"WARN:UrlFetchFailed:{exc}") + return UrlReqResp(False, 502, f"WARN:UrlReqFailed:{exc}") def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): @@ -162,7 +162,7 @@ def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.end_headers() ph.wfile.write(got.contentData.encode('utf-8')) except Exception as exc: - ph.send_error(502, f"WARN:UrlFetchFailed:{exc}") + ph.send_error(502, f"WARN:UrlRawFailed:{exc}") class TextHtmlParser(html.parser.HTMLParser): @@ -260,7 +260,7 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.end_headers() ph.wfile.write(textHtml.get_stripped_text().encode('utf-8')) except Exception as exc: - ph.send_error(502, f"WARN:UrlFetchFailed:{exc}") + ph.send_error(502, f"WARN:UrlTextFailed:{exc}") def load_config(): From 05b52c315aaeab217bb869defabe733f6e7205a3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 19:30:48 +0530 Subject: [PATCH 126/187] SimpleChatTC:SimpleProxy: Include a sample config file with allowed domains set to few sites in general to show its use this includes some sites which allow search to be carried out through them as well as provide news aggregation --- .../public_simplechat/local.tools/simpleproxy.json | 12 ++++++++++++ .../public_simplechat/local.tools/simpleproxy.py | 1 + 2 files changed, 13 insertions(+) create mode 100644 tools/server/public_simplechat/local.tools/simpleproxy.json diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json new file mode 100644 index 0000000000000..396567652bd5f --- /dev/null +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -0,0 +1,12 @@ +{ + "allowed.domains": [ + "^www\\.bing\\.com$", + ".*\\.yahoo\\.com$", + "^search\\.yahoo\\.com$", + ".*\\.brave\\.com$", + "^search\\.brave\\.com$", + ".*\\.duckduckgo\\.com$", + ".*\\.google\\.com$", + "^google\\.com$" + ] +} diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 9d1a3287028d9..d0036dbff0fac 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -108,6 +108,7 @@ def validate_url(url: str, tag: str): Implement a re based filter logic on the specified url. """ urlParts = urllib.parse.urlparse(url) + print(f"DBUG:ValidateUrl:{urlParts}, {urlParts.hostname}") urlHName = urlParts.hostname if not urlHName: return UrlReqResp(False, 400, f"WARN:{tag}:Missing hostname in Url") From da99c8b76cdf3f7d539208e82189155a03213245 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 20:57:22 +0530 Subject: [PATCH 127/187] SimpleChatTC: Update readme a bit --- tools/server/public_simplechat/readme.md | 39 +++++++++++++++--------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 779a48adaf9f8..702b83379946c 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -42,8 +42,9 @@ any adaptive culling of old messages nor of replacing them with summary of their is a optional sliding window based chat logic, which provides a simple minded culling of old messages from the chat history before sending to the ai model. -NOTE: Wrt options sent with the request, it mainly sets temperature, max_tokens and optionaly stream for now. -However if someone wants they can update the js file or equivalent member in gMe as needed. +NOTE: Wrt options sent with the request, it mainly sets temperature, max_tokens and optionaly stream as well +as tool_calls mainly for now. However if someone wants they can update the js file or equivalent member in +gMe as needed. NOTE: One may be able to use this to chat with openai api web-service /chat/completions endpoint, in a very limited / minimal way. One will need to set model, openai url and authorization bearer key in settings ui. @@ -55,7 +56,7 @@ One could run this web frontend directly using server itself or if anyone is thi frontend to configure the server over http(s) or so, then run this web frontend using something like python's http module. -### running using tools/server +### running directly using tools/server ./llama-server -m path/model.gguf --path tools/server/public_simplechat [--port PORT] @@ -78,10 +79,15 @@ remember to * use a GenAi/LLM model which supports tool calling. -* if fetch web url / page tool call is needed, remember to run the bundled local.tools/simpleproxy.py helper +* if fetch web url / page tool call is needed remember to run the bundled local.tools/simpleproxy.py + helper along with its config file - * remember that this is a relatively dumb proxy logic along with optional stripping of scripts/styles/headers/footers/..., - Be careful if trying to fetch web pages, and use it only with known safe sites. + * cd tools/server/public_simplechat/local.tools; python3 ./simpleproxy.py --config simpleproxy.json + + * remember that this is a relatively dumb proxy logic along with optional stripping of scripts / styles + / headers / footers /..., Be careful if trying to fetch web pages, and use it only with known safe sites. + + * it allows one to specify a white list of allowed.domains, look into local.tools/simpleproxy.json ### using the front end @@ -312,7 +318,9 @@ wrt repeatations in general in the generated text response. A end-user can change these behaviour by editing gMe from browser's devel-tool/console or by using the provided settings ui (for settings exposed through the ui). The logic uses a generic helper which autocreates property edit ui elements for the specified set of properties. If the -new property is a number or text or boolean, the autocreate logic will handle it. +new property is a number or text or boolean or a object with properties within it, autocreate +logic will try handle it automatically. A developer can trap this autocreation flow and change +things if needed. ### OpenAi / Equivalent API WebService @@ -362,11 +370,11 @@ server logic, this helps bypass the CORS restrictions applied if trying to direc browser js runtime environment. Depending on the path specified wrt the proxy server, if urltext (and not urlraw), it additionally tries to convert html content into equivalent text to some extent in a simple minded manner by dropping head block as well as all scripts/styles/footers/headers/nav. -May add support for white list of allowed sites to access or so. * the logic does a simple check to see if the bundled simpleproxy is running at specified fetchProxyUrl before enabling fetch web related tool calls. * The bundled simple proxy can be found at * tools/server/public_simplechat/local.tools/simpleproxy.py +* it provides for a basic white list of allowed domains to access or so #### Extending with new tools @@ -374,10 +382,10 @@ May add support for white list of allowed sites to access or so. Provide a descriptive meta data explaining the tool / function being provided for tool calling, as well as its arguments. -Provide a handler which should implement the specified tool / function call or rather constructs -the code to be run to get the tool / function call job done, and inturn pass the same to the -provided web worker to get it executed. Remember to use console.log while generating any response -that should be sent back to the ai model, in your constructed code. +Provide a handler which should implement the specified tool / function call or rather for many +cases constructs the code to be run to get the tool / function call job done, and inturn pass +the same to the provided web worker to get it executed. Remember to use console.log while +generating any response that should be sent back to the ai model, in your constructed code. Update the tc_switch to include a object entry for the tool, which inturn includes * the meta data as well as @@ -415,14 +423,17 @@ so that any scheduled asynchronous code or related async error handling using pr gets executed, before tool calling returns and thus data / error generated by those async code also get incorporated in result sent to ai engine on the server side. -#### ToDo -Is the promise land trap deep enough, need to think through and explore around this once later. +### ToDo + +Is the tool call promise land trap deep enough, need to think through and explore around this once later. Trap error responses. Handle reasoning/thinking responses from ai models. +Handle multimodal handshaking with ai models. + ### Debuging the handshake From e42e72ed822c6d854542b038e434414272ef7115 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 22:39:05 +0530 Subject: [PATCH 128/187] SimpleChatTC:SimpleProxy: Some debug prints which give info --- tools/server/public_simplechat/local.tools/simpleproxy.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index d0036dbff0fac..31086780371de 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -51,6 +51,7 @@ def send_error(self, code: int, message: str | None = None, explain: str | None so that the common headers mentioned above can get added to them else CORS failure will be triggered by the browser on fetch from browser. """ + print(f"WARN:PH:SendError:{code}:{message}") self.send_response(code, message) self.send_headers_common() @@ -58,7 +59,8 @@ def do_GET(self): """ Handle GET requests """ - print(f"DBUG:ProxyHandler:GET:{self.path}") + print(f"\n\n\nDBUG:ProxyHandler:GET:{self.address_string()}:{self.path}") + print(f"DBUG:PH:Get:Headers:{self.headers}") pr = urllib.parse.urlparse(self.path) print(f"DBUG:ProxyHandler:GET:{pr}") match pr.path: From 0cb2217e6a3d19c47d8c402c70b890c6afa60545 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 22:49:07 +0530 Subject: [PATCH 129/187] SimpleChatTC:SimpleProxy:Try mimic real client using got req info ie include User-Agent, Accept-Language and Accept in the generated request using equivalent values got in the request being proxied. --- .../local.tools/simpleproxy.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 31086780371de..598d77b2f4dc7 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -123,9 +123,18 @@ def validate_url(url: str, tag: str): return UrlReqResp(True, 200) -def handle_urlreq(pr: urllib.parse.ParseResult, tag: str): +def handle_urlreq(ph: ProxyHandler, pr: urllib.parse.ParseResult, tag: str): """ Common part of the url request handling used by both urlraw and urltext. + + Verify the url being requested is allowed. + + Include User-Agent, Accept-Language and Accept in the generated request using + equivalent values got in the request being proxied, so as to try mimic the + real client, whose request we are proxying. In case a header is missing in the + got request, fallback to using some possibly ok enough defaults. + + Fetch the requested url. """ print(f"DBUG:{tag}:{pr}") queryParams = urllib.parse.parse_qs(pr.query) @@ -140,8 +149,17 @@ def handle_urlreq(pr: urllib.parse.ParseResult, tag: str): if not gotVU.callOk: return gotVU try: + hUA = ph.headers.get('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0') + hAL = ph.headers.get('Accept-Language', "en-US,en") + hA = ph.headers.get('Accept', "text/html,*/*") + headers = { + 'User-Agent': hUA, + 'Accept': hA, + 'Accept-Language': hAL + } + req = urllib.request.Request(url, headers=headers) # Get requested url - with urllib.request.urlopen(url, timeout=10) as response: + with urllib.request.urlopen(req, timeout=10) as response: contentData = response.read().decode('utf-8') statusCode = response.status or 200 contentType = response.getheader('Content-Type') or 'text/html' @@ -153,7 +171,7 @@ def handle_urlreq(pr: urllib.parse.ParseResult, tag: str): def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): try: # Get requested url - got = handle_urlreq(pr, "HandleUrlRaw") + got = handle_urlreq(ph, pr, "HandleUrlRaw") if not got.callOk: ph.send_error(got.httpStatus, got.httpStatusMsg) return @@ -248,7 +266,7 @@ def get_stripped_text(self): def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): try: # Get requested url - got = handle_urlreq(pr, "HandleUrlText") + got = handle_urlreq(ph, pr, "HandleUrlText") if not got.callOk: ph.send_error(got.httpStatus, got.httpStatusMsg) return From 25b7aab351272d2902a12bcb14e9bdc9b6f7b0db Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 23:18:06 +0530 Subject: [PATCH 130/187] SimpleChatTC:SimpleProxy:Cleanup a bit The tagging of messages wrt ValidateUrl and UrlReq Also dump req Move check for --allowed.domains to ValidateUrl NOTE: Also with mimicing of user agent etal from got request to the generated request, yahoo search/news is returning results now, instead of the bland error before. --- .../server/public_simplechat/local.tools/simpleproxy.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 598d77b2f4dc7..6846caf5ee33c 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -109,6 +109,9 @@ def validate_url(url: str, tag: str): """ Implement a re based filter logic on the specified url. """ + tag=f"VU:{tag}" + if (not gMe.get('--allowed.domains')): + return UrlReqResp(False, 400, f"DBUG:{tag}:MissingAllowedDomains") urlParts = urllib.parse.urlparse(url) print(f"DBUG:ValidateUrl:{urlParts}, {urlParts.hostname}") urlHName = urlParts.hostname @@ -136,6 +139,7 @@ def handle_urlreq(ph: ProxyHandler, pr: urllib.parse.ParseResult, tag: str): Fetch the requested url. """ + tag=f"UrlReq:{tag}" print(f"DBUG:{tag}:{pr}") queryParams = urllib.parse.parse_qs(pr.query) url = queryParams['url'] @@ -143,8 +147,6 @@ def handle_urlreq(ph: ProxyHandler, pr: urllib.parse.ParseResult, tag: str): url = url[0] if (not url) or (len(url) == 0): return UrlReqResp(False, 400, f"WARN:{tag}:MissingUrl") - if (not gMe.get('--allowed.domains')): - return UrlReqResp(False, 400, f"DBUG:{tag}:MissingAllowedDomains") gotVU = validate_url(url, tag) if not gotVU.callOk: return gotVU @@ -159,13 +161,14 @@ def handle_urlreq(ph: ProxyHandler, pr: urllib.parse.ParseResult, tag: str): } req = urllib.request.Request(url, headers=headers) # Get requested url + print(f"DBUG:{tag}:Req:{req.full_url}:{req.headers}") with urllib.request.urlopen(req, timeout=10) as response: contentData = response.read().decode('utf-8') statusCode = response.status or 200 contentType = response.getheader('Content-Type') or 'text/html' return UrlReqResp(True, statusCode, "", contentType, contentData) except Exception as exc: - return UrlReqResp(False, 502, f"WARN:UrlReqFailed:{exc}") + return UrlReqResp(False, 502, f"WARN:{tag}:Failed:{exc}") def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): From 9bd3b35e2c2e41b6e34ed1434197c55dedc0e028 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 00:36:54 +0530 Subject: [PATCH 131/187] SimpleChatTC:SimpleProxy: mimicing got req helps wrt duckduckgo mimicing got req in generated req helps with duckduckgo also and not just yahoo. also update allowed.domains to allow a url generated by ai when trying to access the bing's news aggregation url --- tools/server/public_simplechat/local.tools/simpleproxy.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json index 396567652bd5f..a0af1169d0d7a 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.json +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -1,11 +1,13 @@ { "allowed.domains": [ + ".*\\.bing\\.com$", "^www\\.bing\\.com$", ".*\\.yahoo\\.com$", "^search\\.yahoo\\.com$", ".*\\.brave\\.com$", "^search\\.brave\\.com$", ".*\\.duckduckgo\\.com$", + "^duckduckgo\\.com$", ".*\\.google\\.com$", "^google\\.com$" ] From 944179887af9c1759be03344e742be5b41ecddad Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 01:11:22 +0530 Subject: [PATCH 132/187] SimpleChatTC:ToolCall response relaxed handling Use DOMParser parseFromString in text/html mode rather than text/xml as it makes it more relaxed without worrying about special chars of xml like & etal --- tools/server/public_simplechat/simplechat.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index e58f6daddc86b..cdaf47bcd1486 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -107,12 +107,18 @@ class ChatMessageEx { /** * Extract the elements of the all in one tool call result string - * This should potentially account for content tag having xml content within to an extent. + * This should potentially account for content tag having xml/html content within to an extent. + * + * NOTE: Rather text/html is a more relaxed/tolarent mode for parseFromString than text/xml. + * NOTE: Maybe better to switch to a json string format or use a more intelligent xml encoder + * in createToolCallResultAllInOne so that extractor like this dont have to worry about special + * xml chars like & as is, in the AllInOne content. For now text/html tolarence seems ok enough. + * * @param {string} allInOne */ static extractToolCallResultAllInOne(allInOne) { const dParser = new DOMParser(); - const got = dParser.parseFromString(allInOne, 'text/xml'); + const got = dParser.parseFromString(allInOne, 'text/html'); const parseErrors = got.querySelector('parseerror') if (parseErrors) { console.debug("WARN:ChatMessageEx:ExtractToolCallResultAllInOne:", parseErrors.textContent.trim()) From 200181f7e8f0f23bd7ed142572562c847114e57e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 01:31:30 +0530 Subject: [PATCH 133/187] SimpleChatTC:SimpleProxy: Update readme wrt mimicing client req ie during proxying --- tools/server/public_simplechat/readme.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 702b83379946c..83781a31ead85 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -374,7 +374,11 @@ in a simple minded manner by dropping head block as well as all scripts/styles/f before enabling fetch web related tool calls. * The bundled simple proxy can be found at * tools/server/public_simplechat/local.tools/simpleproxy.py -* it provides for a basic white list of allowed domains to access or so + * it provides for a basic white list of allowed domains to access, to an extent + * it tries to mimic the client/browser making the request to it by propogating header entries like + user-agent, accept and accept-language from the got request to the generated request during proxying + so that websites will hopefully respect the request rather than blindly rejecting it as coming from + a non-browser entity. #### Extending with new tools From 33f35cab4b0eb92dfa26d6a28fe1b589773ccf5f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 03:00:09 +0530 Subject: [PATCH 134/187] SimpleChatTC:ToolResponse: Use browser dom for xml/html safe Instead of simple concatenating of tool call id, name and result now use browser's dom logic to create the xml structure used for now to store these within content field. This should take care of transforming / escaping any xml special chars in the result, so that extracting them later for putting into different fields in the server handshake doesnt have any problem. --- tools/server/public_simplechat/simplechat.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index cdaf47bcd1486..6f4d55940cf52 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -79,12 +79,21 @@ class ChatMessageEx { /** * Create a all in one tool call result string + * Use browser's dom logic to handle strings in a xml/html safe way by escaping things where needed, + * so that extracting the same later doesnt create any problems. * @param {string} toolCallId * @param {string} toolName * @param {string} toolResult */ static createToolCallResultAllInOne(toolCallId, toolName, toolResult) { - return ` ${toolCallId} ${toolName} ${toolResult} `; + let dp = new DOMParser() + let doc = dp.parseFromString("", "text/xml") + for (const k of [["id", toolCallId], ["name", toolName], ["content", toolResult]]) { + let el = doc.createElement(k[0]) + el.appendChild(doc.createTextNode(k[1])) + doc.documentElement.appendChild(el) + } + return new XMLSerializer().serializeToString(doc); } /** From fb982f05f4504672e52143649effc023cad8c9e8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 03:40:55 +0530 Subject: [PATCH 135/187] SimpleChatTC:SimpleProxy: debug dumps to identify funny bing bing raised a challenge for chrome triggered search requests after few requests, which were spread few minutes apart, while still seemingly allowing wget based search to continue (again spread few minutes apart). Added a simple helper to trace this, use --debug True to enable same. --- .../local.tools/simpleproxy.json | 1 + .../local.tools/simpleproxy.py | 22 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json index a0af1169d0d7a..30f14b1d2c0fd 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.json +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -6,6 +6,7 @@ "^search\\.yahoo\\.com$", ".*\\.brave\\.com$", "^search\\.brave\\.com$", + "^brave\\.com$", ".*\\.duckduckgo\\.com$", "^duckduckgo\\.com$", ".*\\.google\\.com$", diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 6846caf5ee33c..76ea363ca3e3c 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -22,11 +22,13 @@ from dataclasses import dataclass import html.parser import re +import time gMe = { '--port': 3128, '--config': '/dev/null', + '--debug': False, 'server': None } @@ -105,6 +107,18 @@ class UrlReqResp: contentData: str = "" +def debug_dump(meta: dict, data: dict): + if not gMe['--debug']: + return + timeTag = f"{time.time():0.12f}" + with open(f"/tmp/simpleproxy.{timeTag}.meta", '+w') as f: + for k in meta: + f.write(f"\n\n\n\n{k}:{meta[k]}\n\n\n\n") + with open(f"/tmp/simpleproxy.{timeTag}.data", '+w') as f: + for k in data: + f.write(f"\n\n\n\n{k}:{data[k]}\n\n\n\n") + + def validate_url(url: str, tag: str): """ Implement a re based filter logic on the specified url. @@ -152,7 +166,7 @@ def handle_urlreq(ph: ProxyHandler, pr: urllib.parse.ParseResult, tag: str): return gotVU try: hUA = ph.headers.get('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0') - hAL = ph.headers.get('Accept-Language', "en-US,en") + hAL = ph.headers.get('Accept-Language', "en-US,en;q=0.9") hA = ph.headers.get('Accept', "text/html,*/*") headers = { 'User-Agent': hUA, @@ -166,6 +180,7 @@ def handle_urlreq(ph: ProxyHandler, pr: urllib.parse.ParseResult, tag: str): contentData = response.read().decode('utf-8') statusCode = response.status or 200 contentType = response.getheader('Content-Type') or 'text/html' + debug_dump({ 'url': req.full_url, 'headers': req.headers, 'ctype': contentType }, { 'cdata': contentData }) return UrlReqResp(True, statusCode, "", contentType, contentData) except Exception as exc: return UrlReqResp(False, 502, f"WARN:{tag}:Failed:{exc}") @@ -283,6 +298,7 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.send_header('Access-Control-Allow-Origin', '*') ph.end_headers() ph.wfile.write(textHtml.get_stripped_text().encode('utf-8')) + debug_dump({ 'RawText': 'yes', 'StrippedText': 'yes' }, { 'RawText': textHtml.text, 'StrippedText': textHtml.get_stripped_text() }) except Exception as exc: ph.send_error(502, f"WARN:UrlTextFailed:{exc}") @@ -336,6 +352,10 @@ def process_args(args: list[str]): iArg += 1 gMe[cArg] = ast.literal_eval(args[iArg]) iArg += 1 + case '--debug': + iArg += 1 + gMe[cArg] = ast.literal_eval(args[iArg]) + iArg += 1 case _: gMe['INTERNAL.ProcessArgs.Unknown'].append(cArg) print(f"WARN:ProcessArgs:{iArg}:IgnoringUnknownCommand:{cArg}") From 8b1847314227573374b4862b7c86692387a3524d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 04:18:36 +0530 Subject: [PATCH 136/187] SimpleChatTC:SimpleProxy:Cleanup avoid logically duplicate debug log --- tools/server/public_simplechat/local.tools/simpleproxy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 76ea363ca3e3c..86f5aa0e7b60f 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -154,7 +154,6 @@ def handle_urlreq(ph: ProxyHandler, pr: urllib.parse.ParseResult, tag: str): Fetch the requested url. """ tag=f"UrlReq:{tag}" - print(f"DBUG:{tag}:{pr}") queryParams = urllib.parse.parse_qs(pr.query) url = queryParams['url'] print(f"DBUG:{tag}:Url:{url}") From a128f837a154a7c79bf3d6a8b6545a8f95748984 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 05:05:24 +0530 Subject: [PATCH 137/187] SimpleChatTC:Auto tool calling control to end user Instead of enforcing always explicit user triggered tool calling, now user is given the option whether to use explicit user triggered tool calling or to use auto triggering after showing tool details for a user specified amount of seconds. NOTE: The current logic doesnt account for user clicking the buttons before the autoclick triggers; need to cancel the auto clicks, if user triggers before autoclick, ie in future. --- tools/server/public_simplechat/simplechat.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 6f4d55940cf52..13d8034c51080 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -741,6 +741,11 @@ class MultiChatUI { this.elInToolName.dataset.tool_call_id = ar.ns.tool_calls[0].id this.elInToolArgs.value = ar.ns.tool_calls[0].function.arguments this.elBtnTool.disabled = false + if (gMe.tools.auto > 0) { + setTimeout(()=>{ + this.elBtnTool.click() + }, gMe.tools.auto*1000) + } } else { this.elDivTool.hidden = true this.elInToolName.value = "" @@ -808,6 +813,11 @@ class MultiChatUI { clearTimeout(this.idTimeOut) this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(id, name, data); this.ui_reset_userinput(false) + if (gMe.tools.auto > 0) { + setTimeout(()=>{ + this.elBtnUser.click() + }, gMe.tools.auto*1000) + } }) this.elInUser.addEventListener("keyup", (ev)=> { @@ -1033,7 +1043,8 @@ class Me { this.tools = { enabled: false, fetchProxyUrl: "http://127.0.0.1:3128", - toolNames: /** @type {Array} */([]) + toolNames: /** @type {Array} */([]), + auto: 0 }; this.chatProps = { apiEP: ApiEP.Type.Chat, From 524aa01f1ad8a0a7aee0c456be6353b694b812b3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 19:04:33 +0530 Subject: [PATCH 138/187] SimpleChatTC:AutoToolCalls: Track and clear related timers also cleanup the existing toolResponseTimeout timer to be in the same structure and have similar flow convention. --- tools/server/public_simplechat/simplechat.js | 47 +++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 13d8034c51080..c0f0ac4544f82 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -691,6 +691,29 @@ class MultiChatUI { /** @type {string} */ this.curChatId = ""; + this.TimePeriods = { + ToolCallResponseTimeout: 10000, + ToolCallAutoTimeUnit: 1000 + } + + this.timers = { + /** + * Used to identify Delay with getting response from a tool call. + * @type {number | undefined} + */ + toolcallResponseTimeout: undefined, + /** + * Used to auto trigger tool call, after a set time, if enabled. + * @type {number | undefined} + */ + toolcallTriggerClick: undefined, + /** + * Used to auto submit tool call response, after a set time, if enabled. + * @type {number | undefined} + */ + toolcallResponseSubmitClick: undefined + } + // the ui elements this.elInSystem = /** @type{HTMLInputElement} */(document.getElementById("system-in")); this.elDivChat = /** @type{HTMLDivElement} */(document.getElementById("chat-div")); @@ -742,9 +765,9 @@ class MultiChatUI { this.elInToolArgs.value = ar.ns.tool_calls[0].function.arguments this.elBtnTool.disabled = false if (gMe.tools.auto > 0) { - setTimeout(()=>{ + this.timers.toolcallTriggerClick = setTimeout(()=>{ this.elBtnTool.click() - }, gMe.tools.auto*1000) + }, gMe.tools.auto*this.TimePeriods.ToolCallAutoTimeUnit) } } else { this.elDivTool.hidden = true @@ -791,6 +814,8 @@ class MultiChatUI { }); this.elBtnUser.addEventListener("click", (ev)=>{ + clearTimeout(this.timers.toolcallResponseSubmitClick) + this.timers.toolcallResponseSubmitClick = undefined if (this.elInUser.disabled) { return; } @@ -803,20 +828,24 @@ class MultiChatUI { }); this.elBtnTool.addEventListener("click", (ev)=>{ + clearTimeout(this.timers.toolcallTriggerClick) + this.timers.toolcallTriggerClick = undefined if (this.elDivTool.hidden) { return; } this.handle_tool_run(this.curChatId); }) + // Handle messages from Tools web worker tools.setup((id, name, data)=>{ - clearTimeout(this.idTimeOut) + clearTimeout(this.timers.toolcallResponseTimeout) + this.timers.toolcallResponseTimeout = undefined this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(id, name, data); this.ui_reset_userinput(false) if (gMe.tools.auto > 0) { - setTimeout(()=>{ + this.timers.toolcallResponseSubmitClick = setTimeout(()=>{ this.elBtnUser.click() - }, gMe.tools.auto*1000) + }, gMe.tools.auto*this.TimePeriods.ToolCallAutoTimeUnit) } }) @@ -946,10 +975,10 @@ class MultiChatUI { this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, toolResult); this.ui_reset_userinput(false) } else { - this.idTimeOut = setTimeout(() => { + this.timers.toolcallResponseTimeout = setTimeout(() => { this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, `Tool/Function call ${toolname} taking too much time, aborting...`); this.ui_reset_userinput(false) - }, 10000) + }, this.TimePeriods.ToolCallResponseTimeout) } } @@ -1044,6 +1073,10 @@ class Me { enabled: false, fetchProxyUrl: "http://127.0.0.1:3128", toolNames: /** @type {Array} */([]), + /** + * Control how many seconds to wait before auto triggering tool call or its response submission. + * A value of 0 is treated as auto triggering disable. + */ auto: 0 }; this.chatProps = { From a4152d1d38127fab6c15d252c3812c4d0759b6dc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 20:51:10 +0530 Subject: [PATCH 139/187] SimpleChatTC: Cleanup whitespaces identified by llama.cpp editorconfig check * convert tab to spaces in json config file * remove extra space at end of line --- .../local.tools/simpleproxy.json | 28 +++++++++---------- tools/server/public_simplechat/tooljs.mjs | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json index 30f14b1d2c0fd..4a6a520612964 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.json +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -1,15 +1,15 @@ { - "allowed.domains": [ - ".*\\.bing\\.com$", - "^www\\.bing\\.com$", - ".*\\.yahoo\\.com$", - "^search\\.yahoo\\.com$", - ".*\\.brave\\.com$", - "^search\\.brave\\.com$", - "^brave\\.com$", - ".*\\.duckduckgo\\.com$", - "^duckduckgo\\.com$", - ".*\\.google\\.com$", - "^google\\.com$" - ] -} + "allowed.domains": [ + ".*\\.bing\\.com$", + "^www\\.bing\\.com$", + ".*\\.yahoo\\.com$", + "^search\\.yahoo\\.com$", + ".*\\.brave\\.com$", + "^search\\.brave\\.com$", + "^brave\\.com$", + ".*\\.duckduckgo\\.com$", + "^duckduckgo\\.com$", + ".*\\.google\\.com$", + "^google\\.com$" + ] +} \ No newline at end of file diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 3943ca453488b..cfd216e3666b7 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -73,7 +73,7 @@ function calc_run(toolcallid, toolname, obj) { /** - * Send a message to Tools WebWorker's monitor in main thread directly + * Send a message to Tools WebWorker's monitor in main thread directly * @param {MessageEvent} mev */ function message_toolsworker(mev) { From cff1de965f462d365ba0b7fc13f587c2ee421935 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 20:55:44 +0530 Subject: [PATCH 140/187] SimpleChatTC:Cleanup whitespace - github editorconfig checker Add missing newline to ending bracket line of json config file --- tools/server/public_simplechat/local.tools/simpleproxy.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json index 4a6a520612964..949b7e014d5af 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.json +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -12,4 +12,4 @@ ".*\\.google\\.com$", "^google\\.com$" ] -} \ No newline at end of file +} From 8481ab46644ab29cfde01a01ee235b839bf25d13 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 12:01:56 +0530 Subject: [PATCH 141/187] SimpleChatTC:Update and cleanup the readme a bit include info about the auto option within tools. use nonwrapped text wrt certain sections, so that the markdown readme can be viewed properly wrt the structure of the content in it. --- tools/server/public_simplechat/readme.md | 230 ++++++++++++++--------- 1 file changed, 137 insertions(+), 93 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 83781a31ead85..6576e26914c90 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -21,7 +21,7 @@ own system prompts. This allows seeing the generated text / ai-model response in oneshot at the end, after it is fully generated, or potentially as it is being generated, in a streamed manner from the server/ai-model. -![Chat and Settings screens](./simplechat_screens.webp "Chat and Settings screens") +![Chat and Settings (old) screens](./simplechat_screens.webp "Chat and Settings (old) screens") Auto saves the chat session locally as and when the chat is progressing and inturn at a later time when you open SimpleChat, option is provided to restore the old chat session, if a matching one exists. @@ -80,7 +80,7 @@ remember to * use a GenAi/LLM model which supports tool calling. * if fetch web url / page tool call is needed remember to run the bundled local.tools/simpleproxy.py - helper along with its config file + helper along with its config file, before using/loading this client ui through a browser * cd tools/server/public_simplechat/local.tools; python3 ./simpleproxy.py --config simpleproxy.json @@ -154,6 +154,9 @@ Once inside User can even modify the response generated by the tool, if required, before submitting. * just refresh the page, to reset wrt the chat history and or system prompt and start afresh. + This also helps if you had forgotten to start the bundled simpleproxy.py server before hand. + Start the simpleproxy.py server and refresh the client ui page, to get access to web access + related tool calls. * Using NewChat one can start independent chat sessions. * two independent chat sessions are setup by default. @@ -181,91 +184,71 @@ Me/gMe consolidates the settings which control the behaviour into one object. One can see the current settings, as well as change/update them using browsers devel-tool/console. It is attached to the document object. Some of these can also be updated using the Settings UI. - baseURL - the domain-name/ip-address and inturn the port to send the request. + * baseURL - the domain-name/ip-address and inturn the port to send the request. - chatProps - maintain a set of properties which manipulate chatting with ai engine + * chatProps - maintain a set of properties which manipulate chatting with ai engine - apiEP - select between /completions and /chat/completions endpoint provided by the server/ai-model. + * apiEP - select between /completions and /chat/completions endpoint provided by the server/ai-model. - stream - control between oneshot-at-end and live-stream-as-its-generated collating and showing - of the generated response. + * stream - control between oneshot-at-end and live-stream-as-its-generated collating and showing of the generated response. the logic assumes that the text sent from the server follows utf-8 encoding. - in streaming mode - if there is any exception, the logic traps the same and tries to ensure - that text generated till then is not lost. + in streaming mode - if there is any exception, the logic traps the same and tries to ensure that text generated till then is not lost. - if a very long text is being generated, which leads to no user interaction for sometime and - inturn the machine goes into power saving mode or so, the platform may stop network connection, - leading to exception. + * if a very long text is being generated, which leads to no user interaction for sometime and inturn the machine goes into power saving mode or so, the platform may stop network connection, leading to exception. - iRecentUserMsgCnt - a simple minded SlidingWindow to limit context window load at Ai Model end. - This is set to 10 by default. So in addition to latest system message, last/latest iRecentUserMsgCnt - user messages after the latest system prompt and its responses from the ai model will be sent - to the ai-model, when querying for a new response. Note that if enabled, only user messages after - the latest system message/prompt will be considered. + * iRecentUserMsgCnt - a simple minded SlidingWindow to limit context window load at Ai Model end. This is set to 10 by default. So in addition to latest system message, last/latest iRecentUserMsgCnt user messages after the latest system prompt and its responses from the ai model will be sent to the ai-model, when querying for a new response. Note that if enabled, only user messages after the latest system message/prompt will be considered. This specified sliding window user message count also includes the latest user query. - <0 : Send entire chat history to server - 0 : Send only the system message if any to the server - >0 : Send the latest chat history from the latest system prompt, limited to specified cnt. - bCompletionFreshChatAlways - whether Completion mode collates complete/sliding-window history when - communicating with the server or only sends the latest user query/message. + * less than 0 : Send entire chat history to server - bCompletionInsertStandardRolePrefix - whether Completion mode inserts role related prefix wrt the - messages that get inserted into prompt field wrt /Completion endpoint. + * 0 : Send only the system message if any to the server - bTrimGarbage - whether garbage repeatation at the end of the generated ai response, should be - trimmed or left as is. If enabled, it will be trimmed so that it wont be sent back as part of - subsequent chat history. At the same time the actual trimmed text is shown to the user, once - when it was generated, so user can check if any useful info/data was there in the response. + * greater than 0 : Send the latest chat history from the latest system prompt, limited to specified cnt. - One may be able to request the ai-model to continue (wrt the last response) (if chat-history - is enabled as part of the chat-history-in-context setting), and chances are the ai-model will - continue starting from the trimmed part, thus allows long response to be recovered/continued - indirectly, in many cases. + * bCompletionFreshChatAlways - whether Completion mode collates complete/sliding-window history when communicating with the server or only sends the latest user query/message. - The histogram/freq based trimming logic is currently tuned for english language wrt its - is-it-a-alpabetic|numeral-char regex match logic. + * bCompletionInsertStandardRolePrefix - whether Completion mode inserts role related prefix wrt the messages that get inserted into prompt field wrt /Completion endpoint. - tools - contains controls related to tool calling + * bTrimGarbage - whether garbage repeatation at the end of the generated ai response, should be trimmed or left as is. If enabled, it will be trimmed so that it wont be sent back as part of subsequent chat history. At the same time the actual trimmed text is shown to the user, once when it was generated, so user can check if any useful info/data was there in the response. - enabled - control whether tool calling is enabled or not + One may be able to request the ai-model to continue (wrt the last response) (if chat-history is enabled as part of the chat-history-in-context setting), and chances are the ai-model will continue starting from the trimmed part, thus allows long response to be recovered/continued indirectly, in many cases. + + The histogram/freq based trimming logic is currently tuned for english language wrt its is-it-a-alpabetic|numeral-char regex match logic. + + * tools - contains controls related to tool calling + + * enabled - control whether tool calling is enabled or not remember to enable this only for GenAi/LLM models which support tool/function calling. - fetchProxyUrl - specify the address for the running instance of bundled local.tools/simpleproxy.py + * fetchProxyUrl - specify the address for the running instance of bundled local.tools/simpleproxy.py + + * auto - the amount of time in seconds to wait before the tool call request is auto triggered and generated response is auto submitted back. + + setting this value to 0 (default), disables auto logic, so that end user can review the tool calls requested by ai and if needed even modify them, before triggering/executing them as well as review and modify results generated by the tool call, before submitting them back to the ai. the builtin tools' meta data is sent to the ai model in the requests sent to it. - inturn if the ai model requests a tool call to be made, the same will be done and the response - sent back to the ai model, under user control. + inturn if the ai model requests a tool call to be made, the same will be done and the response sent back to the ai model, under user control, by default. + + as tool calling will involve a bit of back and forth between ai assistant and end user, it is recommended to set iRecentUserMsgCnt to 10 or more, so that enough context is retained during chatting with ai models with tool support. - as tool calling will involve a bit of back and forth between ai assistant and end user, it is - recommended to set iRecentUserMsgCnt to 10 or more, so that enough context is retained during - chatting with ai models with tool support. + * apiRequestOptions - maintains the list of options/fields to send along with api request, irrespective of whether /chat/completions or /completions endpoint. - apiRequestOptions - maintains the list of options/fields to send along with api request, - irrespective of whether /chat/completions or /completions endpoint. + If you want to add additional options/fields to send to the server/ai-model, and or modify the existing options value or remove them, for now you can update this global var using browser's development-tools/console. - If you want to add additional options/fields to send to the server/ai-model, and or - modify the existing options value or remove them, for now you can update this global var - using browser's development-tools/console. + For string, numeric and boolean fields in apiRequestOptions, including even those added by a user at runtime by directly modifying gMe.apiRequestOptions, setting ui entries will be auto created. - For string, numeric and boolean fields in apiRequestOptions, including even those added by a - user at runtime by directly modifying gMe.apiRequestOptions, setting ui entries will be auto - created. + cache_prompt option supported by example/server is allowed to be controlled by user, so that any caching supported wrt system-prompt and chat history, if usable can get used. When chat history sliding window is enabled, cache_prompt logic may or may not kick in at the backend wrt same, based on aspects related to model, positional encoding, attention mechanism etal. However system prompt should ideally get the benefit of caching. - cache_prompt option supported by example/server is allowed to be controlled by user, so that - any caching supported wrt system-prompt and chat history, if usable can get used. When chat - history sliding window is enabled, cache_prompt logic may or may not kick in at the backend - wrt same, based on aspects related to model, positional encoding, attention mechanism etal. - However system prompt should ideally get the benefit of caching. + * headers - maintains the list of http headers sent when request is made to the server. By default - headers - maintains the list of http headers sent when request is made to the server. By default - Content-Type is set to application/json. Additionally Authorization entry is provided, which can - be set if needed using the settings ui. + * Content-Type is set to application/json. + + * Additionally Authorization entry is provided, which can be set if needed using the settings ui. By using gMe's chatProps.iRecentUserMsgCnt and apiRequestOptions.max_tokens/n_predict one can try to @@ -298,14 +281,14 @@ However a developer when testing the server of ai-model may want to change these Using chatProps.iRecentUserMsgCnt reduce chat history context sent to the server/ai-model to be just the system-prompt, few prev-user-requests-and-ai-responses and cur-user-request, instead of full chat history. This way if there is any response with garbage/repeatation, it doesnt -mess with things beyond the next question/request/query, in some ways. The trim garbage +mess with things beyond the next few question/request/query, in some ways. The trim garbage option also tries to help avoid issues with garbage in the context to an extent. -Set max_tokens to 2048, so that a relatively large previous reponse doesnt eat up the space -available wrt next query-response. While parallely allowing a good enough context size for -some amount of the chat history in the current session to influence future answers. However +Set max_tokens to 2048 or as needed, so that a relatively large previous reponse doesnt eat up +the space available wrt next query-response. While parallely allowing a good enough context size +for some amount of the chat history in the current session to influence future answers. However dont forget that the server when started should also be started with a model context size of -2k or more, to be on safe side. +2k or more, as needed. The /completions endpoint of tools/server doesnt take max_tokens, instead it takes the internal n_predict, for now add the same here on the client side, maybe later add max_tokens @@ -346,56 +329,115 @@ work. ### Tool Calling -ALERT: The simple minded way in which this is implemented, it can be dangerous in the worst case, -Always remember to verify all the tool calls requested and the responses generated manually to -ensure everything is fine, during interaction with ai models with tools support. +Given that browsers provide a implicit env for not only showing ui, but also running logic, +simplechat client ui allows use of tool calling support provided by the newer ai models by +end users of llama.cpp's server in a simple way without needing to worry about seperate mcp +host / router, tools etal, for basic useful tools/functions like calculator, code execution +(javascript in this case). + +Additionally if users want to work with web content as part of their ai chat session, Few +functions related to web access which work with a included python based simple proxy server +have been implemented. + +This can allow end users to use some basic yet useful tool calls to enhance their ai chat +sessions to some extent. It also provides for a simple minded exploration of tool calling +support in newer ai models and some fun along the way as well as occasional practical use +like + +* verifying mathematical or logical statements/reasoning made by the ai model during chat +sessions by getting it to also create and execute mathematical expressions or code to verify +such stuff and so. + +* access content from internet and augment the ai model's context with additional data as +needed to help generate better responses. this can also be used for + * generating the latest news summary by fetching from news aggregator sites and collating + organising and summarising the same + * searching for specific topics and summarising the results + * or so + +The tool calling feature has been tested with Gemma3N, Granite4 and GptOss (given that +reasoning is currently unsupported by this client ui, it can mess with things) + +ALERT: The simple minded way in which this is implemented, it provides some minimal safety +mechanism like running ai generated code in web workers and restricting web access to user +specified whitelist and so, but it can still be dangerous in the worst case, So remember +to verify all the tool calls requested and the responses generated manually to ensure +everything is fine, during interaction with ai models with tools support. #### Builtin Tools The following tools/functions are currently provided by default + +##### directly in browser + * simple_calculator - which can solve simple arithmatic expressions + * run_javascript_function_code - which can be used to run some javascript code in the browser context. -* fetch_web_url_raw - fetch requested url through a proxy server -* fetch_web_url_text - fetch requested url through a proxy server - and also try strip the html respose of html tags and also head, script, style, header,footer,... blocks. -Currently the generated code / expression is run through a simple minded eval inside a web worker +Currently the ai generated code / expression is run through a simple minded eval inside a web worker mechanism. Use of WebWorker helps avoid exposing browser global scope to the generated code directly. However any shared web worker scope isnt isolated. Either way always remember to cross check the tool requests and generated responses when using tool calling. -fetch_web_url_raw/text and family works along with a corresponding simple local web proxy/caching -server logic, this helps bypass the CORS restrictions applied if trying to directly fetch from the -browser js runtime environment. Depending on the path specified wrt the proxy server, if urltext -(and not urlraw), it additionally tries to convert html content into equivalent text to some extent -in a simple minded manner by dropping head block as well as all scripts/styles/footers/headers/nav. -* the logic does a simple check to see if the bundled simpleproxy is running at specified fetchProxyUrl - before enabling fetch web related tool calls. -* The bundled simple proxy can be found at +##### using bundled simpleproxy.py (helps bypass browser cors restriction, ...) + +* fetch_web_url_raw - fetch contents of the requested url through a proxy server + +* fetch_web_url_text - fetch text parts of the content from the requested url through a proxy server. + Related logic tries to strip html response of html tags and also head, script, style, header,footer, + nav, ... blocks. + +fetch_web_url_raw/text and family works along with a corresponding simple local web proxy (/caching +in future) server logic, this helps bypass the CORS restrictions applied if trying to directly fetch +from the browser js runtime environment. + +Depending on the path specified wrt the proxy server, it executes the corresponding logic. Like if +urltext path is used (and not urlraw), the logic in addition to fetching content from given url, it +tries to convert html content into equivalent text content to some extent in a simple minded manner +by dropping head block as well as all scripts/styles/footers/headers/nav blocks and inturn dropping +the html tags. + +The client ui logic does a simple check to see if the bundled simpleproxy is running at specified +fetchProxyUrl before enabling these web and related tool calls. + +The bundled simple proxy + +* can be found at * tools/server/public_simplechat/local.tools/simpleproxy.py - * it provides for a basic white list of allowed domains to access, to an extent - * it tries to mimic the client/browser making the request to it by propogating header entries like - user-agent, accept and accept-language from the got request to the generated request during proxying - so that websites will hopefully respect the request rather than blindly rejecting it as coming from - a non-browser entity. +* it provides for a basic white list of allowed domains to access, to be specified by the end user. + This should help limit web access to a safe set of sites determined by the end user. + +* it tries to mimic the client/browser making the request to it by propogating header entries like + user-agent, accept and accept-language from the got request to the generated request during proxying + so that websites will hopefully respect the request rather than blindly rejecting it as coming from + a non-browser entity. + +In future it can be extended to help with other relatively simple yet useful tool calls like search_web, +data/documents_store and so. + + * for now search_web can be indirectly achieved using fetch_web_url_text/raw. #### Extending with new tools +This client ui implements the json schema based function calling convention supported by gen ai +engines over http. + Provide a descriptive meta data explaining the tool / function being provided for tool calling, as well as its arguments. -Provide a handler which should implement the specified tool / function call or rather for many -cases constructs the code to be run to get the tool / function call job done, and inturn pass -the same to the provided web worker to get it executed. Remember to use console.log while -generating any response that should be sent back to the ai model, in your constructed code. +Provide a handler which +* implements the specified tool / function call or +* rather in some cases constructs the code to be run to get the tool / function call job done, + and inturn pass the same to the provided web worker to get it executed. Use console.log while + generating any response that should be sent back to the ai model, in your constructed code. +* once the job is done, return the generated result as needed. Update the tc_switch to include a object entry for the tool, which inturn includes -* the meta data as well as -* a reference to the handler and also - the handler should take toolCallId, toolName and toolArgs and pass these along to - web worker as needed. +* the meta data wrt the tool call +* a reference to the handler - the handler should take toolCallId, toolName and toolArgs. + It should pass these along to the tools web worker, if used. * the result key (was used previously, may use in future, but for now left as is) #### OLD: Mapping tool calls and responses to normal assistant - user chat flow @@ -406,7 +448,7 @@ the SimpleChatTC pushes it into the normal assistant - user chat flow itself, by tool call and response as a pair of tagged request with details in the assistant block and inturn tagged response in the subsequent user block. -This allows the GenAi/LLM to be aware of the tool calls it made as well as the responses it got, +This allows GenAi/LLM to be still aware of the tool calls it made as well as the responses it got, so that it can incorporate the results of the same in the subsequent chat / interactions. NOTE: This flow tested to be ok enough with Gemma-3N-E4B-it-Q8_0 LLM ai model for now. Logically @@ -418,7 +460,7 @@ the tool call responses or even go further and have the logically seperate tool_ structures also. DONE: rather both tool_calls structure wrt assistant messages and tool role based tool call -result messages are generated as needed. +result messages are generated as needed now. #### Related stuff @@ -438,6 +480,8 @@ Handle reasoning/thinking responses from ai models. Handle multimodal handshaking with ai models. +Add search_web and documents|data_store tool calling, through the simpleproxy.py if and where needed. + ### Debuging the handshake From d0621d4a9d1d1d451828a2ab5bf580ff10f187db Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 15:51:48 +0530 Subject: [PATCH 142/187] SimpleChatTC:Duplicate tooljs.mjs to toolweb.mjs So as to split browser js webworker based tool calls from web related tool calls. --- tools/server/public_simplechat/toolweb.mjs | 257 +++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 tools/server/public_simplechat/toolweb.mjs diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs new file mode 100644 index 0000000000000..cfd216e3666b7 --- /dev/null +++ b/tools/server/public_simplechat/toolweb.mjs @@ -0,0 +1,257 @@ +//@ts-check +// DANGER DANGER DANGER - Simple and Stupid - Use from a discardable VM only +// Helpers to handle tools/functions calling wrt +// * javascript interpreter +// * simple arithmatic calculator +// by Humans for All +// + + +let gToolsWorker = /** @type{Worker} */(/** @type {unknown} */(null)); + + +let js_meta = { + "type": "function", + "function": { + "name": "run_javascript_function_code", + "description": "Runs given code using eval within a web worker context in a browser's javascript environment and returns the console.log outputs of the execution after few seconds", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The code that will be run using eval within a web worker in the browser's javascript interpreter environment." + } + }, + "required": ["code"] + } + } + } + + +/** + * Implementation of the javascript interpretor logic. Minimal skeleton for now. + * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + */ +function js_run(toolcallid, toolname, obj) { + gToolsWorker.postMessage({ id: toolcallid, name: toolname, code: obj["code"]}) +} + + +let calc_meta = { + "type": "function", + "function": { + "name": "simple_calculator", + "description": "Calculates the provided arithmatic expression using console.log within a web worker of a browser's javascript interpreter environment and returns the output of the execution once it is done in few seconds", + "parameters": { + "type": "object", + "properties": { + "arithexpr":{ + "type":"string", + "description":"The arithmatic expression that will be calculated by passing it to console.log of a browser's javascript interpreter." + } + }, + "required": ["arithexpr"] + } + } + } + + +/** + * Implementation of the simple calculator logic. Minimal skeleton for now. + * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + */ +function calc_run(toolcallid, toolname, obj) { + gToolsWorker.postMessage({ id: toolcallid, name: toolname, code: `console.log(${obj["arithexpr"]})`}) +} + + +/** + * Send a message to Tools WebWorker's monitor in main thread directly + * @param {MessageEvent} mev + */ +function message_toolsworker(mev) { + // @ts-ignore + gToolsWorker.onmessage(mev) +} + + +let fetchweburlraw_meta = { + "type": "function", + "function": { + "name": "fetch_web_url_raw", + "description": "Fetch the requested web url through a proxy server and return the got content as is, in few seconds", + "parameters": { + "type": "object", + "properties": { + "url":{ + "type":"string", + "description":"url of the web page to fetch from the internet" + } + }, + "required": ["url"] + } + } + } + + +/** + * Implementation of the fetch web url raw logic. Dumb initial go. + * Expects a simple minded proxy server to be running locally + * * listening on port 3128 + * * expecting http requests + * * with a query token named url wrt the path urlraw + * which gives the actual url to fetch + * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + */ +function fetchweburlraw_run(toolcallid, toolname, obj) { + if (gToolsWorker.onmessage != null) { + // @ts-ignore + let newUrl = `${document['gMe'].tools.fetchProxyUrl}/urlraw?url=${encodeURIComponent(obj.url)}` + fetch(newUrl).then(resp => { + if (!resp.ok) { + throw new Error(`${resp.status}:${resp.statusText}`); + } + return resp.text() + }).then(data => { + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) + }).catch((err)=>{ + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) + }) + } +} + + +/** + * Setup fetch_web_url_raw for tool calling + * NOTE: Currently the logic is setup for the bundled simpleproxy.py + * @param {Object>} tcs + */ +async function fetchweburlraw_setup(tcs) { + // @ts-ignore + let got = await fetch(`${document["gMe"].tools.fetchProxyUrl}/aum?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ + if (resp.statusText != 'bharatavarshe') { + console.log("WARN:ToolJS:FetchWebUrlRaw:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") + return + } else { + console.log("INFO:ToolJS:FetchWebUrlRaw:Enabling...") + } + tcs["fetch_web_url_raw"] = { + "handler": fetchweburlraw_run, + "meta": fetchweburlraw_meta, + "result": "" + }; + }).catch(err=>console.log(`WARN:ToolJS:FetchWebUrlRaw:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) +} + + +let fetchweburltext_meta = { + "type": "function", + "function": { + "name": "fetch_web_url_text", + "description": "Fetch the requested web url through a proxy server and return its text content after stripping away the html tags as well as head, script, style, header, footer, nav blocks, in few seconds", + "parameters": { + "type": "object", + "properties": { + "url":{ + "type":"string", + "description":"url of the page that will be fetched from the internet and inturn unwanted stuff stripped from its contents to some extent" + } + }, + "required": ["url"] + } + } + } + + +/** + * Implementation of the fetch web url text logic. Dumb initial go. + * Expects a simple minded proxy server to be running locally + * * listening on port 3128 + * * expecting http requests + * * with a query token named url wrt urltext path, + * which gives the actual url to fetch + * * strips out head as well as any script, style, header, footer, nav and so blocks in body + * before returning remaining body contents. + * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + */ +function fetchweburltext_run(toolcallid, toolname, obj) { + if (gToolsWorker.onmessage != null) { + // @ts-ignore + let newUrl = `${document['gMe'].tools.fetchProxyUrl}/urltext?url=${encodeURIComponent(obj.url)}` + fetch(newUrl).then(resp => { + if (!resp.ok) { + throw new Error(`${resp.status}:${resp.statusText}`); + } + return resp.text() + }).then(data => { + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) + }).catch((err)=>{ + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) + }) + } +} + + +/** + * Setup fetch_web_url_text for tool calling + * NOTE: Currently the logic is setup for the bundled simpleproxy.py + * @param {Object>} tcs + */ +async function fetchweburltext_setup(tcs) { + // @ts-ignore + let got = await fetch(`${document["gMe"].tools.fetchProxyUrl}/aum?url=jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ + if (resp.statusText != 'bharatavarshe') { + console.log("WARN:ToolJS:FetchWebUrlText:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") + return + } else { + console.log("INFO:ToolJS:FetchWebUrlText:Enabling...") + } + tcs["fetch_web_url_text"] = { + "handler": fetchweburltext_run, + "meta": fetchweburltext_meta, + "result": "" + }; + }).catch(err=>console.log(`WARN:ToolJS:FetchWebUrlText:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) +} + + +/** + * @type {Object>} + */ +export let tc_switch = { + "run_javascript_function_code": { + "handler": js_run, + "meta": js_meta, + "result": "" + }, + "simple_calculator": { + "handler": calc_run, + "meta": calc_meta, + "result": "" + }, +} + + +/** + * Used to get hold of the web worker to use for running tool/function call related code + * Also to setup tool calls, which need to cross check things at runtime + * @param {Worker} toolsWorker + */ +export async function init(toolsWorker) { + gToolsWorker = toolsWorker + await fetchweburlraw_setup(tc_switch) + await fetchweburltext_setup(tc_switch) +} From 2ab014bbd0ecf011dd61c4f3d532c6f7b941cf81 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 15:54:47 +0530 Subject: [PATCH 143/187] SimpleChatTC:ToolCalling:Seprat out JSWebWorker and ProxyBasedWeb Remove the unneed (belonging to the other file) stuff from tooljs and toolweb files. Update tools manager to make use of the new toolweb module --- tools/server/public_simplechat/tooljs.mjs | 160 +-------------------- tools/server/public_simplechat/tools.mjs | 21 ++- tools/server/public_simplechat/toolweb.mjs | 90 +----------- 3 files changed, 25 insertions(+), 246 deletions(-) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index cfd216e3666b7..0e9ce61c3eb3e 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -1,5 +1,5 @@ //@ts-check -// DANGER DANGER DANGER - Simple and Stupid - Use from a discardable VM only +// ALERT - Simple Stupid flow - Using from a discardable VM is better // Helpers to handle tools/functions calling wrt // * javascript interpreter // * simple arithmatic calculator @@ -72,162 +72,6 @@ function calc_run(toolcallid, toolname, obj) { } -/** - * Send a message to Tools WebWorker's monitor in main thread directly - * @param {MessageEvent} mev - */ -function message_toolsworker(mev) { - // @ts-ignore - gToolsWorker.onmessage(mev) -} - - -let fetchweburlraw_meta = { - "type": "function", - "function": { - "name": "fetch_web_url_raw", - "description": "Fetch the requested web url through a proxy server and return the got content as is, in few seconds", - "parameters": { - "type": "object", - "properties": { - "url":{ - "type":"string", - "description":"url of the web page to fetch from the internet" - } - }, - "required": ["url"] - } - } - } - - -/** - * Implementation of the fetch web url raw logic. Dumb initial go. - * Expects a simple minded proxy server to be running locally - * * listening on port 3128 - * * expecting http requests - * * with a query token named url wrt the path urlraw - * which gives the actual url to fetch - * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful - * @param {string} toolcallid - * @param {string} toolname - * @param {any} obj - */ -function fetchweburlraw_run(toolcallid, toolname, obj) { - if (gToolsWorker.onmessage != null) { - // @ts-ignore - let newUrl = `${document['gMe'].tools.fetchProxyUrl}/urlraw?url=${encodeURIComponent(obj.url)}` - fetch(newUrl).then(resp => { - if (!resp.ok) { - throw new Error(`${resp.status}:${resp.statusText}`); - } - return resp.text() - }).then(data => { - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) - }).catch((err)=>{ - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) - }) - } -} - - -/** - * Setup fetch_web_url_raw for tool calling - * NOTE: Currently the logic is setup for the bundled simpleproxy.py - * @param {Object>} tcs - */ -async function fetchweburlraw_setup(tcs) { - // @ts-ignore - let got = await fetch(`${document["gMe"].tools.fetchProxyUrl}/aum?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ - if (resp.statusText != 'bharatavarshe') { - console.log("WARN:ToolJS:FetchWebUrlRaw:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") - return - } else { - console.log("INFO:ToolJS:FetchWebUrlRaw:Enabling...") - } - tcs["fetch_web_url_raw"] = { - "handler": fetchweburlraw_run, - "meta": fetchweburlraw_meta, - "result": "" - }; - }).catch(err=>console.log(`WARN:ToolJS:FetchWebUrlRaw:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) -} - - -let fetchweburltext_meta = { - "type": "function", - "function": { - "name": "fetch_web_url_text", - "description": "Fetch the requested web url through a proxy server and return its text content after stripping away the html tags as well as head, script, style, header, footer, nav blocks, in few seconds", - "parameters": { - "type": "object", - "properties": { - "url":{ - "type":"string", - "description":"url of the page that will be fetched from the internet and inturn unwanted stuff stripped from its contents to some extent" - } - }, - "required": ["url"] - } - } - } - - -/** - * Implementation of the fetch web url text logic. Dumb initial go. - * Expects a simple minded proxy server to be running locally - * * listening on port 3128 - * * expecting http requests - * * with a query token named url wrt urltext path, - * which gives the actual url to fetch - * * strips out head as well as any script, style, header, footer, nav and so blocks in body - * before returning remaining body contents. - * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful - * @param {string} toolcallid - * @param {string} toolname - * @param {any} obj - */ -function fetchweburltext_run(toolcallid, toolname, obj) { - if (gToolsWorker.onmessage != null) { - // @ts-ignore - let newUrl = `${document['gMe'].tools.fetchProxyUrl}/urltext?url=${encodeURIComponent(obj.url)}` - fetch(newUrl).then(resp => { - if (!resp.ok) { - throw new Error(`${resp.status}:${resp.statusText}`); - } - return resp.text() - }).then(data => { - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) - }).catch((err)=>{ - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) - }) - } -} - - -/** - * Setup fetch_web_url_text for tool calling - * NOTE: Currently the logic is setup for the bundled simpleproxy.py - * @param {Object>} tcs - */ -async function fetchweburltext_setup(tcs) { - // @ts-ignore - let got = await fetch(`${document["gMe"].tools.fetchProxyUrl}/aum?url=jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ - if (resp.statusText != 'bharatavarshe') { - console.log("WARN:ToolJS:FetchWebUrlText:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") - return - } else { - console.log("INFO:ToolJS:FetchWebUrlText:Enabling...") - } - tcs["fetch_web_url_text"] = { - "handler": fetchweburltext_run, - "meta": fetchweburltext_meta, - "result": "" - }; - }).catch(err=>console.log(`WARN:ToolJS:FetchWebUrlText:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) -} - - /** * @type {Object>} */ @@ -252,6 +96,4 @@ export let tc_switch = { */ export async function init(toolsWorker) { gToolsWorker = toolsWorker - await fetchweburlraw_setup(tc_switch) - await fetchweburltext_setup(tc_switch) } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 2b4237258e332..23eb7e35e8323 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -1,30 +1,42 @@ //@ts-check -// DANGER DANGER DANGER - Simple and Stupid - Use from a discardable VM only +// ALERT - Simple Stupid flow - Using from a discardable VM is better // Helpers to handle tools/functions calling in a direct and dangerous way // by Humans for All // import * as tjs from './tooljs.mjs' +import * as tweb from './toolweb.mjs' let gToolsWorker = new Worker('./toolsworker.mjs', { type: 'module' }); /** + * Maintain currently available tool/function calls * @type {Object>} */ export let tc_switch = {} + export async function init() { - return tjs.init(gToolsWorker).then(()=>{ - let toolNames = [] + /** + * @type {string[]} + */ + let toolNames = [] + await tjs.init(gToolsWorker).then(()=>{ for (const key in tjs.tc_switch) { tc_switch[key] = tjs.tc_switch[key] toolNames.push(key) } - return toolNames }) + let tNs = await tweb.init(gToolsWorker) + for (const key in tNs) { + tc_switch[key] = tNs[key] + toolNames.push(key) + } + return toolNames } + export function meta() { let tools = [] for (const key in tc_switch) { @@ -33,6 +45,7 @@ export function meta() { return tools } + /** * Setup the callback that will be called when ever message * is recieved from the Tools Web Worker. diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index cfd216e3666b7..351b41ff48ab4 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -1,8 +1,6 @@ //@ts-check -// DANGER DANGER DANGER - Simple and Stupid - Use from a discardable VM only -// Helpers to handle tools/functions calling wrt -// * javascript interpreter -// * simple arithmatic calculator +// ALERT - Simple Stupid flow - Using from a discardable VM is better +// Helpers to handle tools/functions calling related to web access // by Humans for All // @@ -10,68 +8,6 @@ let gToolsWorker = /** @type{Worker} */(/** @type {unknown} */(null)); -let js_meta = { - "type": "function", - "function": { - "name": "run_javascript_function_code", - "description": "Runs given code using eval within a web worker context in a browser's javascript environment and returns the console.log outputs of the execution after few seconds", - "parameters": { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "The code that will be run using eval within a web worker in the browser's javascript interpreter environment." - } - }, - "required": ["code"] - } - } - } - - -/** - * Implementation of the javascript interpretor logic. Minimal skeleton for now. - * ALERT: Has access to the javascript web worker environment and can mess with it and beyond - * @param {string} toolcallid - * @param {string} toolname - * @param {any} obj - */ -function js_run(toolcallid, toolname, obj) { - gToolsWorker.postMessage({ id: toolcallid, name: toolname, code: obj["code"]}) -} - - -let calc_meta = { - "type": "function", - "function": { - "name": "simple_calculator", - "description": "Calculates the provided arithmatic expression using console.log within a web worker of a browser's javascript interpreter environment and returns the output of the execution once it is done in few seconds", - "parameters": { - "type": "object", - "properties": { - "arithexpr":{ - "type":"string", - "description":"The arithmatic expression that will be calculated by passing it to console.log of a browser's javascript interpreter." - } - }, - "required": ["arithexpr"] - } - } - } - - -/** - * Implementation of the simple calculator logic. Minimal skeleton for now. - * ALERT: Has access to the javascript web worker environment and can mess with it and beyond - * @param {string} toolcallid - * @param {string} toolname - * @param {any} obj - */ -function calc_run(toolcallid, toolname, obj) { - gToolsWorker.postMessage({ id: toolcallid, name: toolname, code: `console.log(${obj["arithexpr"]})`}) -} - - /** * Send a message to Tools WebWorker's monitor in main thread directly * @param {MessageEvent} mev @@ -228,30 +164,18 @@ async function fetchweburltext_setup(tcs) { } -/** - * @type {Object>} - */ -export let tc_switch = { - "run_javascript_function_code": { - "handler": js_run, - "meta": js_meta, - "result": "" - }, - "simple_calculator": { - "handler": calc_run, - "meta": calc_meta, - "result": "" - }, -} - - /** * Used to get hold of the web worker to use for running tool/function call related code * Also to setup tool calls, which need to cross check things at runtime * @param {Worker} toolsWorker */ export async function init(toolsWorker) { + /** + * @type {Object>} tcs + */ + let tc_switch = {} gToolsWorker = toolsWorker await fetchweburlraw_setup(tc_switch) await fetchweburltext_setup(tc_switch) + return tc_switch } From 21ac08c78d36d375fea4c4e4c11ffdf3ba45b2cd Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 19:13:30 +0530 Subject: [PATCH 144/187] SimpleChatTC:ToolCall:SearchWebText using UrlText Initial go at implementing a web search tool call, which uses the existing UrlText support of the bundled simpleproxy.py. It allows user to control the search engine to use, by allowing them to set the search engine url template. The logic comes with search engine url template strings for duckduckgo, brave, bing and google. With duckduckgo set by default. --- tools/server/public_simplechat/simplechat.js | 13 +++ tools/server/public_simplechat/toolweb.mjs | 97 +++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index c0f0ac4544f82..9992ec247469f 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1063,6 +1063,18 @@ class MultiChatUI { } +/** + * Few web search engine url template strings. + * The SEARCHWORDS keyword will get replaced by the actual user specified search words at runtime. + */ +const SearchURLS = { + duckduckgo: "https://duckduckgo.com/html/?q=SEARCHWORDS", + bing: "https://www.bing.com/search?q=SEARCHWORDS", // doesnt seem to like google chrome clients in particular + brave: "https://search.brave.com/search?q=SEARCHWORDS", + google: "https://www.google.com/search?q=SEARCHWORDS", // doesnt seem to like any client in general +} + + class Me { constructor() { @@ -1072,6 +1084,7 @@ class Me { this.tools = { enabled: false, fetchProxyUrl: "http://127.0.0.1:3128", + searchUrl: SearchURLS.duckduckgo, toolNames: /** @type {Array} */([]), /** * Control how many seconds to wait before auto triggering tool call or its response submission. diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 351b41ff48ab4..6d64099c5d392 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -1,6 +1,7 @@ //@ts-check // ALERT - Simple Stupid flow - Using from a discardable VM is better // Helpers to handle tools/functions calling related to web access +// which work in sync with the bundled simpleproxy.py server logic. // by Humans for All // @@ -18,6 +19,15 @@ function message_toolsworker(mev) { } +/** + * Retrieve a member of the global Me instance + */ +function get_gme() { + return (/** @type {Object>} */(/** @type {unknown} */(document)))['gMe'] + //return (/** @type {Object>} */(/** @type {unknown} */(document)))['gMe'][key] +} + + let fetchweburlraw_meta = { "type": "function", "function": { @@ -40,7 +50,7 @@ let fetchweburlraw_meta = { /** * Implementation of the fetch web url raw logic. Dumb initial go. * Expects a simple minded proxy server to be running locally - * * listening on port 3128 + * * listening on a configured port * * expecting http requests * * with a query token named url wrt the path urlraw * which gives the actual url to fetch @@ -112,7 +122,7 @@ let fetchweburltext_meta = { /** * Implementation of the fetch web url text logic. Dumb initial go. * Expects a simple minded proxy server to be running locally - * * listening on port 3128 + * * listening on a configured port * * expecting http requests * * with a query token named url wrt urltext path, * which gives the actual url to fetch @@ -164,6 +174,88 @@ async function fetchweburltext_setup(tcs) { } +// +// Search Web Text +// + + +let searchwebtext_meta = { + "type": "function", + "function": { + "name": "search_web_text", + "description": "search web for given words and return the plain text content after stripping the html tags as well as head, script, style, header, footer, nav blocks from got html result page, in few seconds", + "parameters": { + "type": "object", + "properties": { + "words":{ + "type":"string", + "description":"the words to search for on the web" + } + }, + "required": ["words"] + } + } + } + + +/** + * Implementation of the search web text logic. Initial go. + * Expects simpleproxy.py server to be running locally + * * listening on a configured port + * * expecting http requests + * * with a query token named url wrt urltext path, + * which gives the actual url to fetch + * * strips out head as well as any script, style, header, footer, nav and so blocks in body + * before returning remaining body contents. + * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + */ +function searchwebtext_run(toolcallid, toolname, obj) { + if (gToolsWorker.onmessage != null) { + /** @type {string} */ + let searchUrl = get_gme().tools.searchUrl; + searchUrl = searchUrl.replace("SEARCHWORDS", encodeURIComponent(obj.words)) + let newUrl = `${get_gme().tools.fetchProxyUrl}/urltext?url=${encodeURIComponent(searchUrl)}` + fetch(newUrl).then(resp => { + if (!resp.ok) { + throw new Error(`${resp.status}:${resp.statusText}`); + } + return resp.text() + }).then(data => { + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) + }).catch((err)=>{ + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) + }) + } +} + + +/** + * Setup search_web_text for tool calling + * NOTE: Currently the logic is setup for the bundled simpleproxy.py + * @param {Object>} tcs + */ +async function searchwebtext_setup(tcs) { + // @ts-ignore + let got = await fetch(`${document["gMe"].tools.fetchProxyUrl}/aum?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ + if (resp.statusText != 'bharatavarshe') { + console.log("WARN:ToolJS:SearchWebText:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") + return + } else { + console.log("INFO:ToolJS:SearchWebText:Enabling...") + } + tcs["search_web_text"] = { + "handler": searchwebtext_run, + "meta": searchwebtext_meta, + "result": "" + }; + }).catch(err=>console.log(`WARN:ToolJS:SearchWebText:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) +} + + + /** * Used to get hold of the web worker to use for running tool/function call related code * Also to setup tool calls, which need to cross check things at runtime @@ -177,5 +269,6 @@ export async function init(toolsWorker) { gToolsWorker = toolsWorker await fetchweburlraw_setup(tc_switch) await fetchweburltext_setup(tc_switch) + await searchwebtext_setup(tc_switch) return tc_switch } From 303d1f333d53d534c20e856751b987fe2d7cedc5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 21:12:48 +0530 Subject: [PATCH 145/187] SimpleChatTC:ToolCallWeby: Cleanup the toolweb module flow Avoid code duplication, by creating helpers for setup and toolcall. Also send indication of the path that will be used, when checking for simpleproxy.py server to be running at runtime setup. --- tools/server/public_simplechat/toolweb.mjs | 174 +++++++++++---------- 1 file changed, 88 insertions(+), 86 deletions(-) diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 6d64099c5d392..11f4cd6b3f31c 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -20,14 +20,73 @@ function message_toolsworker(mev) { /** - * Retrieve a member of the global Me instance + * Retrieve the global Me instance */ function get_gme() { return (/** @type {Object>} */(/** @type {unknown} */(document)))['gMe'] - //return (/** @type {Object>} */(/** @type {unknown} */(document)))['gMe'][key] } +/** + * Helper http get logic wrt the bundled SimpleProxy server, + * which helps execute a given proxy dependent tool call. + * Expects the simple minded proxy server to be running locally + * * listening on a configured port + * * expecting http requests + * * with a predefined query token and value wrt a predefined path + * NOTE: Initial go, handles textual data type. + * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + * @param {string} path + * @param {string} qkey + * @param {string} qvalue + */ +function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { + if (gToolsWorker.onmessage != null) { + let newUrl = `${get_gme().tools.fetchProxyUrl}/${path}?${qkey}=${qvalue}` + fetch(newUrl).then(resp => { + if (!resp.ok) { + throw new Error(`${resp.status}:${resp.statusText}`); + } + return resp.text() + }).then(data => { + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) + }).catch((err)=>{ + message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) + }) + } +} + + +/** + * Setup a proxy server dependent tool call + * NOTE: Currently the logic is setup for the bundled simpleproxy.py + * @param {string} tag + * @param {string} tcPath + * @param {string} tcName + * @param {{ [x: string]: any; }} tcsData + * @param {Object>} tcs + */ +async function proxyserver_tc_setup(tag, tcPath, tcName, tcsData, tcs) { + await fetch(`${get_gme().tools.fetchProxyUrl}/aum?url=${tcPath}.jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ + if (resp.statusText != 'bharatavarshe') { + console.log(`WARN:ToolWeb:${tag}:Dont forget to run the bundled local.tools/simpleproxy.py to enable me`) + return + } else { + console.log(`INFO:ToolWeb:${tag}:Enabling...`) + } + tcs[tcName] = tcsData; + }).catch(err=>console.log(`WARN:ToolWeb:${tag}:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) +} + + +// +// Fetch Web Url Raw +// + + let fetchweburlraw_meta = { "type": "function", "function": { @@ -48,7 +107,7 @@ let fetchweburlraw_meta = { /** - * Implementation of the fetch web url raw logic. Dumb initial go. + * Implementation of the fetch web url raw logic. * Expects a simple minded proxy server to be running locally * * listening on a configured port * * expecting http requests @@ -60,20 +119,7 @@ let fetchweburlraw_meta = { * @param {any} obj */ function fetchweburlraw_run(toolcallid, toolname, obj) { - if (gToolsWorker.onmessage != null) { - // @ts-ignore - let newUrl = `${document['gMe'].tools.fetchProxyUrl}/urlraw?url=${encodeURIComponent(obj.url)}` - fetch(newUrl).then(resp => { - if (!resp.ok) { - throw new Error(`${resp.status}:${resp.statusText}`); - } - return resp.text() - }).then(data => { - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) - }).catch((err)=>{ - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) - }) - } + return proxyserver_get_1arg(toolcallid, toolname, obj, 'urlraw', 'url', encodeURIComponent(obj.url)); } @@ -83,23 +129,19 @@ function fetchweburlraw_run(toolcallid, toolname, obj) { * @param {Object>} tcs */ async function fetchweburlraw_setup(tcs) { - // @ts-ignore - let got = await fetch(`${document["gMe"].tools.fetchProxyUrl}/aum?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ - if (resp.statusText != 'bharatavarshe') { - console.log("WARN:ToolJS:FetchWebUrlRaw:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") - return - } else { - console.log("INFO:ToolJS:FetchWebUrlRaw:Enabling...") - } - tcs["fetch_web_url_raw"] = { - "handler": fetchweburlraw_run, - "meta": fetchweburlraw_meta, - "result": "" - }; - }).catch(err=>console.log(`WARN:ToolJS:FetchWebUrlRaw:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) + return proxyserver_tc_setup('FetchWebUrlRaw', 'urlraw', 'fetch_web_url_raw', { + "handler": fetchweburlraw_run, + "meta": fetchweburlraw_meta, + "result": "" + }, tcs); } +// +// Fetch Web Url Text +// + + let fetchweburltext_meta = { "type": "function", "function": { @@ -120,7 +162,7 @@ let fetchweburltext_meta = { /** - * Implementation of the fetch web url text logic. Dumb initial go. + * Implementation of the fetch web url text logic. * Expects a simple minded proxy server to be running locally * * listening on a configured port * * expecting http requests @@ -134,20 +176,7 @@ let fetchweburltext_meta = { * @param {any} obj */ function fetchweburltext_run(toolcallid, toolname, obj) { - if (gToolsWorker.onmessage != null) { - // @ts-ignore - let newUrl = `${document['gMe'].tools.fetchProxyUrl}/urltext?url=${encodeURIComponent(obj.url)}` - fetch(newUrl).then(resp => { - if (!resp.ok) { - throw new Error(`${resp.status}:${resp.statusText}`); - } - return resp.text() - }).then(data => { - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) - }).catch((err)=>{ - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) - }) - } + return proxyserver_get_1arg(toolcallid, toolname, obj, 'urltext', 'url', encodeURIComponent(obj.url)); } @@ -157,20 +186,11 @@ function fetchweburltext_run(toolcallid, toolname, obj) { * @param {Object>} tcs */ async function fetchweburltext_setup(tcs) { - // @ts-ignore - let got = await fetch(`${document["gMe"].tools.fetchProxyUrl}/aum?url=jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ - if (resp.statusText != 'bharatavarshe') { - console.log("WARN:ToolJS:FetchWebUrlText:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") - return - } else { - console.log("INFO:ToolJS:FetchWebUrlText:Enabling...") - } - tcs["fetch_web_url_text"] = { - "handler": fetchweburltext_run, - "meta": fetchweburltext_meta, - "result": "" - }; - }).catch(err=>console.log(`WARN:ToolJS:FetchWebUrlText:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) + return proxyserver_tc_setup('FetchWebUrlText', 'urltext', 'fetch_web_url_text', { + "handler": fetchweburltext_run, + "meta": fetchweburltext_meta, + "result": "" + }, tcs); } @@ -200,6 +220,7 @@ let searchwebtext_meta = { /** * Implementation of the search web text logic. Initial go. + * Builds on urltext path of the bundled simpleproxy.py. * Expects simpleproxy.py server to be running locally * * listening on a configured port * * expecting http requests @@ -216,18 +237,8 @@ function searchwebtext_run(toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { /** @type {string} */ let searchUrl = get_gme().tools.searchUrl; - searchUrl = searchUrl.replace("SEARCHWORDS", encodeURIComponent(obj.words)) - let newUrl = `${get_gme().tools.fetchProxyUrl}/urltext?url=${encodeURIComponent(searchUrl)}` - fetch(newUrl).then(resp => { - if (!resp.ok) { - throw new Error(`${resp.status}:${resp.statusText}`); - } - return resp.text() - }).then(data => { - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) - }).catch((err)=>{ - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) - }) + searchUrl = searchUrl.replace("SEARCHWORDS", encodeURIComponent(obj.words)); + return proxyserver_get_1arg(toolcallid, toolname, obj, 'urltext', 'url', encodeURIComponent(searchUrl)); } } @@ -238,20 +249,11 @@ function searchwebtext_run(toolcallid, toolname, obj) { * @param {Object>} tcs */ async function searchwebtext_setup(tcs) { - // @ts-ignore - let got = await fetch(`${document["gMe"].tools.fetchProxyUrl}/aum?url=jambudweepe.multiverse.987654321123456789`).then(resp=>{ - if (resp.statusText != 'bharatavarshe') { - console.log("WARN:ToolJS:SearchWebText:Dont forget to run the bundled local.tools/simpleproxy.py to enable me") - return - } else { - console.log("INFO:ToolJS:SearchWebText:Enabling...") - } - tcs["search_web_text"] = { - "handler": searchwebtext_run, - "meta": searchwebtext_meta, - "result": "" - }; - }).catch(err=>console.log(`WARN:ToolJS:SearchWebText:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) + return proxyserver_tc_setup('SearchWebText', 'urltext', 'search_web_text', { + "handler": searchwebtext_run, + "meta": searchwebtext_meta, + "result": "" + }, tcs); } From 251eecfab6969bd1b824cdbc444320c1d2563e1b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 23:12:06 +0530 Subject: [PATCH 146/187] SimpleChatTC:WebSearchPlus: Update readme, Wikipedia in allowed If using wikipedia or so, remember to have sufficient context window in general wrt the ai engine as well as wrt the handshake / chat end point. --- .../local.tools/simpleproxy.json | 1 + tools/server/public_simplechat/readme.md | 43 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json index 949b7e014d5af..d68878199aed1 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.json +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -1,5 +1,6 @@ { "allowed.domains": [ + ".*\\.wikipedia\\.org$", ".*\\.bing\\.com$", "^www\\.bing\\.com$", ".*\\.yahoo\\.com$", diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 6576e26914c90..30c4a2271e753 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -34,8 +34,9 @@ console. Parallely some of the directly useful to end-user settings can also be settings ui. For GenAi/LLM models supporting tool / function calling, allows one to interact with them and explore use of -ai driven augmenting of the knowledge used for generating answers by using the predefined tools/functions. -The end user is provided control over tool calling and response submitting. +ai driven augmenting of the knowledge used for generating answers as well as for cross checking ai generated +answers logically / programatically and by checking with other sources and lot more by making using of the +predefined tools / functions. The end user is provided control over tool calling and response submitting. NOTE: Current web service api doesnt expose the model context length directly, so client logic doesnt provide any adaptive culling of old messages nor of replacing them with summary of their content etal. However there @@ -79,13 +80,14 @@ remember to * use a GenAi/LLM model which supports tool calling. -* if fetch web url / page tool call is needed remember to run the bundled local.tools/simpleproxy.py +* if fetch web page or web search tool call is needed remember to run bundled local.tools/simpleproxy.py helper along with its config file, before using/loading this client ui through a browser * cd tools/server/public_simplechat/local.tools; python3 ./simpleproxy.py --config simpleproxy.json - * remember that this is a relatively dumb proxy logic along with optional stripping of scripts / styles - / headers / footers /..., Be careful if trying to fetch web pages, and use it only with known safe sites. + * remember that this is a relatively minimal dumb proxy logic along with optional stripping of non textual + content like head, scripts, styles, headers, footers, ... Be careful when accessing web through this and + use it only with known safe sites. * it allows one to specify a white list of allowed.domains, look into local.tools/simpleproxy.json @@ -226,6 +228,8 @@ It is attached to the document object. Some of these can also be updated using t * fetchProxyUrl - specify the address for the running instance of bundled local.tools/simpleproxy.py + * searchUrl - specify the search engine's search url template along with the tag SEARCHWORDS in place where the search words should be substituted at runtime. + * auto - the amount of time in seconds to wait before the tool call request is auto triggered and generated response is auto submitted back. setting this value to 0 (default), disables auto logic, so that end user can review the tool calls requested by ai and if needed even modify them, before triggering/executing them as well as review and modify results generated by the tool call, before submitting them back to the ai. @@ -362,7 +366,8 @@ ALERT: The simple minded way in which this is implemented, it provides some mini mechanism like running ai generated code in web workers and restricting web access to user specified whitelist and so, but it can still be dangerous in the worst case, So remember to verify all the tool calls requested and the responses generated manually to ensure -everything is fine, during interaction with ai models with tools support. +everything is fine, during interaction with ai models with tools support. One could also +always run this from a discardable vm, just in case if one wants to be extra cautious. #### Builtin Tools @@ -388,15 +393,18 @@ requests and generated responses when using tool calling. Related logic tries to strip html response of html tags and also head, script, style, header,footer, nav, ... blocks. -fetch_web_url_raw/text and family works along with a corresponding simple local web proxy (/caching -in future) server logic, this helps bypass the CORS restrictions applied if trying to directly fetch -from the browser js runtime environment. +* search_web_text - search for the specified words using the configured search engine and return the +plain textual content from the search result page. + +the above set of web related tool calls work by handshaking with a bundled simple local web proxy +(/caching in future) server logic, this helps bypass the CORS restrictions applied if trying to +directly fetch from the browser js runtime environment. Depending on the path specified wrt the proxy server, it executes the corresponding logic. Like if urltext path is used (and not urlraw), the logic in addition to fetching content from given url, it -tries to convert html content into equivalent text content to some extent in a simple minded manner -by dropping head block as well as all scripts/styles/footers/headers/nav blocks and inturn dropping -the html tags. +tries to convert html content into equivalent plain text content to some extent in a simple minded +manner by dropping head block as well as all scripts/styles/footers/headers/nav blocks and inturn +dropping the html tags. The client ui logic does a simple check to see if the bundled simpleproxy is running at specified fetchProxyUrl before enabling these web and related tool calls. @@ -414,10 +422,10 @@ The bundled simple proxy so that websites will hopefully respect the request rather than blindly rejecting it as coming from a non-browser entity. -In future it can be extended to help with other relatively simple yet useful tool calls like search_web, -data/documents_store and so. +In future it can be further extended to help with other relatively simple yet useful tool calls like +data / documents_store, fetch_rss and so. - * for now search_web can be indirectly achieved using fetch_web_url_text/raw. + * for now fetch_rss can be indirectly achieved using fetch_web_url_raw. #### Extending with new tools @@ -440,6 +448,9 @@ Update the tc_switch to include a object entry for the tool, which inturn includ It should pass these along to the tools web worker, if used. * the result key (was used previously, may use in future, but for now left as is) +Look into tooljs.mjs for javascript and inturn web worker based tool calls and toolweb.mjs +for the simpleproxy.py based tool calls. + #### OLD: Mapping tool calls and responses to normal assistant - user chat flow Instead of maintaining tool_call request and resultant response in logically seperate parallel @@ -480,7 +491,7 @@ Handle reasoning/thinking responses from ai models. Handle multimodal handshaking with ai models. -Add search_web and documents|data_store tool calling, through the simpleproxy.py if and where needed. +Add fetch_rss and documents|data_store tool calling, through the simpleproxy.py if and where needed. ### Debuging the handshake From 16834c1210317d5c36b28cea4f474f8e37726527 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 23:25:05 +0530 Subject: [PATCH 147/187] SimpleChatTC:ToolCallResponseTimeout: Allow end user to control Moved it into Me->tools, so that end user can modify the same as required from the settings ui. TODO: Currently, if tc response is got after a tool call timed out and user submitted default timed out error response, the delayed actual response when it is got may overwrite any new content in user query box, this needs to be tackled. --- tools/server/public_simplechat/readme.md | 4 ++++ tools/server/public_simplechat/simplechat.js | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 30c4a2271e753..0fd69eec92021 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -230,6 +230,10 @@ It is attached to the document object. Some of these can also be updated using t * searchUrl - specify the search engine's search url template along with the tag SEARCHWORDS in place where the search words should be substituted at runtime. + * toolCallResponseTimeoutMS - specifies the time (in msecs) for which the logic should wait for a tool call to respond + before a default timed out error response is generated and control given back to end user, for them to decide whether + to submit the error response or wait for actual tool call response further. + * auto - the amount of time in seconds to wait before the tool call request is auto triggered and generated response is auto submitted back. setting this value to 0 (default), disables auto logic, so that end user can review the tool calls requested by ai and if needed even modify them, before triggering/executing them as well as review and modify results generated by the tool call, before submitting them back to the ai. diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 9992ec247469f..8758fa6366ade 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -692,7 +692,6 @@ class MultiChatUI { this.curChatId = ""; this.TimePeriods = { - ToolCallResponseTimeout: 10000, ToolCallAutoTimeUnit: 1000 } @@ -978,7 +977,7 @@ class MultiChatUI { this.timers.toolcallResponseTimeout = setTimeout(() => { this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, `Tool/Function call ${toolname} taking too much time, aborting...`); this.ui_reset_userinput(false) - }, this.TimePeriods.ToolCallResponseTimeout) + }, gMe.tools.toolCallResponseTimeoutMS) } } @@ -1086,6 +1085,11 @@ class Me { fetchProxyUrl: "http://127.0.0.1:3128", searchUrl: SearchURLS.duckduckgo, toolNames: /** @type {Array} */([]), + /** + * Control how many milliseconds to wait for tool call to respond, before generating a timed out + * error response and giving control back to end user. + */ + toolCallResponseTimeoutMS: 20000, /** * Control how many seconds to wait before auto triggering tool call or its response submission. * A value of 0 is treated as auto triggering disable. From 3c03ec95df5c9c05060ccb9d56cf85acf8e59b99 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 09:09:05 +0530 Subject: [PATCH 148/187] SimpleChatTC:SimpleProxy:LoadConfig ProcessArgs cleanup - initial Now both follow a similar mechanism and do the following * exit on finding any issue, so that things are in a known state from usage perspective, without any confusion/overlook * check if the cmdlineArgCmd/configCmd being processed is a known one or not. * check value of the cmd is of the expected type * have a generic flow which can accomodate more cmds in future in a simple way --- .../local.tools/simpleproxy.py | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 86f5aa0e7b60f..6658d26dd405b 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -32,6 +32,13 @@ 'server': None } +gConfigType = { + '--port': 'int', + '--config': 'str', + '--debug': 'bool', + '--allowed.domains': 'list' +} + class ProxyHandler(http.server.BaseHTTPRequestHandler): """ @@ -318,7 +325,18 @@ def load_config(): with open(gMe['--config']) as f: cfg = json.load(f) for k in cfg: - gMe[f"--{k}"] = cfg[k] + try: + cArg = f"--{k}" + aTypeCheck = gConfigType[cArg] + aValue = cfg[k] + aType = type(aValue).__name__ + if aType != aTypeCheck: + print(f"ERRR:LoadConfig:{k}:expected type [{aTypeCheck}] got type [{aType}]") + exit(112) + gMe[cArg] = aValue + except KeyError: + print(f"ERRR:LoadConfig:{k}:UnknownCommand") + exit(113) def process_args(args: list[str]): @@ -327,38 +345,25 @@ def process_args(args: list[str]): Helper to process command line arguments """ global gMe - gMe['INTERNAL.ProcessArgs.Malformed'] = [] - gMe['INTERNAL.ProcessArgs.Unknown'] = [] iArg = 1 while iArg < len(args): cArg = args[iArg] if (not cArg.startswith("--")): - gMe['INTERNAL.ProcessArgs.Malformed'].append(cArg) - print(f"WARN:ProcessArgs:{iArg}:IgnoringMalformedCommandOr???:{cArg}") + print(f"ERRR:ProcessArgs:{iArg}:{cArg}:MalformedCommandOr???") + exit(101) + try: + aTypeCheck = gConfigType[cArg] iArg += 1 - continue - match cArg: - case '--port': - iArg += 1 - gMe[cArg] = int(args[iArg]) - iArg += 1 - case '--config': - iArg += 1 - gMe[cArg] = args[iArg] - iArg += 1 - load_config() - case '--allowed.domains': - iArg += 1 - gMe[cArg] = ast.literal_eval(args[iArg]) - iArg += 1 - case '--debug': - iArg += 1 - gMe[cArg] = ast.literal_eval(args[iArg]) - iArg += 1 - case _: - gMe['INTERNAL.ProcessArgs.Unknown'].append(cArg) - print(f"WARN:ProcessArgs:{iArg}:IgnoringUnknownCommand:{cArg}") - iArg += 1 + aValue = ast.literal_eval(args[iArg]) + aType = type(aValue).__name__ + if aType != aTypeCheck: + print(f"ERRR:ProcessArgs:{iArg}:{cArg}:expected type [{aTypeCheck}] got type [{aType}]") + exit(102) + gMe[cArg] = aValue + iArg += 1 + except KeyError: + print(f"ERRR:ProcessArgs:{iArg}:{cArg}:UnknownCommand") + exit(103) print(gMe) From 6ed43e03503d93e20999ec52611b1c34433b9b7f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 09:43:49 +0530 Subject: [PATCH 149/187] SimpleChatTC:SimpleProxy: Prg Parameters handling cleanup - next Ensure load_config gets called on encountering --config in cmdline, so that the user has control over whether cmdline or config file will decide the final value of any given parameter. Ensure that str type values in cmdline are picked up directly, without running them through ast.literal_eval, bcas otherwise one will have to ensure throught the cmdline arg mechanism that string quote is retained for literal_eval Have the """ function note/description below def line immidiately so that it is interpreted as a function description. --- .../local.tools/simpleproxy.py | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 6658d26dd405b..4ebaf83182854 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -325,6 +325,7 @@ def load_config(): with open(gMe['--config']) as f: cfg = json.load(f) for k in cfg: + print(f"DBUG:LoadConfig:{k}") try: cArg = f"--{k}" aTypeCheck = gConfigType[cArg] @@ -340,10 +341,17 @@ def load_config(): def process_args(args: list[str]): - import ast """ - Helper to process command line arguments + Helper to process command line arguments. + + Flow setup below such that + * location of --config in commandline will decide whether command line or config file will get + priority wrt setting program parameters. + * str type values in cmdline are picked up directly, without running them through ast.literal_eval, + bcas otherwise one will have to ensure throught the cmdline arg mechanism that string quote is + retained for literal_eval """ + import ast global gMe iArg = 1 while iArg < len(args): @@ -351,16 +359,20 @@ def process_args(args: list[str]): if (not cArg.startswith("--")): print(f"ERRR:ProcessArgs:{iArg}:{cArg}:MalformedCommandOr???") exit(101) + print(f"DBUG:ProcessArgs:{iArg}:{cArg}") try: aTypeCheck = gConfigType[cArg] - iArg += 1 - aValue = ast.literal_eval(args[iArg]) - aType = type(aValue).__name__ - if aType != aTypeCheck: - print(f"ERRR:ProcessArgs:{iArg}:{cArg}:expected type [{aTypeCheck}] got type [{aType}]") - exit(102) + aValue = args[iArg+1] + if aTypeCheck != 'str': + aValue = ast.literal_eval(aValue) + aType = type(aValue).__name__ + if aType != aTypeCheck: + print(f"ERRR:ProcessArgs:{iArg}:{cArg}:expected type [{aTypeCheck}] got type [{aType}]") + exit(102) gMe[cArg] = aValue - iArg += 1 + iArg += 2 + if cArg == '--config': + load_config() except KeyError: print(f"ERRR:ProcessArgs:{iArg}:{cArg}:UnknownCommand") exit(103) From 945dedeed1d3b50d594651fff4865a1fe21a9f22 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 10:32:19 +0530 Subject: [PATCH 150/187] SimpleChatTC:SimpleProxy:BearerInsecure a needed config Add a config entry called bearer.insecure which will contain a token used for bearer auth of http requests Make bearer.insecure and allowed.domains as needed configs, and exit program if they arent got through cmdline or config file. --- .../public_simplechat/local.tools/simpleproxy.json | 3 ++- .../server/public_simplechat/local.tools/simpleproxy.py | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json index d68878199aed1..a4ea4305f045d 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.json +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -12,5 +12,6 @@ "^duckduckgo\\.com$", ".*\\.google\\.com$", "^google\\.com$" - ] + ], + "bearer.insecure": "NeverSecure" } diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 4ebaf83182854..f2d5b52722ffb 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -36,9 +36,12 @@ '--port': 'int', '--config': 'str', '--debug': 'bool', - '--allowed.domains': 'list' + '--allowed.domains': 'list', + '--bearer.insecure': 'str' } +gConfigNeeded = [ '--allowed.domains', '--bearer.insecure' ] + class ProxyHandler(http.server.BaseHTTPRequestHandler): """ @@ -377,6 +380,10 @@ def process_args(args: list[str]): print(f"ERRR:ProcessArgs:{iArg}:{cArg}:UnknownCommand") exit(103) print(gMe) + for k in gConfigNeeded: + if gMe.get(k) == None: + print(f"ERRR:ProcessArgs:{k}:missing, did you forget to pass the config file...") + exit(104) def run(): From 0083efd09be16ff3157c1e09dc3964ef89211f19 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 11:00:16 +0530 Subject: [PATCH 151/187] SimpleChatTC:SimpleProxy: Check for bearer authorization As noted in the comments in code, this is a very insecure flow for now. --- .../local.tools/simpleproxy.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index f2d5b52722ffb..67b2741542415 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -13,6 +13,9 @@ # * any request to aum path is used to respond with a predefined text response # which can help identify this server, in a simple way. # +# Expects a Bearer authorization line in the http header of the requests got. +# HOWEVER DO KEEP IN MIND THAT ITS A VERY INSECURE IMPLEMENTATION, AT BEST +# import sys @@ -67,12 +70,33 @@ def send_error(self, code: int, message: str | None = None, explain: str | None self.send_response(code, message) self.send_headers_common() + def auth_check(self): + """ + Simple Bearer authorization + ALERT: For multiple reasons, this is a very insecure implementation. + """ + authline = self.headers['Authorization'] + if authline == None: + return { 'AllOk': False, 'Msg': "No auth line" } + authlineA = authline.strip().split(' ') + if len(authlineA) != 2: + return { 'AllOk': False, 'Msg': "Invalid auth line" } + if authlineA[0] != 'Bearer': + return { 'AllOk': False, 'Msg': "Invalid auth type" } + if authlineA[1] != gMe['--bearer.insecure']: + return { 'AllOk': False, 'Msg': "Invalid auth" } + return { 'AllOk': True, 'Msg': "Auth Ok" } + def do_GET(self): """ Handle GET requests """ print(f"\n\n\nDBUG:ProxyHandler:GET:{self.address_string()}:{self.path}") print(f"DBUG:PH:Get:Headers:{self.headers}") + acGot = self.auth_check() + if not acGot['AllOk']: + self.send_error(400, f"WARN:{acGot['Msg']}") + return pr = urllib.parse.urlparse(self.path) print(f"DBUG:ProxyHandler:GET:{pr}") match pr.path: From 926b27a8ec1c5a5560a8411295e5059200ea444b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 11:10:08 +0530 Subject: [PATCH 152/187] SimpleChatTC:tools.proxyUrl: rename to just proxyUrl Next will be adding a proxyAuth field also to tools. --- tools/server/public_simplechat/readme.md | 4 ++-- tools/server/public_simplechat/simplechat.js | 2 +- tools/server/public_simplechat/toolweb.mjs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 0fd69eec92021..973ef77a768ef 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -226,7 +226,7 @@ It is attached to the document object. Some of these can also be updated using t remember to enable this only for GenAi/LLM models which support tool/function calling. - * fetchProxyUrl - specify the address for the running instance of bundled local.tools/simpleproxy.py + * proxyUrl - specify the address for the running instance of bundled local.tools/simpleproxy.py * searchUrl - specify the search engine's search url template along with the tag SEARCHWORDS in place where the search words should be substituted at runtime. @@ -411,7 +411,7 @@ manner by dropping head block as well as all scripts/styles/footers/headers/nav dropping the html tags. The client ui logic does a simple check to see if the bundled simpleproxy is running at specified -fetchProxyUrl before enabling these web and related tool calls. +proxyUrl before enabling these web and related tool calls. The bundled simple proxy diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 8758fa6366ade..1b61b8451a10e 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1082,7 +1082,7 @@ class Me { this.multiChat = new MultiChatUI(); this.tools = { enabled: false, - fetchProxyUrl: "http://127.0.0.1:3128", + proxyUrl: "http://127.0.0.1:3128", searchUrl: SearchURLS.duckduckgo, toolNames: /** @type {Array} */([]), /** diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 11f4cd6b3f31c..7e71961cf1327 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -45,7 +45,7 @@ function get_gme() { */ function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { if (gToolsWorker.onmessage != null) { - let newUrl = `${get_gme().tools.fetchProxyUrl}/${path}?${qkey}=${qvalue}` + let newUrl = `${get_gme().tools.proxyUrl}/${path}?${qkey}=${qvalue}` fetch(newUrl).then(resp => { if (!resp.ok) { throw new Error(`${resp.status}:${resp.statusText}`); @@ -70,7 +70,7 @@ function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { * @param {Object>} tcs */ async function proxyserver_tc_setup(tag, tcPath, tcName, tcsData, tcs) { - await fetch(`${get_gme().tools.fetchProxyUrl}/aum?url=${tcPath}.jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ + await fetch(`${get_gme().tools.proxyUrl}/aum?url=${tcPath}.jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ if (resp.statusText != 'bharatavarshe') { console.log(`WARN:ToolWeb:${tag}:Dont forget to run the bundled local.tools/simpleproxy.py to enable me`) return From 41a5fda67ca308d91915a82bef5a7724c8b5565c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 11:29:13 +0530 Subject: [PATCH 153/187] SimpleChatTC:SimpleProxy:ClientUI: Send Authorization bearer User can configure the bearer token to send --- tools/server/public_simplechat/simplechat.js | 1 + tools/server/public_simplechat/toolweb.mjs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 1b61b8451a10e..21ebb008f8417 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1083,6 +1083,7 @@ class Me { this.tools = { enabled: false, proxyUrl: "http://127.0.0.1:3128", + proxyAuthInsecure: "NeverSecure", searchUrl: SearchURLS.duckduckgo, toolNames: /** @type {Array} */([]), /** diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 7e71961cf1327..0808c6d0b3da0 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -46,7 +46,7 @@ function get_gme() { function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { if (gToolsWorker.onmessage != null) { let newUrl = `${get_gme().tools.proxyUrl}/${path}?${qkey}=${qvalue}` - fetch(newUrl).then(resp => { + fetch(newUrl, { headers: { 'Authorization': `Bearer ${get_gme().tools.proxyAuthInsecure}` }}).then(resp => { if (!resp.ok) { throw new Error(`${resp.status}:${resp.statusText}`); } @@ -70,7 +70,9 @@ function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { * @param {Object>} tcs */ async function proxyserver_tc_setup(tag, tcPath, tcName, tcsData, tcs) { - await fetch(`${get_gme().tools.proxyUrl}/aum?url=${tcPath}.jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ + await fetch(`${get_gme().tools.proxyUrl}/aum?url=${tcPath}.jambudweepe.akashaganga.multiverse.987654321123456789`, { + headers: { 'Authorization': `Bearer ${get_gme().tools.proxyAuthInsecure}` } + }).then(resp=>{ if (resp.statusText != 'bharatavarshe') { console.log(`WARN:ToolWeb:${tag}:Dont forget to run the bundled local.tools/simpleproxy.py to enable me`) return From 4bc5d26f96417d9ea437512a093c5c75e537e6e3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 15:44:17 +0530 Subject: [PATCH 154/187] SimpleChatTC:SimpleProxy: once in a bluemoon transformed bearer instead of using the shared bearer token as is, hash it with current year and use the hash. keep /aum path out of auth check. in future bearer token could be transformed more often, as well as with additional nounce/dynamic token from server got during initial /aum handshake as also running counter and so ... NOTE: All these circus not good enough, given that currently the simpleproxy.py handshakes work over http. However these skeletons put in place, for future, if needed. TODO: There is a once in a bluemoon race when the year transitions between client generating the request and server handling the req. But other wise year transitions dont matter bcas client always creates fresh token, and server checks for year change to genrate fresh token if required. --- .../local.tools/simpleproxy.py | 36 +++++++++++++++---- tools/server/public_simplechat/readme.md | 14 ++++++-- tools/server/public_simplechat/toolweb.mjs | 16 ++++++--- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 67b2741542415..8b17d012c053b 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -32,6 +32,7 @@ '--port': 3128, '--config': '/dev/null', '--debug': False, + 'bearer.transformed.year': "", 'server': None } @@ -46,6 +47,22 @@ gConfigNeeded = [ '--allowed.domains', '--bearer.insecure' ] +def bearer_transform(): + """ + Transform the raw bearer token to the network handshaked token, + if and when needed. + """ + global gMe + year = str(time.gmtime().tm_year) + if gMe['bearer.transformed.year'] == year: + return + import hashlib + s256 = hashlib.sha256(year.encode('utf-8')) + s256.update(gMe['--bearer.insecure'].encode('utf-8')) + gMe['--bearer.transformed'] = s256.hexdigest() + gMe['bearer.transformed.year'] = year + + class ProxyHandler(http.server.BaseHTTPRequestHandler): """ Implements the logic for handling requests sent to this server. @@ -75,6 +92,7 @@ def auth_check(self): Simple Bearer authorization ALERT: For multiple reasons, this is a very insecure implementation. """ + bearer_transform() authline = self.headers['Authorization'] if authline == None: return { 'AllOk': False, 'Msg': "No auth line" } @@ -83,7 +101,7 @@ def auth_check(self): return { 'AllOk': False, 'Msg': "Invalid auth line" } if authlineA[0] != 'Bearer': return { 'AllOk': False, 'Msg': "Invalid auth type" } - if authlineA[1] != gMe['--bearer.insecure']: + if authlineA[1] != gMe['--bearer.transformed']: return { 'AllOk': False, 'Msg': "Invalid auth" } return { 'AllOk': True, 'Msg': "Auth Ok" } @@ -93,17 +111,21 @@ def do_GET(self): """ print(f"\n\n\nDBUG:ProxyHandler:GET:{self.address_string()}:{self.path}") print(f"DBUG:PH:Get:Headers:{self.headers}") - acGot = self.auth_check() - if not acGot['AllOk']: - self.send_error(400, f"WARN:{acGot['Msg']}") - return pr = urllib.parse.urlparse(self.path) print(f"DBUG:ProxyHandler:GET:{pr}") match pr.path: case '/urlraw': - handle_urlraw(self, pr) + acGot = self.auth_check() + if not acGot['AllOk']: + self.send_error(400, f"WARN:{acGot['Msg']}") + else: + handle_urlraw(self, pr) case '/urltext': - handle_urltext(self, pr) + acGot = self.auth_check() + if not acGot['AllOk']: + self.send_error(400, f"WARN:{acGot['Msg']}") + else: + handle_urltext(self, pr) case '/aum': handle_aum(self, pr) case _: diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 973ef77a768ef..25089be4bc66a 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -89,7 +89,12 @@ remember to content like head, scripts, styles, headers, footers, ... Be careful when accessing web through this and use it only with known safe sites. - * it allows one to specify a white list of allowed.domains, look into local.tools/simpleproxy.json + * look into local.tools/simpleproxy.json for specifying + + * the white list of allowed.domains + * the shared bearer token between server and client ui + + ### using the front end @@ -228,6 +233,10 @@ It is attached to the document object. Some of these can also be updated using t * proxyUrl - specify the address for the running instance of bundled local.tools/simpleproxy.py + * proxyAuthInsecure - shared token between simpleproxy.py server and client ui, for accessing service provided by it. + + Shared token is currently hashed with the current year and inturn handshaked over the network. In future if required one could also include a dynamic token provided by simpleproxy server during /aum handshake and running counter or so into hashed token. ALERT: However do remember that currently the handshake occurs over http and not https, so others can snoop the network and get token. Per client ui running counter and random dynamic token can help mitigate things to some extent, if required in future. + * searchUrl - specify the search engine's search url template along with the tag SEARCHWORDS in place where the search words should be substituted at runtime. * toolCallResponseTimeoutMS - specifies the time (in msecs) for which the logic should wait for a tool call to respond @@ -419,7 +428,8 @@ The bundled simple proxy * tools/server/public_simplechat/local.tools/simpleproxy.py * it provides for a basic white list of allowed domains to access, to be specified by the end user. - This should help limit web access to a safe set of sites determined by the end user. + This should help limit web access to a safe set of sites determined by the end user. There is also + a provision for shared bearer token to be specified by the end user. * it tries to mimic the client/browser making the request to it by propogating header entries like user-agent, accept and accept-language from the got request to the generated request during proxying diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 0808c6d0b3da0..909fbdae7edfd 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -27,6 +27,13 @@ function get_gme() { } +function bearer_transform() { + let data = `${new Date().getUTCFullYear()}${get_gme().tools.proxyAuthInsecure}` + return crypto.subtle.digest('sha-256', new TextEncoder().encode(data)).then(ab=>{ + return Array.from(new Uint8Array(ab)).map(b=>b.toString(16).padStart(2,'0')).join('') + }) +} + /** * Helper http get logic wrt the bundled SimpleProxy server, * which helps execute a given proxy dependent tool call. @@ -43,10 +50,11 @@ function get_gme() { * @param {string} qkey * @param {string} qvalue */ -function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { +async function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { if (gToolsWorker.onmessage != null) { let newUrl = `${get_gme().tools.proxyUrl}/${path}?${qkey}=${qvalue}` - fetch(newUrl, { headers: { 'Authorization': `Bearer ${get_gme().tools.proxyAuthInsecure}` }}).then(resp => { + let btoken = await bearer_transform() + fetch(newUrl, { headers: { 'Authorization': `Bearer ${btoken}` }}).then(resp => { if (!resp.ok) { throw new Error(`${resp.status}:${resp.statusText}`); } @@ -70,9 +78,7 @@ function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { * @param {Object>} tcs */ async function proxyserver_tc_setup(tag, tcPath, tcName, tcsData, tcs) { - await fetch(`${get_gme().tools.proxyUrl}/aum?url=${tcPath}.jambudweepe.akashaganga.multiverse.987654321123456789`, { - headers: { 'Authorization': `Bearer ${get_gme().tools.proxyAuthInsecure}` } - }).then(resp=>{ + await fetch(`${get_gme().tools.proxyUrl}/aum?url=${tcPath}.jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ if (resp.statusText != 'bharatavarshe') { console.log(`WARN:ToolWeb:${tag}:Dont forget to run the bundled local.tools/simpleproxy.py to enable me`) return From 067f61b48e0228bc9610e24c4d2d9e342592c863 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 00:23:04 +0530 Subject: [PATCH 155/187] SimpleChatTC:ToolTemp and ChatShow Add a new role ToolTemp, which is used to maintain any tool call response on the client ui side, without submitting it to the server ie till user or auto submit triggers the submitting of that tool call response. When ever a tool call response is got, create a ToolTemp role based message in the corresponding chat session. And dont directly update the user query input area, rather leave it to the updated simplechat show and the new multichatui chat_show helper and inturn whether the current chat session active in ui is same as the one for which the tool call response has been recieved. TODO: Currently the response message is added to the current active chat session, but this needs to be changed by tracking chatId/session through the full tool call cycle and then adding the tool call response in the related chat session, and inturn updating or not the ui based on whether that chat session is still the active chat session in ui or not, given that tool call gets handled in a asynchronous way. Now when that tool call response is submitted, promote the equiv tool temp role based message that should be in the session's chat history as the last message into becoming a normal tool response message. SimpleChat.show has been updated to take care of showing any ToolTemp role message in the user query input area. A newer chat_show helper added to MultiChatUI, that takes care of calling SimpleChat.show, provided the chat_show is being requested for the currently active in ui, chat session. As well as to take care of passing both the ChatDiv and elInUser. Converts users of SimpleChat.show to use MultiChatUI.chat_show --- tools/server/public_simplechat/simplechat.js | 82 ++++++++++++++++---- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 21ebb008f8417..f68cc12f60ea1 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -12,6 +12,7 @@ class Roles { static User = "user"; static Assistant = "assistant"; static Tool = "tool"; + static ToolTemp = "TOOL.TEMP"; } class ApiEP { @@ -319,7 +320,7 @@ class SimpleChat { this.iLastSys = ods.iLastSys; this.xchat = []; for (const cur of ods.xchat) { - if (cur.ns == undefined) { + if (cur.ns == undefined) { // this relates to the old on-disk-structure/format, needs to be removed later /** @typedef {{role: string, content: string}} OldChatMessage */ let tcur = /** @type {OldChatMessage} */(/** @type {unknown} */(cur)); this.xchat.push(new ChatMessageEx(tcur.role, tcur.content)) @@ -383,6 +384,12 @@ class SimpleChat { let xchat = this.recent_chat(iRecentUserMsgCnt); let chat = []; for (const msg of xchat) { + if (msg.ns.role == Roles.ToolTemp) { + // Skip (temp) tool response which has not yet been accepted by user + // In future need to check that it is the last message + // and not something in between, which shouldnt occur normally. + continue + } let tmsg = ChatMessageEx.newFrom(msg); if (!tmsg.has_toolcall()) { tmsg.ns_delete("tool_calls") @@ -413,25 +420,55 @@ class SimpleChat { return true; } + /** + * Check if the last message in the chat history is a ToolTemp role based one. + * If so, then + * * update that to a regular Tool role based message. + * * also update the content of that message to what is passed. + * @param {string} content + */ + promote_tooltemp(content) { + let lastIndex = this.xchat.length - 1; + if (lastIndex < 0) { + console.error("DBUG:SimpleChat:PromoteToolTemp:No chat messages including ToolTemp") + return + } + if (this.xchat[lastIndex].ns.role != Roles.ToolTemp) { + console.error("DBUG:SimpleChat:PromoteToolTemp:LastChatMsg not ToolTemp") + return + } + this.xchat[lastIndex].ns.role = Roles.Tool; + this.xchat[lastIndex].ns.content = content; + } + /** * Show the chat contents in the specified div. + * Also update the user query input box, with ToolTemp role message, if any. + * * If requested to clear prev stuff and inturn no chat content then show * * usage info * * option to load prev saved chat if any * * as well as settings/info. * @param {HTMLDivElement} div + * @param {HTMLInputElement} elInUser * @param {boolean} bClear * @param {boolean} bShowInfoAll */ - show(div, bClear=true, bShowInfoAll=false) { + show(div, elInUser, bClear=true, bShowInfoAll=false) { if (bClear) { div.replaceChildren(); } let last = undefined; for(const x of this.recent_chat(gMe.chatProps.iRecentUserMsgCnt)) { - let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, div); - entry.className = `role-${x.ns.role}`; - last = entry; + if (x.ns.role != Roles.ToolTemp) { + let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, div); + entry.className = `role-${x.ns.role}`; + last = entry; + } else { + if (elInUser) { + elInUser.value = x.ns.content; + } + } } if (last !== undefined) { last.scrollIntoView(false); @@ -792,6 +829,20 @@ class MultiChatUI { this.elInUser.focus(); } + /** + * Refresh UI wrt given chatId, provided it matches the currently selected chatId + * @param {string} chatId + * @param {boolean} bClear + * @param {boolean} bShowInfoAll + */ + chat_show(chatId, bClear=true, bShowInfoAll=false) { + if (chatId != this.curChatId) { + return + } + let chat = this.simpleChats[this.curChatId]; + chat.show(this.elDivChat, this.elInUser, bClear, bShowInfoAll) + } + /** * Setup the needed callbacks wrt UI, curChatId to defaultChatId and * optionally switch to specified defaultChatId. @@ -839,7 +890,12 @@ class MultiChatUI { tools.setup((id, name, data)=>{ clearTimeout(this.timers.toolcallResponseTimeout) this.timers.toolcallResponseTimeout = undefined - this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(id, name, data); + // TODO: Check for chat id in future so as to + // identify the right chat session to add the tc response to + // as well as to decide whether to show this chat currently or not and same with auto submit + let chat = this.simpleChats[this.curChatId]; // rather we should pick chat based on tool response's chatId + chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(id, name, data))) + this.chat_show(chat.chatId) // one needs to use tool response's chatId this.ui_reset_userinput(false) if (gMe.tools.auto > 0) { this.timers.toolcallResponseSubmitClick = setTimeout(()=>{ @@ -867,7 +923,7 @@ class MultiChatUI { this.elInSystem.value = value.substring(0,value.length-1); let chat = this.simpleChats[this.curChatId]; chat.add_system_anytime(this.elInSystem.value, this.curChatId); - chat.show(this.elDivChat); + this.chat_show(chat.chatId) ev.preventDefault(); } }); @@ -922,11 +978,11 @@ class MultiChatUI { return; } if (content.startsWith("")) { - chat.add(new ChatMessageEx(Roles.Tool, content)) + chat.promote_tooltemp(content) } else { chat.add(new ChatMessageEx(Roles.User, content)) } - chat.show(this.elDivChat); + this.chat_show(chat.chatId); let theUrl = ApiEP.Url(gMe.baseURL, apiEP); let theBody = chat.request_jsonstr(apiEP); @@ -943,7 +999,7 @@ class MultiChatUI { let theResp = await chat.handle_response(resp, apiEP, this.elDivChat); if (chatId == this.curChatId) { - chat.show(this.elDivChat); + this.chat_show(chatId); if (theResp.trimmedContent.length > 0) { let p = ui.el_create_append_p(`TRIMMED:${theResp.trimmedContent}`, this.elDivChat); p.className="role-trim"; @@ -1053,9 +1109,9 @@ class MultiChatUI { } this.elInSystem.value = chat.get_system_latest().ns.content; this.elInUser.value = ""; - chat.show(this.elDivChat, true, true); - this.elInUser.focus(); this.curChatId = chatId; + this.chat_show(chatId, true, true); + this.elInUser.focus(); console.log(`INFO:SimpleChat:MCUI:HandleSessionSwitch:${chatId} entered...`); } @@ -1159,7 +1215,7 @@ class Me { console.log("DBUG:SimpleChat:SC:Load", chat); chat.load(); queueMicrotask(()=>{ - chat.show(div, true, true); + chat.show(div, this.multiChat.elInUser, true, true); this.multiChat.elInSystem.value = chat.get_system_latest().ns.content; }); }); From efdce1b750c8eef7e0aaaf18fd225fe587954cdb Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 01:43:52 +0530 Subject: [PATCH 156/187] SimpleChatTC:ToolCallErrPath:ToolTemp and MultiChatUIChatShow Update the immidiate tool call triggering failure and tool call response timeout paths to use the new ToolTemp and MultiChatUI based chat show logics. Actual tool call itself generating errors, is already handled in the previous commit changes. --- tools/server/public_simplechat/simplechat.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index f68cc12f60ea1..ccab26c393ae3 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1027,11 +1027,13 @@ class MultiChatUI { } let toolResult = await chat.handle_toolcall(toolCallId, toolname, this.elInToolArgs.value) if (toolResult !== undefined) { - this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, toolResult); + chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, toolResult))) + this.chat_show(chat.chatId) this.ui_reset_userinput(false) } else { this.timers.toolcallResponseTimeout = setTimeout(() => { - this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, `Tool/Function call ${toolname} taking too much time, aborting...`); + chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, `Tool/Function call ${toolname} taking too much time, aborting...`))) + this.chat_show(chat.chatId) this.ui_reset_userinput(false) }, gMe.tools.toolCallResponseTimeoutMS) } From d86bcc5c08b571a46989d017a02e41ce2f6c14e3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 02:17:15 +0530 Subject: [PATCH 157/187] SimpleChatTC:ToolTemp: Ensure add removes non promoted ToolTemp --- tools/server/public_simplechat/simplechat.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index ccab26c393ae3..b7e8af1603d19 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -407,11 +407,21 @@ class SimpleChat { /** * Add an entry into xchat. + * If the last message in chat history is a ToolTemp message, discard it + * as the runtime logic is asking for adding new message instead of promoting the tooltemp message. + * * NOTE: A new copy is created and added into xchat. * Also update iLastSys system prompt index tracker * @param {ChatMessageEx} chatMsg */ add(chatMsg) { + if (this.xchat.length > 0) { + let lastIndex = this.xchat.length - 1; + if (this.xchat[lastIndex].ns.role == Roles.ToolTemp) { + console.debug("DBUG:SimpleChat:Add:Discarding prev ToolTemp message...") + this.xchat.pop() + } + } this.xchat.push(ChatMessageEx.newFrom(chatMsg)); if (chatMsg.ns.role == Roles.System) { this.iLastSys = this.xchat.length - 1; From a74abcef88f7f79ad364e49f61750d9af035ec77 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 02:37:34 +0530 Subject: [PATCH 158/187] SimpleChatTC:ChatSessionID through the tool call cycle Pass chatId to tool call, and use chatId in got tool call resp, to decide as to to which chat session the async tool call resp belongs and inturn if auto submit timer should be started if auto is enabled. --- tools/server/public_simplechat/simplechat.js | 27 ++++++++++---------- tools/server/public_simplechat/tools.mjs | 9 ++++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index b7e8af1603d19..0c074561e63bf 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -721,7 +721,7 @@ class SimpleChat { return "Tool/Function call name not specified" } try { - return await tools.tool_call(toolcallid, toolname, toolargs) + return await tools.tool_call(this.chatId, toolcallid, toolname, toolargs) } catch (/** @type {any} */error) { return `Tool/Function call raised an exception:${error.name}:${error.message}` } @@ -847,10 +847,11 @@ class MultiChatUI { */ chat_show(chatId, bClear=true, bShowInfoAll=false) { if (chatId != this.curChatId) { - return + return false } let chat = this.simpleChats[this.curChatId]; chat.show(this.elDivChat, this.elInUser, bClear, bShowInfoAll) + return true } /** @@ -897,21 +898,19 @@ class MultiChatUI { }) // Handle messages from Tools web worker - tools.setup((id, name, data)=>{ + tools.setup((cid, tcid, name, data)=>{ clearTimeout(this.timers.toolcallResponseTimeout) this.timers.toolcallResponseTimeout = undefined - // TODO: Check for chat id in future so as to - // identify the right chat session to add the tc response to - // as well as to decide whether to show this chat currently or not and same with auto submit - let chat = this.simpleChats[this.curChatId]; // rather we should pick chat based on tool response's chatId - chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(id, name, data))) - this.chat_show(chat.chatId) // one needs to use tool response's chatId - this.ui_reset_userinput(false) - if (gMe.tools.auto > 0) { - this.timers.toolcallResponseSubmitClick = setTimeout(()=>{ - this.elBtnUser.click() - }, gMe.tools.auto*this.TimePeriods.ToolCallAutoTimeUnit) + let chat = this.simpleChats[cid]; + chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(tcid, name, data))) + if (this.chat_show(cid)) { + if (gMe.tools.auto > 0) { + this.timers.toolcallResponseSubmitClick = setTimeout(()=>{ + this.elBtnUser.click() + }, gMe.tools.auto*this.TimePeriods.ToolCallAutoTimeUnit) + } } + this.ui_reset_userinput(false) }) this.elInUser.addEventListener("keyup", (ev)=> { diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 23eb7e35e8323..73a79f460c71c 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -49,11 +49,11 @@ export function meta() { /** * Setup the callback that will be called when ever message * is recieved from the Tools Web Worker. - * @param {(id: string, name: string, data: string) => void} cb + * @param {(chatId: string, toolCallId: string, name: string, data: string) => void} cb */ export function setup(cb) { gToolsWorker.onmessage = function (ev) { - cb(ev.data.id, ev.data.name, ev.data.data) + cb(ev.data.cid, ev.data.tcid, ev.data.name, ev.data.data) } } @@ -62,15 +62,16 @@ export function setup(cb) { * Try call the specified tool/function call. * Returns undefined, if the call was placed successfully * Else some appropriate error message will be returned. + * @param {string} chatid * @param {string} toolcallid * @param {string} toolname * @param {string} toolargs */ -export async function tool_call(toolcallid, toolname, toolargs) { +export async function tool_call(chatid, toolcallid, toolname, toolargs) { for (const fn in tc_switch) { if (fn == toolname) { try { - tc_switch[fn]["handler"](toolcallid, fn, JSON.parse(toolargs)) + tc_switch[fn]["handler"](chatid, toolcallid, fn, JSON.parse(toolargs)) return undefined } catch (/** @type {any} */error) { return `Tool/Function call raised an exception:${error.name}:${error.message}` From 0b024d8dc61bb21464b03f446895ae3e138a14f9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 02:50:28 +0530 Subject: [PATCH 159/187] SimpleChatTC:ChatSessionID: Get all handlers to account for chatid This should ensure that tool call responses can be mapped back to the chat session for which it was triggered. --- tools/server/public_simplechat/readme.md | 2 +- tools/server/public_simplechat/tooljs.mjs | 10 +++++---- .../server/public_simplechat/toolsworker.mjs | 2 +- tools/server/public_simplechat/toolweb.mjs | 22 +++++++++++-------- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 25089be4bc66a..b8afbb9dd36d8 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -458,7 +458,7 @@ Provide a handler which Update the tc_switch to include a object entry for the tool, which inturn includes * the meta data wrt the tool call -* a reference to the handler - the handler should take toolCallId, toolName and toolArgs. +* a reference to the handler - handler should take chatSessionId, toolCallId, toolName and toolArgs. It should pass these along to the tools web worker, if used. * the result key (was used previously, may use in future, but for now left as is) diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index 0e9ce61c3eb3e..a30330ab8244c 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -32,12 +32,13 @@ let js_meta = { /** * Implementation of the javascript interpretor logic. Minimal skeleton for now. * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * @param {string} chatid * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function js_run(toolcallid, toolname, obj) { - gToolsWorker.postMessage({ id: toolcallid, name: toolname, code: obj["code"]}) +function js_run(chatid, toolcallid, toolname, obj) { + gToolsWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: obj["code"]}) } @@ -63,12 +64,13 @@ let calc_meta = { /** * Implementation of the simple calculator logic. Minimal skeleton for now. * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * @param {string} chatid * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function calc_run(toolcallid, toolname, obj) { - gToolsWorker.postMessage({ id: toolcallid, name: toolname, code: `console.log(${obj["arithexpr"]})`}) +function calc_run(chatid, toolcallid, toolname, obj) { + gToolsWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: `console.log(${obj["arithexpr"]})`}) } diff --git a/tools/server/public_simplechat/toolsworker.mjs b/tools/server/public_simplechat/toolsworker.mjs index b85b83b33b327..15675a8df8e87 100644 --- a/tools/server/public_simplechat/toolsworker.mjs +++ b/tools/server/public_simplechat/toolsworker.mjs @@ -23,6 +23,6 @@ self.onmessage = async function (ev) { console.log(`\n\nTool/Function call "${ev.data.name}" raised an exception:${error.name}:${error.message}\n\n`) } tconsole.console_revert() - self.postMessage({ id: ev.data.id, name: ev.data.name, data: tconsole.gConsoleStr}) + self.postMessage({ cid: ev.data.cid, tcid: ev.data.tcid, name: ev.data.name, data: tconsole.gConsoleStr}) console.info("DBUG:WW:OnMessage done") } diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 909fbdae7edfd..eeecf846b0f19 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -43,6 +43,7 @@ function bearer_transform() { * * with a predefined query token and value wrt a predefined path * NOTE: Initial go, handles textual data type. * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful + * @param {string} chatid * @param {string} toolcallid * @param {string} toolname * @param {any} obj @@ -50,7 +51,7 @@ function bearer_transform() { * @param {string} qkey * @param {string} qvalue */ -async function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalue) { +async function proxyserver_get_1arg(chatid, toolcallid, toolname, obj, path, qkey, qvalue) { if (gToolsWorker.onmessage != null) { let newUrl = `${get_gme().tools.proxyUrl}/${path}?${qkey}=${qvalue}` let btoken = await bearer_transform() @@ -60,9 +61,9 @@ async function proxyserver_get_1arg(toolcallid, toolname, obj, path, qkey, qvalu } return resp.text() }).then(data => { - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: data}})) + message_toolsworker(new MessageEvent('message', {data: {cid: chatid, tcid: toolcallid, name: toolname, data: data}})) }).catch((err)=>{ - message_toolsworker(new MessageEvent('message', {data: {id: toolcallid, name: toolname, data: `Error:${err}`}})) + message_toolsworker(new MessageEvent('message', {data: {cid: chatid, tcid: toolcallid, name: toolname, data: `Error:${err}`}})) }) } } @@ -122,12 +123,13 @@ let fetchweburlraw_meta = { * * with a query token named url wrt the path urlraw * which gives the actual url to fetch * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful + * @param {string} chatid * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function fetchweburlraw_run(toolcallid, toolname, obj) { - return proxyserver_get_1arg(toolcallid, toolname, obj, 'urlraw', 'url', encodeURIComponent(obj.url)); +function fetchweburlraw_run(chatid, toolcallid, toolname, obj) { + return proxyserver_get_1arg(chatid, toolcallid, toolname, obj, 'urlraw', 'url', encodeURIComponent(obj.url)); } @@ -179,12 +181,13 @@ let fetchweburltext_meta = { * * strips out head as well as any script, style, header, footer, nav and so blocks in body * before returning remaining body contents. * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful + * @param {string} chatid * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function fetchweburltext_run(toolcallid, toolname, obj) { - return proxyserver_get_1arg(toolcallid, toolname, obj, 'urltext', 'url', encodeURIComponent(obj.url)); +function fetchweburltext_run(chatid, toolcallid, toolname, obj) { + return proxyserver_get_1arg(chatid, toolcallid, toolname, obj, 'urltext', 'url', encodeURIComponent(obj.url)); } @@ -237,16 +240,17 @@ let searchwebtext_meta = { * * strips out head as well as any script, style, header, footer, nav and so blocks in body * before returning remaining body contents. * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful + * @param {string} chatid * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function searchwebtext_run(toolcallid, toolname, obj) { +function searchwebtext_run(chatid, toolcallid, toolname, obj) { if (gToolsWorker.onmessage != null) { /** @type {string} */ let searchUrl = get_gme().tools.searchUrl; searchUrl = searchUrl.replace("SEARCHWORDS", encodeURIComponent(obj.words)); - return proxyserver_get_1arg(toolcallid, toolname, obj, 'urltext', 'url', encodeURIComponent(searchUrl)); + return proxyserver_get_1arg(chatid, toolcallid, toolname, obj, 'urltext', 'url', encodeURIComponent(searchUrl)); } } From 61dde69fbc51cd89f9d04461e3d160123d9c3f1e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 03:34:02 +0530 Subject: [PATCH 160/187] SimpleChatTC:Reasoning: Initial Go --- tools/server/public_simplechat/simplechat.js | 40 +++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 0c074561e63bf..e9cf7d05916bf 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -45,7 +45,7 @@ class ApiEP { */ /** - * @typedef {{role: string, content: string, tool_calls: Array}} NSChatMessage + * @typedef {{role: string, content: string, reasoning_content: string, tool_calls: Array}} NSChatMessage */ class ChatMessageEx { @@ -54,12 +54,13 @@ class ChatMessageEx { * Represent a Message in the Chat * @param {string} role * @param {string} content + * @param {string} reasoning_content * @param {Array} tool_calls * @param {string} trimmedContent */ - constructor(role = "", content="", tool_calls=[], trimmedContent="") { + constructor(role = "", content="", reasoning_content="", tool_calls=[], trimmedContent="") { /** @type {NSChatMessage} */ - this.ns = { role: role, content: content, tool_calls: tool_calls } + this.ns = { role: role, content: content, tool_calls: tool_calls, reasoning_content: reasoning_content } this.trimmedContent = trimmedContent; } @@ -68,12 +69,13 @@ class ChatMessageEx { * @param {ChatMessageEx} old */ static newFrom(old) { - return new ChatMessageEx(old.ns.role, old.ns.content, old.ns.tool_calls, old.trimmedContent) + return new ChatMessageEx(old.ns.role, old.ns.content, old.ns.reasoning_content, old.ns.tool_calls, old.trimmedContent) } clear() { this.ns.role = ""; this.ns.content = ""; + this.ns.reasoning_content = ""; this.ns.tool_calls = []; this.trimmedContent = ""; } @@ -182,6 +184,7 @@ class ChatMessageEx { } } else { let toolCalls = nwo["choices"][0]["delta"]["tool_calls"]; + let reasoningContent = nwo["choices"][0]["delta"]["reasoning_content"]; if (toolCalls !== undefined) { if (toolCalls[0]["function"]["name"] !== undefined) { this.ns.tool_calls.push(toolCalls[0]); @@ -197,6 +200,9 @@ class ChatMessageEx { } } } + if (reasoningContent !== undefined) { + this.ns.reasoning_content += reasoningContent + } } } } else { @@ -221,6 +227,10 @@ class ChatMessageEx { this.ns.content = curContent; } } + let curRC = nwo["choices"][0]["message"]["reasoning_content"]; + if (curRC != undefined) { + this.ns.reasoning_content = curRC; + } let curTCs = nwo["choices"][0]["message"]["tool_calls"]; if (curTCs != undefined) { this.ns.tool_calls = curTCs; @@ -242,10 +252,21 @@ class ChatMessageEx { } content_equiv() { + let reasoning = "" + if (this.ns.reasoning_content.trim() !== "") { + reasoning = this.ns.reasoning_content.trim() + } if (this.ns.content !== "") { + if (reasoning !== "") { + return `!!!Reasoning: ${reasoning}!!! ${this.ns.content}`; + } return this.ns.content; } else if (this.has_toolcall()) { - return `\n${this.ns.tool_calls[0].function.name}\n${this.ns.tool_calls[0].function.arguments}\n`; + let ret = "" + if (reasoning !== "") { + ret = `!!!Reasoning: ${reasoning}!!!` + } + return `${ret}\n${this.ns.tool_calls[0].function.name}\n${this.ns.tool_calls[0].function.arguments}\n`; } else { return "" } @@ -325,7 +346,7 @@ class SimpleChat { let tcur = /** @type {OldChatMessage} */(/** @type {unknown} */(cur)); this.xchat.push(new ChatMessageEx(tcur.role, tcur.content)) } else { - this.xchat.push(new ChatMessageEx(cur.ns.role, cur.ns.content, cur.ns.tool_calls, cur.trimmedContent)) + this.xchat.push(new ChatMessageEx(cur.ns.role, cur.ns.content, cur.ns.reasoning_content, cur.ns.tool_calls, cur.trimmedContent)) } } } @@ -394,6 +415,9 @@ class SimpleChat { if (!tmsg.has_toolcall()) { tmsg.ns_delete("tool_calls") } + if (tmsg.ns.reasoning_content.trim() === "") { + tmsg.ns_delete("reasoning_content") + } if (tmsg.ns.role == Roles.Tool) { let res = ChatMessageEx.extractToolCallResultAllInOne(tmsg.ns.content) tmsg.ns.content = res.content @@ -471,6 +495,10 @@ class SimpleChat { let last = undefined; for(const x of this.recent_chat(gMe.chatProps.iRecentUserMsgCnt)) { if (x.ns.role != Roles.ToolTemp) { + if (x.ns.reasoning_content.trim() === "") { + let entry = ui.el_create_append_p(`>>${x.ns.role}!<<: ${x.ns.reasoning_content}`, div); + entry.className = `role-${x.ns.role}`; + } let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, div); entry.className = `role-${x.ns.role}`; last = entry; From 72e7a8d443a80aa7410d47516bf5760a7ebd2147 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 15:16:14 +0530 Subject: [PATCH 161/187] SimpleChatTC:Reasoning: Cleanup the initial go Rather simplify and make the content_equiv provide a relatively simple and neat representation of the reasoning with content and toolcall as the cases may be. Also remove the partial new para that I had introduced in the initial go for reasoning. --- tools/server/public_simplechat/simplechat.js | 30 +++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index e9cf7d05916bf..71daf2ad8f70a 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -251,25 +251,25 @@ class ChatMessageEx { return true } + /** + * Collate all the different parts of a chat message into a single string object. + * + * This currently includes reasoning, content and toolcall parts. + */ content_equiv() { let reasoning = "" + let content = "" + let toolcall = "" if (this.ns.reasoning_content.trim() !== "") { - reasoning = this.ns.reasoning_content.trim() + reasoning = `!!!Reasoning: ${this.ns.reasoning_content.trim()} !!!\n`; } if (this.ns.content !== "") { - if (reasoning !== "") { - return `!!!Reasoning: ${reasoning}!!! ${this.ns.content}`; - } - return this.ns.content; - } else if (this.has_toolcall()) { - let ret = "" - if (reasoning !== "") { - ret = `!!!Reasoning: ${reasoning}!!!` - } - return `${ret}\n${this.ns.tool_calls[0].function.name}\n${this.ns.tool_calls[0].function.arguments}\n`; - } else { - return "" + content = this.ns.content; } + if (this.has_toolcall()) { + toolcall = `\n\n\n${this.ns.tool_calls[0].function.name}\n${this.ns.tool_calls[0].function.arguments}\n\n`; + } + return `${reasoning} ${content} ${toolcall}`; } } @@ -495,10 +495,6 @@ class SimpleChat { let last = undefined; for(const x of this.recent_chat(gMe.chatProps.iRecentUserMsgCnt)) { if (x.ns.role != Roles.ToolTemp) { - if (x.ns.reasoning_content.trim() === "") { - let entry = ui.el_create_append_p(`>>${x.ns.role}!<<: ${x.ns.reasoning_content}`, div); - entry.className = `role-${x.ns.role}`; - } let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, div); entry.className = `role-${x.ns.role}`; last = entry; From 65abb70ae7a475a4be966849ac293549a825fa61 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 16:14:00 +0530 Subject: [PATCH 162/187] SimpleChatTC:SimpleProxy: Include some news sites in allowed domains --- .../local.tools/simpleproxy.json | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json index a4ea4305f045d..fce3fb1fb0bff 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.json +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -11,7 +11,33 @@ ".*\\.duckduckgo\\.com$", "^duckduckgo\\.com$", ".*\\.google\\.com$", - "^google\\.com$" + "^google\\.com$", + "^arxiv\\.org$", + ".*\\.nature\\.com$", + ".*\\.science\\.org$", + ".*\\.reuters\\.com$", + ".*\\.bloomberg\\.com$", + ".*\\.forbes\\.com$", + ".*\\.npr\\.org$", + ".*\\.cnn\\.com$", + ".*\\.theguardian\\.com$", + ".*\\.bbc\\.com$", + ".*\\.france24\\.com$", + ".*\\.dw\\.com$", + ".*\\.jpost\\.com$", + ".*\\.aljazeera\\.com$", + ".*\\.alarabiya\\.net$", + ".*\\.rt\\.com$", + "^tass\\.com$", + ".*\\.channelnewsasia\\.com$", + ".*\\.scmp\\.com$", + ".*\\.nikkei\\.com$", + ".*\\.nhk\\.or\\.jp$", + ".*\\.indiatoday\\.in$", + "^theprint\\.in$", + ".*\\.ndtv\\.com$", + "^lwn\\.net$", + "^arstechnica\\.com$" ], "bearer.insecure": "NeverSecure" } From 4d79c2909b50172fee271e969bc0d9353997f00a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 17:05:58 +0530 Subject: [PATCH 163/187] SimpleChatTC:Show: Cleanup Update existing flow so that next Tool Role message is handled directly from within --- tools/server/public_simplechat/simplechat.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 71daf2ad8f70a..a241f3acb5073 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -367,7 +367,7 @@ class SimpleChat { if (iRecentUserMsgCnt == 0) { console.warn("WARN:SimpleChat:SC:RecentChat:iRecentUsermsgCnt of 0 means no user message/query sent"); } - /** @type{ChatMessages} */ + /** @type {ChatMessages} */ let rchat = []; let sysMsg = this.get_system_latest(); if (sysMsg.ns.content.length != 0) { @@ -493,16 +493,16 @@ class SimpleChat { div.replaceChildren(); } let last = undefined; - for(const x of this.recent_chat(gMe.chatProps.iRecentUserMsgCnt)) { - if (x.ns.role != Roles.ToolTemp) { - let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, div); - entry.className = `role-${x.ns.role}`; - last = entry; - } else { - if (elInUser) { + for(const [i, x] of this.recent_chat(gMe.chatProps.iRecentUserMsgCnt).entries()) { + if (x.ns.role === Roles.ToolTemp) { + if (i == (this.xchat.length - 1)) { elInUser.value = x.ns.content; } + continue } + let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, div); + entry.className = `role-${x.ns.role}`; + last = entry; } if (last !== undefined) { last.scrollIntoView(false); From 581280ed8fee3a35d9a607c54de98a78918f1fa9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 17:55:45 +0530 Subject: [PATCH 164/187] SimpleChatTC:MultiChatUI.ChatShow: Mov SimpleChat.Show in -initial Also take care of updating the toolcall ui if needed from within this. --- tools/server/public_simplechat/simplechat.js | 45 ++++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index a241f3acb5073..237c58d16312f 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -488,7 +488,7 @@ class SimpleChat { * @param {boolean} bClear * @param {boolean} bShowInfoAll */ - show(div, elInUser, bClear=true, bShowInfoAll=false) { + showTOREMOVE(div, elInUser, bClear=true, bShowInfoAll=false) { if (bClear) { div.replaceChildren(); } @@ -865,6 +865,17 @@ class MultiChatUI { /** * Refresh UI wrt given chatId, provided it matches the currently selected chatId + * + * Show the chat contents in elDivChat. + * Also update + * * the user query input box, with ToolTemp role message, if last one. + * * the tool call trigger ui, with Tool role message, if last one. + * + * If requested to clear prev stuff and inturn no chat content then show + * * usage info + * * option to load prev saved chat if any + * * as well as settings/info. + * * @param {string} chatId * @param {boolean} bClear * @param {boolean} bShowInfoAll @@ -874,7 +885,34 @@ class MultiChatUI { return false } let chat = this.simpleChats[this.curChatId]; - chat.show(this.elDivChat, this.elInUser, bClear, bShowInfoAll) + if (bClear) { + this.elDivChat.replaceChildren(); + this.ui_reset_toolcall_as_needed(new ChatMessageEx()); + } + let last = undefined; + for(const [i, x] of chat.recent_chat(gMe.chatProps.iRecentUserMsgCnt).entries()) { + if (x.ns.role === Roles.ToolTemp) { + if (i == (chat.xchat.length - 1)) { + this.elInUser.value = x.ns.content; + } + continue + } + let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, this.elDivChat); + entry.className = `role-${x.ns.role}`; + last = entry; + if (x.ns.role === Roles.Tool) { + this.ui_reset_toolcall_as_needed(x); + } + } + if (last !== undefined) { + last.scrollIntoView(false); + } else { + if (bClear) { + this.elDivChat.innerHTML = gUsageMsg; + gMe.setup_load(this.elDivChat, chat); + gMe.show_info(this.elDivChat, bShowInfoAll); + } + } return true } @@ -1040,7 +1078,6 @@ class MultiChatUI { } else { console.debug(`DBUG:SimpleChat:MCUI:HandleUserSubmit:ChatId has changed:[${chatId}] [${this.curChatId}]`); } - this.ui_reset_toolcall_as_needed(theResp); this.ui_reset_userinput(); } @@ -1250,7 +1287,7 @@ class Me { console.log("DBUG:SimpleChat:SC:Load", chat); chat.load(); queueMicrotask(()=>{ - chat.show(div, this.multiChat.elInUser, true, true); + this.multiChat.chat_show(chat.chatId, true, true); this.multiChat.elInSystem.value = chat.get_system_latest().ns.content; }); }); From fac185d4035422ba63e308ca6ac8a27850b7a6dd Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 18:14:26 +0530 Subject: [PATCH 165/187] SimpleChatTC:MultiChatUI:ChatShow cleanup of Initial skeleton Fix up the initial skeleton / logic as needed. Remember that we are working with potentially a subset of chat messages from the session, given the sliding window logic of context managing on client ui side, so fix up the logic to use the right subset of messages array and not the global xchat when deciding whether a message is the last or last but one, which need special handling wrt Assistant (with toolcall) and Tool (ie response) messages. Moving tool call ui setup as well as tool call response got ui setup into ChatShow of MultiChatUI ensures that switching between chat sessions handle the ui wrt tool call triggering ui and tool call response submission related ui as needed properly. Rather even loading a previously auto saved chat session if it had tool call or tool call response to be handled, the chat ui will be setup as needed to continue that session properly. --- tools/server/public_simplechat/simplechat.js | 30 ++++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 237c58d16312f..b4aa6b6d17ea9 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -353,11 +353,11 @@ class SimpleChat { /** * Recent chat messages. - * If iRecentUserMsgCnt < 0 - * Then return the full chat history - * Else - * Return chat messages from latest going back till the last/latest system prompt. - * While keeping track that the number of user queries/messages doesnt exceed iRecentUserMsgCnt. + * + * If iRecentUserMsgCnt < 0, Then return the full chat history + * + * Else Return chat messages from latest going back till the last/latest system prompt. + * While keeping track that the number of user queries/messages doesnt exceed iRecentUserMsgCnt. * @param {number} iRecentUserMsgCnt */ recent_chat(iRecentUserMsgCnt) { @@ -890,9 +890,10 @@ class MultiChatUI { this.ui_reset_toolcall_as_needed(new ChatMessageEx()); } let last = undefined; - for(const [i, x] of chat.recent_chat(gMe.chatProps.iRecentUserMsgCnt).entries()) { + let chatToShow = chat.recent_chat(gMe.chatProps.iRecentUserMsgCnt); + for(const [i, x] of chatToShow.entries()) { if (x.ns.role === Roles.ToolTemp) { - if (i == (chat.xchat.length - 1)) { + if (i == (chatToShow.length - 1)) { this.elInUser.value = x.ns.content; } continue @@ -900,8 +901,19 @@ class MultiChatUI { let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, this.elDivChat); entry.className = `role-${x.ns.role}`; last = entry; - if (x.ns.role === Roles.Tool) { - this.ui_reset_toolcall_as_needed(x); + if (x.ns.role === Roles.Assistant) { + let bTC = false + if (i == (chatToShow.length-1)) { + bTC = true + } + if (i == (chatToShow.length-2)) { + if (chatToShow[i+1].ns.role == Roles.ToolTemp) { + bTC = true + } + } + if (bTC) { + this.ui_reset_toolcall_as_needed(x); + } } } if (last !== undefined) { From d445e4abb1ca9cbca9f8fd3373b6f7291e5e4f0f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 18:56:24 +0530 Subject: [PATCH 166/187] SimpleChatTC:Reasoning+: Update readme wrt reasoning, flow cleanup Also cleanup the minimal based showing of chat messages a bit And add github.com to allowed list --- .../local.tools/simpleproxy.json | 3 +- tools/server/public_simplechat/readme.md | 91 +++++++++++++++---- tools/server/public_simplechat/simplechat.js | 4 +- 3 files changed, 76 insertions(+), 22 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json index fce3fb1fb0bff..1902890c6036c 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.json +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -37,7 +37,8 @@ "^theprint\\.in$", ".*\\.ndtv\\.com$", "^lwn\\.net$", - "^arstechnica\\.com$" + "^arstechnica\\.com$", + ".*\\.github\\.com$" ], "bearer.insecure": "NeverSecure" } diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index b8afbb9dd36d8..2dfdf07451109 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -14,9 +14,9 @@ Continue reading for the details. ## overview This simple web frontend, allows triggering/testing the server's /completions or /chat/completions endpoints -in a simple way with minimal code from a common code base. Inturn additionally it tries to allow single or -multiple independent back and forth chatting to an extent, with the ai llm model at a basic level, with their -own system prompts. +in a simple way with minimal code from a common code base. Additionally it also allows end users to have +single or multiple independent chat sessions with back and forth chatting to an extent, with the ai llm model +at a basic level, with their own system prompts. This allows seeing the generated text / ai-model response in oneshot at the end, after it is fully generated, or potentially as it is being generated, in a streamed manner from the server/ai-model. @@ -24,7 +24,10 @@ or potentially as it is being generated, in a streamed manner from the server/ai ![Chat and Settings (old) screens](./simplechat_screens.webp "Chat and Settings (old) screens") Auto saves the chat session locally as and when the chat is progressing and inturn at a later time when you -open SimpleChat, option is provided to restore the old chat session, if a matching one exists. +open SimpleChat, option is provided to restore the old chat session, if a matching one exists. In turn if +any of those chat sessions were pending wrt user triggering a tool call or submitting a tool call response, +the ui is setup as needed for end user to continue with those previously saved sessions, from where they +left off. The UI follows a responsive web design so that the layout can adapt to available display space in a usable enough manner, in general. @@ -36,12 +39,17 @@ settings ui. For GenAi/LLM models supporting tool / function calling, allows one to interact with them and explore use of ai driven augmenting of the knowledge used for generating answers as well as for cross checking ai generated answers logically / programatically and by checking with other sources and lot more by making using of the -predefined tools / functions. The end user is provided control over tool calling and response submitting. +simple yet useful predefined tools / functions provided by this client web ui. The end user is provided full +control over tool calling and response submitting. -NOTE: Current web service api doesnt expose the model context length directly, so client logic doesnt provide -any adaptive culling of old messages nor of replacing them with summary of their content etal. However there -is a optional sliding window based chat logic, which provides a simple minded culling of old messages from -the chat history before sending to the ai model. +For GenAi/LLM models which support reasoning, the thinking of the model will be shown to the end user as the +model is running through its reasoning. + +NOTE: As all genai/llm web service apis may or may not expose the model context length directly, and also +as using ai out of band for additional parallel work may not be efficient given the loading of current systems +by genai/llm models, so client logic doesnt provide any adaptive culling of old messages nor of replacing them +with summary of their content etal. However there is a optional sliding window based chat logic, which provides +a simple minded culling of old messages from the chat history before sending to the ai model. NOTE: Wrt options sent with the request, it mainly sets temperature, max_tokens and optionaly stream as well as tool_calls mainly for now. However if someone wants they can update the js file or equivalent member in @@ -110,7 +118,7 @@ Once inside * try trim garbage in response or not * amount of chat history in the context sent to server/ai-model * oneshot or streamed mode. - * use built in tool calling or not + * use built in tool calling or not and its related params. * In completion mode >> note: most recent work has been in chat mode << * one normally doesnt use a system prompt in completion mode. @@ -149,6 +157,9 @@ Once inside * the user input box will be disabled and a working message will be shown in it. * if trim garbage is enabled, the logic will try to trim repeating text kind of garbage to some extent. +* any reasoning / thinking by the model is shown to the end user, as it is occuring, if the ai model + shares the same over the http interface. + * tool calling flow when working with ai models which support tool / function calling * if tool calling is enabled and the user query results in need for one of the builtin tools to be called, then the ai response might include request for tool call. @@ -159,6 +170,9 @@ Once inside ie generated result with meta data * if user is ok with the tool response, they can click submit to send the same to the GenAi/LLM. User can even modify the response generated by the tool, if required, before submitting. + * ALERT: Sometimes the reasoning or chat from ai model may indicate tool call, but you may actually + not get/see a tool call, in such situations, dont forget to cross check that tool calling is + enabled in the settings. * just refresh the page, to reset wrt the chat history and or system prompt and start afresh. This also helps if you had forgotten to start the bundled simpleproxy.py server before hand. @@ -372,8 +386,7 @@ needed to help generate better responses. this can also be used for * searching for specific topics and summarising the results * or so -The tool calling feature has been tested with Gemma3N, Granite4 and GptOss (given that -reasoning is currently unsupported by this client ui, it can mess with things) +The tool calling feature has been tested with Gemma3N, Granite4 and GptOss. ALERT: The simple minded way in which this is implemented, it provides some minimal safety mechanism like running ai generated code in web workers and restricting web access to user @@ -454,7 +467,8 @@ Provide a handler which * rather in some cases constructs the code to be run to get the tool / function call job done, and inturn pass the same to the provided web worker to get it executed. Use console.log while generating any response that should be sent back to the ai model, in your constructed code. -* once the job is done, return the generated result as needed. +* once the job is done, return the generated result as needed, along with tool call related meta + data like chatSessionId, toolCallId, toolName which was passed along with the tool call. Update the tc_switch to include a object entry for the tool, which inturn includes * the meta data wrt the tool call @@ -495,24 +509,63 @@ gets executed, before tool calling returns and thus data / error generated by th get incorporated in result sent to ai engine on the server side. -### ToDo +### Progress + +#### Done + +Tool Calling support added, along with a bunch of useful tool calls as well as a bundled simple proxy +if one wants to access web as part of tool call usage. + +Reasoning / thinking response from Ai Models is shown to the user, as they are being generated/shared. + +Chat Messages/Session and UI handling have been moved into corresponding Classes to an extent, this +helps ensure that +* switching chat sessions or loading a previous auto saved chat session will restore state including + ui such that end user can continue the chat session from where they left it, even if in the middle + of a tool call handshake. +* new fields added to http handshake in oneshot or streaming mode can be handled in a structured way + to an extent. + +#### ToDo Is the tool call promise land trap deep enough, need to think through and explore around this once later. Trap error responses. -Handle reasoning/thinking responses from ai models. - Handle multimodal handshaking with ai models. Add fetch_rss and documents|data_store tool calling, through the simpleproxy.py if and where needed. +Save used config entries along with the auto saved chat sessions and inturn give option to reload the +same when saved chat is loaded. + +MAYBE make the settings in general chat session specific, rather than the current global config flow. + + +### Debuging the handshake and beyond + +When working with llama.cpp server based GenAi/LLM running locally, to look at the handshake directly +from the commandline, you could run something like below -### Debuging the handshake +* sudo tcpdump -i lo -s 0 -vvv -A host 127.0.0.1 and port 8080 | tee /tmp/td.log +* or one could also try look at the network tab in the browser developer console -When working with llama.cpp server based GenAi/LLM running locally +One could always remove message entries or manipulate chat sessions by accessing document['gMe'] +in devel console of the browser -sudo tcpdump -i lo -s 0 -vvv -A host 127.0.0.1 and port 8080 | tee /tmp/td.log +* if you want the last tool call response you submitted to be re-available for tool call execution and + resubmitting of response fresh, for any reason, follow below steps + * remove the assistant response from end of chat session, if any, using + * document['gMe'].multiChat.simpleChats['SessionId'].xchat.pop() + * reset role of Tool response chat message to TOOL.TEMP from tool + * toolMessageIndex = document['gMe'].multiChat.simpleChats['SessionId'].xchat.length - 1 + * document['gMe'].multiChat.simpleChats['SessionId'].xchat[toolMessageIndex].role = "TOOL.TEMP" + * clicking on the SessionId at top in UI, should refresh the chat ui and inturn it should now give + the option to control that tool call again + * this can also help in the case where the chat session fails with context window exceeded + * you restart the GenAi/LLM server after increasing the context window as needed + * edit the chat session history as mentioned above, to the extent needed + * resubmit the last needed user/tool response as needed ## At the end diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index b4aa6b6d17ea9..706cbad03225e 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -261,7 +261,7 @@ class ChatMessageEx { let content = "" let toolcall = "" if (this.ns.reasoning_content.trim() !== "") { - reasoning = `!!!Reasoning: ${this.ns.reasoning_content.trim()} !!!\n`; + reasoning = `!!!Reasoning: ${this.ns.reasoning_content.trim()} !!!\n\n`; } if (this.ns.content !== "") { content = this.ns.content; @@ -898,7 +898,7 @@ class MultiChatUI { } continue } - let entry = ui.el_create_append_p(`${x.ns.role}: ${x.content_equiv()}`, this.elDivChat); + let entry = ui.el_create_append_p(`[[ ${x.ns.role} ]]: ${x.content_equiv()}`, this.elDivChat); entry.className = `role-${x.ns.role}`; last = entry; if (x.ns.role === Roles.Assistant) { From 4cda0edc9c4a4facdaf7c81c27f76ad47f7e573b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 03:38:34 +0530 Subject: [PATCH 167/187] SimpleChatTC:Cleanup: tool resp xml, some allowed domains Add a newline between name and content in the xml representation of the tool response, so that it is more easy to distinguish things Add github, linkedin and apnews domains to allowed.domains for simpleproxy.py --- tools/server/public_simplechat/local.tools/simpleproxy.json | 5 +++++ tools/server/public_simplechat/simplechat.js | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json index 1902890c6036c..1bae207341ec0 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.json +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -15,6 +15,8 @@ "^arxiv\\.org$", ".*\\.nature\\.com$", ".*\\.science\\.org$", + "^apnews\\.com$", + ".*\\.apnews\\.com$", ".*\\.reuters\\.com$", ".*\\.bloomberg\\.com$", ".*\\.forbes\\.com$", @@ -38,6 +40,9 @@ ".*\\.ndtv\\.com$", "^lwn\\.net$", "^arstechnica\\.com$", + ".*\\.linkedin\\.com$", + ".*\\.github\\.io$", + "^github\\.com$", ".*\\.github\\.com$" ], "bearer.insecure": "NeverSecure" diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 706cbad03225e..8b8de35e85905 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -96,7 +96,9 @@ class ChatMessageEx { el.appendChild(doc.createTextNode(k[1])) doc.documentElement.appendChild(el) } - return new XMLSerializer().serializeToString(doc); + let xmlStr = new XMLSerializer().serializeToString(doc); + xmlStr = xmlStr.replace(/\/name>\n Date: Wed, 29 Oct 2025 14:16:50 +0530 Subject: [PATCH 168/187] SimpleChatTC:Cleanup:Move showing message into ShowMessage --- tools/server/public_simplechat/simplechat.js | 76 +++++++++++++------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 8b8de35e85905..53adbb28d48f5 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -786,6 +786,12 @@ class MultiChatUI { toolcallResponseSubmitClick: undefined } + /** + * Used for tracking presence of any chat message in show related logics + * @type {HTMLElement | null} + */ + this.elLastChatMessage = null + // the ui elements this.elInSystem = /** @type{HTMLInputElement} */(document.getElementById("system-in")); this.elDivChat = /** @type{HTMLDivElement} */(document.getElementById("chat-div")); @@ -865,6 +871,43 @@ class MultiChatUI { this.elInUser.focus(); } + /** + * Handles showing a chat message in UI. + * + * If handling message belonging to role + * * ToolTemp, updates user query input element, if its the last message. + * * Assistant which contains a tool req, shows tool call ui if needed. ie + * * if it is the last message OR + * * if it is the last but one message and there is a ToolTemp message next + * @param {ChatMessageEx} msg + * @param {number} iFromLast + * @param {ChatMessageEx | undefined} nextMsg + */ + show_message(msg, iFromLast, nextMsg) { + if (msg.ns.role === Roles.ToolTemp) { + if (iFromLast == 0) { + this.elInUser.value = msg.ns.content; + } + return + } + let entry = ui.el_create_append_p(`[[ ${msg.ns.role} ]]: ${msg.content_equiv()}`, this.elDivChat); + entry.className = `role-${msg.ns.role}`; + this.elLastChatMessage = entry; + if (msg.ns.role === Roles.Assistant) { + let bTC = false + if (iFromLast == 0) { + bTC = true + } else if ((iFromLast == 1) && (nextMsg != undefined)) { + if (nextMsg.ns.role == Roles.ToolTemp) { + bTC = true + } + } + if (bTC) { + this.ui_reset_toolcall_as_needed(msg); + } + } + } + /** * Refresh UI wrt given chatId, provided it matches the currently selected chatId * @@ -891,35 +934,18 @@ class MultiChatUI { this.elDivChat.replaceChildren(); this.ui_reset_toolcall_as_needed(new ChatMessageEx()); } - let last = undefined; + this.elLastChatMessage = null let chatToShow = chat.recent_chat(gMe.chatProps.iRecentUserMsgCnt); for(const [i, x] of chatToShow.entries()) { - if (x.ns.role === Roles.ToolTemp) { - if (i == (chatToShow.length - 1)) { - this.elInUser.value = x.ns.content; - } - continue - } - let entry = ui.el_create_append_p(`[[ ${x.ns.role} ]]: ${x.content_equiv()}`, this.elDivChat); - entry.className = `role-${x.ns.role}`; - last = entry; - if (x.ns.role === Roles.Assistant) { - let bTC = false - if (i == (chatToShow.length-1)) { - bTC = true - } - if (i == (chatToShow.length-2)) { - if (chatToShow[i+1].ns.role == Roles.ToolTemp) { - bTC = true - } - } - if (bTC) { - this.ui_reset_toolcall_as_needed(x); - } + let iFromLast = (chatToShow.length - 1)-i + let nextMsg = undefined + if (iFromLast == 1) { + nextMsg = chatToShow[i+1] } + this.show_message(x, iFromLast, nextMsg) } - if (last !== undefined) { - last.scrollIntoView(false); + if (this.elLastChatMessage != null) { + /** @type{HTMLElement} */(this.elLastChatMessage).scrollIntoView(false); // Stupid ts-check js-doc intersection ??? } else { if (bClear) { this.elDivChat.innerHTML = gUsageMsg; From 9a4b25190c51877ec89ee387a75f641ee83e8376 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 15:39:32 +0530 Subject: [PATCH 169/187] SimpleChatTC:ShowMessage: containers, role, contents Seperate out the message ui block into a container containing a role block and contents container block. This will allow themeing of these seperately, if required. As part of same, currently the role has been put to the side of the message with vertical text flow. --- tools/server/public_simplechat/simplechat.css | 17 +++++++++++++ tools/server/public_simplechat/simplechat.js | 25 +++++++++++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.css b/tools/server/public_simplechat/simplechat.css index 98e88d99fb4a7..9125771d5b8ef 100644 --- a/tools/server/public_simplechat/simplechat.css +++ b/tools/server/public_simplechat/simplechat.css @@ -28,6 +28,23 @@ background-color: lightpink; } +.chat-message { + border-style: solid; + border-color: grey; + border-width: thin; + border-radius: 2px; + display: flex; +} +.chat-message-role { + border-style: dotted; + border-color: black; + border-width: thin; + border-radius: 4px; + writing-mode: vertical-lr; + padding-inline: 1vmin; +} + + .gridx2 { display: grid; grid-template-columns: repeat(2, 1fr); diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 53adbb28d48f5..cb0d373cc1643 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -879,20 +879,35 @@ class MultiChatUI { * * Assistant which contains a tool req, shows tool call ui if needed. ie * * if it is the last message OR * * if it is the last but one message and there is a ToolTemp message next + * @param {HTMLElement | undefined} elParent * @param {ChatMessageEx} msg * @param {number} iFromLast * @param {ChatMessageEx | undefined} nextMsg */ - show_message(msg, iFromLast, nextMsg) { + show_message(elParent, msg, iFromLast, nextMsg) { + // Handle ToolTemp if (msg.ns.role === Roles.ToolTemp) { if (iFromLast == 0) { this.elInUser.value = msg.ns.content; } return } - let entry = ui.el_create_append_p(`[[ ${msg.ns.role} ]]: ${msg.content_equiv()}`, this.elDivChat); - entry.className = `role-${msg.ns.role}`; - this.elLastChatMessage = entry; + // Create main section + let secMain = document.createElement('section') + secMain.classList.add(`role-${msg.ns.role}`) + secMain.classList.add('chat-message') + elParent?.append(secMain) + this.elLastChatMessage = secMain; + // Create role para + let entry = ui.el_create_append_p(`${msg.ns.role}`, secMain); + entry.className = `chat-message-role`; + // Create content section + let secContent = document.createElement('section') + secContent.classList.add('chat-message-content') + secMain.append(secContent) + // Add the content + entry = ui.el_create_append_p(`${msg.content_equiv()}`, secContent); + // Handle tool call ui, if reqd if (msg.ns.role === Roles.Assistant) { let bTC = false if (iFromLast == 0) { @@ -942,7 +957,7 @@ class MultiChatUI { if (iFromLast == 1) { nextMsg = chatToShow[i+1] } - this.show_message(x, iFromLast, nextMsg) + this.show_message(this.elDivChat, x, iFromLast, nextMsg) } if (this.elLastChatMessage != null) { /** @type{HTMLElement} */(this.elLastChatMessage).scrollIntoView(false); // Stupid ts-check js-doc intersection ??? From 796d32fa32c050c975f468bcae6fe0c26196aa05 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 19:32:44 +0530 Subject: [PATCH 170/187] SimpleChatTC:CSS: Instead of hardcoded btn minwidth use padding --- tools/server/public_simplechat/simplechat.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.css b/tools/server/public_simplechat/simplechat.css index 9125771d5b8ef..a26ab4976dbd4 100644 --- a/tools/server/public_simplechat/simplechat.css +++ b/tools/server/public_simplechat/simplechat.css @@ -67,7 +67,7 @@ min-height: 40vh; } button { - min-width: 8vw; + padding-inline: 2vmin; } .sameline { From 39c75064c0ba1af75c998ee08b3f420a801686e5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 20:11:14 +0530 Subject: [PATCH 171/187] SimpleChatTC:ShowMessage: Seperate out the content parts --- tools/server/public_simplechat/simplechat.css | 12 +++++++ tools/server/public_simplechat/simplechat.js | 33 ++++++++++++++++--- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.css b/tools/server/public_simplechat/simplechat.css index a26ab4976dbd4..e3db6037a830a 100644 --- a/tools/server/public_simplechat/simplechat.css +++ b/tools/server/public_simplechat/simplechat.css @@ -43,6 +43,18 @@ writing-mode: vertical-lr; padding-inline: 1vmin; } +.chat-message-toolcall { + border-style: solid; + border-color: grey; + border-width: thin; + border-radius: 2px; +} +.chat-message-toolcall-arg { + border-style: solid; + border-color: grey; + border-width: thin; + border-radius: 2px; +} .gridx2 { diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index cb0d373cc1643..1d4b394a779b3 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -902,14 +902,21 @@ class MultiChatUI { let entry = ui.el_create_append_p(`${msg.ns.role}`, secMain); entry.className = `chat-message-role`; // Create content section - let secContent = document.createElement('section') - secContent.classList.add('chat-message-content') - secMain.append(secContent) + let secContents = document.createElement('section') + secContents.classList.add('chat-message-contents') + secMain.append(secContents) // Add the content - entry = ui.el_create_append_p(`${msg.content_equiv()}`, secContent); + //entry = ui.el_create_append_p(`${msg.content_equiv()}`, secContents); + for (const [name, content] of [['reasoning', msg.ns.reasoning_content], ['content', msg.ns.content]]) { + let cTrimmed = content.trim() + if (cTrimmed.length > 0) { + entry = ui.el_create_append_p(`${cTrimmed}`, secContents); + entry.classList.add(`chat-message-${name}`) + } + } // Handle tool call ui, if reqd + let bTC = false if (msg.ns.role === Roles.Assistant) { - let bTC = false if (iFromLast == 0) { bTC = true } else if ((iFromLast == 1) && (nextMsg != undefined)) { @@ -921,6 +928,22 @@ class MultiChatUI { this.ui_reset_toolcall_as_needed(msg); } } + // Handle tool call non ui + if (msg.has_toolcall() && !bTC) { + let secTC = document.createElement('section') + secTC.classList.add('chat-message-toolcall') + secContents.append(secTC) + entry = ui.el_create_append_p(`name: ${msg.ns.tool_calls[0].function.name}`, secTC); + entry = ui.el_create_append_p(`id: ${msg.ns.tool_calls[0].id}`, secTC); + let oArgs = JSON.parse(msg.ns.tool_calls[0].function.arguments) + for (const k in oArgs) { + entry = ui.el_create_append_p(`arg: ${k}`, secTC); + let secArg = document.createElement('section') + secArg.classList.add('chat-message-toolcall-arg') + secTC.append(secArg) + secArg.innerText = oArgs[k] + } + } } /** From ac231688a23b7b9aa958427c2e462ad1ad459df5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 21:14:21 +0530 Subject: [PATCH 172/187] SimpleChatTC:ShowMessage:Show any number of toolcalls Also make reasoning easily identifiable in the chat --- tools/server/public_simplechat/simplechat.css | 3 ++ tools/server/public_simplechat/simplechat.js | 49 +++++++++++++------ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.css b/tools/server/public_simplechat/simplechat.css index e3db6037a830a..0dcb2cf8d8ffb 100644 --- a/tools/server/public_simplechat/simplechat.css +++ b/tools/server/public_simplechat/simplechat.css @@ -43,6 +43,9 @@ writing-mode: vertical-lr; padding-inline: 1vmin; } +.chat-message-reasoning { + border-block-style: dashed; +} .chat-message-toolcall { border-style: solid; border-color: grey; diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 1d4b394a779b3..8e2a7c37de9c9 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -871,6 +871,27 @@ class MultiChatUI { this.elInUser.focus(); } + /** + * Show the passed function / tool call details in specified parent element. + * @param {HTMLElement} elParent + * @param {NSToolCalls} tc + */ + show_message_toolcall(elParent, tc) { + let secTC = document.createElement('section') + secTC.classList.add('chat-message-toolcall') + elParent.append(secTC) + let entry = ui.el_create_append_p(`name: ${tc.function.name}`, secTC); + entry = ui.el_create_append_p(`id: ${tc.id}`, secTC); + let oArgs = JSON.parse(tc.function.arguments) + for (const k in oArgs) { + entry = ui.el_create_append_p(`arg: ${k}`, secTC); + let secArg = document.createElement('section') + secArg.classList.add('chat-message-toolcall-arg') + secTC.append(secArg) + secArg.innerText = oArgs[k] + } + } + /** * Handles showing a chat message in UI. * @@ -907,10 +928,16 @@ class MultiChatUI { secMain.append(secContents) // Add the content //entry = ui.el_create_append_p(`${msg.content_equiv()}`, secContents); - for (const [name, content] of [['reasoning', msg.ns.reasoning_content], ['content', msg.ns.content]]) { - let cTrimmed = content.trim() - if (cTrimmed.length > 0) { - entry = ui.el_create_append_p(`${cTrimmed}`, secContents); + let showList = [] + if (msg.ns.reasoning_content.trim().length > 0) { + showList.push(['reasoning', `!!!Reasoning: ${msg.ns.reasoning_content.trim()} !!!\n\n`]) + } + if (msg.ns.content.trim().length > 0) { + showList.push(['content', msg.ns.content.trim()]) + } + for (const [name, content] of showList) { + if (content.length > 0) { + entry = ui.el_create_append_p(`${content}`, secContents); entry.classList.add(`chat-message-${name}`) } } @@ -930,18 +957,8 @@ class MultiChatUI { } // Handle tool call non ui if (msg.has_toolcall() && !bTC) { - let secTC = document.createElement('section') - secTC.classList.add('chat-message-toolcall') - secContents.append(secTC) - entry = ui.el_create_append_p(`name: ${msg.ns.tool_calls[0].function.name}`, secTC); - entry = ui.el_create_append_p(`id: ${msg.ns.tool_calls[0].id}`, secTC); - let oArgs = JSON.parse(msg.ns.tool_calls[0].function.arguments) - for (const k in oArgs) { - entry = ui.el_create_append_p(`arg: ${k}`, secTC); - let secArg = document.createElement('section') - secArg.classList.add('chat-message-toolcall-arg') - secTC.append(secArg) - secArg.innerText = oArgs[k] + for (const i in msg.ns.tool_calls) { + this.show_message_toolcall(secContents, msg.ns.tool_calls[i]) } } } From 036da6ad9b1cc713b1f10c7c883834d3f6788c9a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 22:06:57 +0530 Subject: [PATCH 173/187] SimpleChatTC:UICleanup: WordBreaks, Print avoid side vertical Define rules to ensure that chat message contents wrap so as to avoid overflowing beyond the size of the screen being viewed. The style used for chat message role to be placed with vertical oriented text adjacent to the actual message content on the side seems to be creating issue with blank pages in some browsers, so avoid that styling when one is printing. --- tools/server/public_simplechat/simplechat.css | 31 +++++++++++++++++++ tools/server/public_simplechat/simplechat.js | 1 + 2 files changed, 32 insertions(+) diff --git a/tools/server/public_simplechat/simplechat.css b/tools/server/public_simplechat/simplechat.css index 0dcb2cf8d8ffb..1f913fa272d49 100644 --- a/tools/server/public_simplechat/simplechat.css +++ b/tools/server/public_simplechat/simplechat.css @@ -45,6 +45,9 @@ } .chat-message-reasoning { border-block-style: dashed; + overflow-wrap: break-word; + word-break: break-word; + hyphens: auto; } .chat-message-toolcall { border-style: solid; @@ -58,6 +61,16 @@ border-width: thin; border-radius: 2px; } +.chat-message-content { + overflow-wrap: break-word; + word-break: break-word; + hyphens: auto; +} +.chat-message-content-live { + overflow-wrap: break-word; + word-break: break-word; + hyphens: auto; +} .gridx2 { @@ -117,8 +130,26 @@ button { @media print { + #fullbody { height: auto; } + .chat-message { + border-style: solid; + border-color: grey; + border-width: thin; + border-radius: 2px; + display:inherit; + } + .chat-message-role { + border-style: dotted; + border-color: black; + border-width: thin; + border-radius: 4px; + writing-mode:inherit; + padding-inline: 1vmin; + } + + } diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 8e2a7c37de9c9..044e889d2e8e3 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -643,6 +643,7 @@ class SimpleChat { */ async handle_response_multipart(resp, apiEP, elDiv) { let elP = ui.el_create_append_p("", elDiv); + elP.classList.add("chat-message-content-live") if (!resp.body) { throw Error("ERRR:SimpleChat:SC:HandleResponseMultiPart:No body..."); } From 6fee68e80faacc60551feb650e0fa4d4c0767036 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 01:35:09 +0530 Subject: [PATCH 174/187] SimpleChatTC:UICleanup:ShowMessage: Update readme --- tools/server/public_simplechat/readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 2dfdf07451109..7f4df25e60ad9 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -526,6 +526,11 @@ helps ensure that * new fields added to http handshake in oneshot or streaming mode can be handled in a structured way to an extent. +Chat message parts seperated out and tagged to allow theming chat message as needed in future. +The default Chat UI theme/look changed to help differentiate between different messages in chat +history as well as the parts of each message in a slightly better manner. Change the theme slightly +between normal and print views (beyond previous infinite height) for better printed chat history. + #### ToDo Is the tool call promise land trap deep enough, need to think through and explore around this once later. From 57523ea0855ba4d40bcd0a8a43439e516ad17954 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 01:13:12 +0530 Subject: [PATCH 175/187] SimpleChatTC:DataStore: Initial skeleton of a Db WebWorker Create the DB store Try Get and Set operations The post back to main thread done from asynchronous paths. NOTE: given that it has been ages since indexed db was used, so this is a logical implementation by refering to mdn as needed. --- .../public_simplechat/toolsdbworker.mjs | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tools/server/public_simplechat/toolsdbworker.mjs diff --git a/tools/server/public_simplechat/toolsdbworker.mjs b/tools/server/public_simplechat/toolsdbworker.mjs new file mode 100644 index 0000000000000..6be6ddb41d089 --- /dev/null +++ b/tools/server/public_simplechat/toolsdbworker.mjs @@ -0,0 +1,86 @@ +//@ts-check +// STILL DANGER DANGER DANGER - Simple and Stupid - Use from a discardable VM only +// Helpers to handle db related tool/function calling using web worker +// by Humans for All +// + +/** + * Expects to get a message with cid, tcid, (f)name and args + * Posts message with cid, tcid, (f)name and data if any + */ + + +/** + * Allows the db connection to be openned. + */ +function db_open() { + return new Promise((resolve, reject) => { + const dbConn = indexedDB.open('TCDB', 1); + dbConn.onupgradeneeded = (ev) => { + console.debug("DBUG:WWDb:Conn:Upgrade needed...") + dbConn.result.createObjectStore('theDB'); + dbConn.result.onerror = (ev) => { + console.debug(`DBUG:WWDb:Db:Op failed [${ev}]...`) + } + }; + dbConn.onsuccess = (ev) => { + console.debug("DBUG:WWDb:Conn:Opened...") + resolve(dbConn.result); + } + dbConn.onerror = (ev) => { + console.debug(`DBUG:WWDb:Conn:Failed [${ev}]...`) + reject(ev); + } + }); +} + + +self.onmessage = async function (ev) { + try { + console.info(`DBUG:WWDb:${ev.data.name}:OnMessage started...`) + /** @type {IDBDatabase} */ + let db = await db_open(); + let dbTrans = db.transaction('theDB', 'readwrite'); + let dbOS = dbTrans.objectStore('theDB'); + let args = JSON.parse(ev.data.args); + switch (ev.data.name) { + case 'data_store_get': + let reqGet = dbOS.get(args['key']) + reqGet.onsuccess = (evGet) => { + console.info(`DBUG:WWDb:${ev.data.name}:transact success`) + self.postMessage({ + cid: ev.data.cid, + tcid: ev.data.tcid, + name: ev.data.name, + data: { 'status': 'ok', 'data': reqGet.result, 'msg': `DataStoreGet:Ok:${args['key']}:${reqGet.result}`} + }); + } + break; + case 'data_store_set': + let reqSet = dbOS.add(args['value'], args['key']); + reqSet.onsuccess = (evSet) => { + console.info(`DBUG:WWDb:${ev.data.name}:transact success`) + self.postMessage({ + cid: ev.data.cid, + tcid: ev.data.tcid, + name: ev.data.name, + data: { 'status': 'ok', 'msg': `DataStoreSet:Ok:${args['key']}:${reqSet.result}`} + }); + } + break; + default: + console.info(`ERRR:WWDb:${ev.data.name}:OnMessage:Unknown func call...`) + break; + } + console.info(`DBUG:WWDb:${ev.data.name}:OnMessage end`) + } catch (/** @type {any} */error) { + let errMsg = `\n\nTool/Function call "${ev.data.name}" raised an exception:${error.name}:${error.message}\n\n`; + self.postMessage({ + cid: ev.data.cid, + tcid: ev.data.tcid, + name: ev.data.name, + data: {'status': 'error', 'msg': errMsg} + }); + console.info(`ERRR:WWDb:${ev.data.name}:OnMessage end:${error}`) + } +} From e8340821f6dbd1ba78b76d1a7327b1b5799acad3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 01:53:28 +0530 Subject: [PATCH 176/187] SimpleChatTC:DataStore: Duplicate tooljs to tooldb initial skel --- tools/server/public_simplechat/tooldb.mjs | 102 ++++++++++++++++++ tools/server/public_simplechat/tooljs.mjs | 1 + tools/server/public_simplechat/tools.mjs | 4 + .../public_simplechat/toolsdbworker.mjs | 2 +- .../server/public_simplechat/toolsworker.mjs | 2 +- 5 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 tools/server/public_simplechat/tooldb.mjs diff --git a/tools/server/public_simplechat/tooldb.mjs b/tools/server/public_simplechat/tooldb.mjs new file mode 100644 index 0000000000000..364d92daa4c5d --- /dev/null +++ b/tools/server/public_simplechat/tooldb.mjs @@ -0,0 +1,102 @@ +//@ts-check +// ALERT - Simple Stupid flow - Using from a discardable VM is better +// Helpers to handle tools/functions calling wrt +// * javascript interpreter +// * simple arithmatic calculator +// using a web worker. +// by Humans for All +// + + +let gToolsWorker = /** @type{Worker} */(/** @type {unknown} */(null)); + + +let js_meta = { + "type": "function", + "function": { + "name": "run_javascript_function_code", + "description": "Runs given code using eval within a web worker context in a browser's javascript environment and returns the console.log outputs of the execution after few seconds", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The code that will be run using eval within a web worker in the browser's javascript interpreter environment." + } + }, + "required": ["code"] + } + } + } + + +/** + * Implementation of the javascript interpretor logic. Minimal skeleton for now. + * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * @param {string} chatid + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + */ +function js_run(chatid, toolcallid, toolname, obj) { + gToolsWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: obj["code"]}) +} + + +let calc_meta = { + "type": "function", + "function": { + "name": "simple_calculator", + "description": "Calculates the provided arithmatic expression using console.log within a web worker of a browser's javascript interpreter environment and returns the output of the execution once it is done in few seconds", + "parameters": { + "type": "object", + "properties": { + "arithexpr":{ + "type":"string", + "description":"The arithmatic expression that will be calculated by passing it to console.log of a browser's javascript interpreter." + } + }, + "required": ["arithexpr"] + } + } + } + + +/** + * Implementation of the simple calculator logic. Minimal skeleton for now. + * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * @param {string} chatid + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + */ +function calc_run(chatid, toolcallid, toolname, obj) { + gToolsWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: `console.log(${obj["arithexpr"]})`}) +} + + +/** + * @type {Object>} + */ +export let tc_switch = { + "run_javascript_function_code": { + "handler": js_run, + "meta": js_meta, + "result": "" + }, + "simple_calculator": { + "handler": calc_run, + "meta": calc_meta, + "result": "" + }, +} + + +/** + * Used to get hold of the web worker to use for running tool/function call related code + * Also to setup tool calls, which need to cross check things at runtime + * @param {Worker} toolsWorker + */ +export async function init(toolsWorker) { + gToolsWorker = toolsWorker +} diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index a30330ab8244c..364d92daa4c5d 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -3,6 +3,7 @@ // Helpers to handle tools/functions calling wrt // * javascript interpreter // * simple arithmatic calculator +// using a web worker. // by Humans for All // diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 73a79f460c71c..79de29c765c93 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -10,6 +10,7 @@ import * as tweb from './toolweb.mjs' let gToolsWorker = new Worker('./toolsworker.mjs', { type: 'module' }); +let gToolsDBWorker = new Worker('./toolsdbworker.mjs', { type: 'module' }); /** * Maintain currently available tool/function calls * @type {Object>} @@ -55,6 +56,9 @@ export function setup(cb) { gToolsWorker.onmessage = function (ev) { cb(ev.data.cid, ev.data.tcid, ev.data.name, ev.data.data) } + gToolsDBWorker.onmessage = function (ev) { + cb(ev.data.cid, ev.data.tcid, ev.data.name, ev.data.data) + } } diff --git a/tools/server/public_simplechat/toolsdbworker.mjs b/tools/server/public_simplechat/toolsdbworker.mjs index 6be6ddb41d089..698f281ade7c6 100644 --- a/tools/server/public_simplechat/toolsdbworker.mjs +++ b/tools/server/public_simplechat/toolsdbworker.mjs @@ -1,5 +1,5 @@ //@ts-check -// STILL DANGER DANGER DANGER - Simple and Stupid - Use from a discardable VM only +// STILL DANGER DANGER DANGER - Simple and Stupid - Using from a discardable VM better. // Helpers to handle db related tool/function calling using web worker // by Humans for All // diff --git a/tools/server/public_simplechat/toolsworker.mjs b/tools/server/public_simplechat/toolsworker.mjs index 15675a8df8e87..6706a44721d6a 100644 --- a/tools/server/public_simplechat/toolsworker.mjs +++ b/tools/server/public_simplechat/toolsworker.mjs @@ -1,5 +1,5 @@ //@ts-check -// STILL DANGER DANGER DANGER - Simple and Stupid - Use from a discardable VM only +// STILL DANGER DANGER DANGER - Simple and Stupid - Using from a discardable VM better. // Helpers to handle tools/functions calling using web worker // by Humans for All // From fbe8344cd9d02038b2e723d2043e7b0416f6ee19 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 02:29:46 +0530 Subject: [PATCH 177/187] SimpleChatTC:DataStore: Remaining plumbing to try this Update tooldb logic to match that needed for the db logic and its web worker. Bring in the remaining aspects of db helpers into tools flow. --- tools/server/public_simplechat/tooldb.mjs | 69 ++++++++++++----------- tools/server/public_simplechat/tools.mjs | 7 +++ 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/tools/server/public_simplechat/tooldb.mjs b/tools/server/public_simplechat/tooldb.mjs index 364d92daa4c5d..1a2e2c71ebe5a 100644 --- a/tools/server/public_simplechat/tooldb.mjs +++ b/tools/server/public_simplechat/tooldb.mjs @@ -1,92 +1,95 @@ //@ts-check // ALERT - Simple Stupid flow - Using from a discardable VM is better -// Helpers to handle tools/functions calling wrt -// * javascript interpreter -// * simple arithmatic calculator +// Helpers to handle tools/functions calling wrt data store // using a web worker. // by Humans for All // -let gToolsWorker = /** @type{Worker} */(/** @type {unknown} */(null)); +let gToolsDBWorker = /** @type{Worker} */(/** @type {unknown} */(null)); -let js_meta = { +let dsget_meta = { "type": "function", "function": { - "name": "run_javascript_function_code", - "description": "Runs given code using eval within a web worker context in a browser's javascript environment and returns the console.log outputs of the execution after few seconds", + "name": "data_store_get", + "description": "Retrieve the value associated with a given key, in few seconds", "parameters": { "type": "object", "properties": { - "code": { + "key": { "type": "string", - "description": "The code that will be run using eval within a web worker in the browser's javascript interpreter environment." + "description": "The key whose value should be returned." } }, - "required": ["code"] + "required": [ "key" ], } } } /** - * Implementation of the javascript interpretor logic. Minimal skeleton for now. - * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * Implementation of the data store get logic. Minimal skeleton for now. + * NOTE: Has access to the javascript web worker environment and can mess with it and beyond * @param {string} chatid * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function js_run(chatid, toolcallid, toolname, obj) { - gToolsWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: obj["code"]}) +function dsget_run(chatid, toolcallid, toolname, obj) { + gToolsDBWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) } -let calc_meta = { +let dsset_meta = { "type": "function", "function": { - "name": "simple_calculator", - "description": "Calculates the provided arithmatic expression using console.log within a web worker of a browser's javascript interpreter environment and returns the output of the execution once it is done in few seconds", + "name": "data_store_set", + "description": "Store a value under a given key, in few seconds using a web worker", "parameters": { "type": "object", "properties": { - "arithexpr":{ - "type":"string", - "description":"The arithmatic expression that will be calculated by passing it to console.log of a browser's javascript interpreter." + "key": { + "type": "string", + "description": "The key under which to store the value." + }, + "value": { + "type": "any", + "description": "The value to store. Can be any JSON-serialisable type." } }, - "required": ["arithexpr"] - } + "required": ["key", "value"] + }, } } /** - * Implementation of the simple calculator logic. Minimal skeleton for now. - * ALERT: Has access to the javascript web worker environment and can mess with it and beyond + * Implementation of the data store set logic. Minimal skeleton for now. + * NOTE: Has access to the javascript web worker environment and can mess with it and beyond * @param {string} chatid * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function calc_run(chatid, toolcallid, toolname, obj) { - gToolsWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: `console.log(${obj["arithexpr"]})`}) +function dsset_run(chatid, toolcallid, toolname, obj) { + gToolsDBWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) } + /** * @type {Object>} */ export let tc_switch = { - "run_javascript_function_code": { - "handler": js_run, - "meta": js_meta, + "data_store_get": { + "handler": dsget_run, + "meta": dsget_meta, "result": "" }, - "simple_calculator": { - "handler": calc_run, - "meta": calc_meta, + "data_store_set": { + "handler": dsset_run, + "meta": dsset_meta, "result": "" }, } @@ -98,5 +101,5 @@ export let tc_switch = { * @param {Worker} toolsWorker */ export async function init(toolsWorker) { - gToolsWorker = toolsWorker + gToolsDBWorker = toolsWorker } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 79de29c765c93..256754f5ab0c1 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -7,6 +7,7 @@ import * as tjs from './tooljs.mjs' import * as tweb from './toolweb.mjs' +import * as tdb from './tooldb.mjs' let gToolsWorker = new Worker('./toolsworker.mjs', { type: 'module' }); @@ -29,6 +30,12 @@ export async function init() { toolNames.push(key) } }) + await tdb.init(gToolsDBWorker).then(()=>{ + for (const key in tdb.tc_switch) { + tc_switch[key] = tdb.tc_switch[key] + toolNames.push(key) + } + }) let tNs = await tweb.init(gToolsWorker) for (const key in tNs) { tc_switch[key] = tNs[key] From 5f68bfe12f4a4fa808f80a6e88495e15b793ea5c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 03:13:32 +0530 Subject: [PATCH 178/187] SimpleChatTC:DataStore:FuncCallArgs: Any type not supported So mention that may be ai can send complex objects in stringified form. Rather once type of value is set to string, ai should normally do it, but no harm is hinting. --- tools/server/public_simplechat/tooldb.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/tooldb.mjs b/tools/server/public_simplechat/tooldb.mjs index 1a2e2c71ebe5a..0c348fc4b861b 100644 --- a/tools/server/public_simplechat/tooldb.mjs +++ b/tools/server/public_simplechat/tooldb.mjs @@ -22,7 +22,7 @@ let dsget_meta = { "description": "The key whose value should be returned." } }, - "required": [ "key" ], + "required": ["key"], } } } @@ -54,8 +54,8 @@ let dsset_meta = { "description": "The key under which to store the value." }, "value": { - "type": "any", - "description": "The value to store. Can be any JSON-serialisable type." + "type": "string", + "description": "The value to store, complex objects could be passed in JSON Stringified format." } }, "required": ["key", "value"] From ab27bbb59c1ae64003d6784e238ed8906f7ae881 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 03:28:07 +0530 Subject: [PATCH 179/187] SimpleChatTC:DataStore:Eagerness to Wrong JSON conversions In the eagerness of initial skeleton, had forgotten that the root/generic tool call router takes care of parsing the json string into a object, before calling the tool call, so no need to try parse again. Fixed the same. Hadnt converted the object based response from data store related calls in the db web worker, into json string before passing to the generic tool response callback, fixed the same. - Rather the though of making the ChatMsgEx.createAllInOne handle string or object set aside for now, to keep things simple and consistant to the greatest extent possible across different flows. And good news - flow is working atleast for the overall happy path Need to check what corner cases are lurking like calling set on same key more than once, seemed to have some flow oddity, which I need to check later. Also maybe change the field name to value from data in the response to get, to match the field name convention of set. GPT-OSS is fine with it. But worst case micro / nano / pico models may trip up, in worst case, so better to keep things consistent. --- tools/server/public_simplechat/readme.md | 6 +++++- tools/server/public_simplechat/tools.mjs | 2 +- tools/server/public_simplechat/toolsdbworker.mjs | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 7f4df25e60ad9..e506837450d1f 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -531,6 +531,10 @@ The default Chat UI theme/look changed to help differentiate between different m history as well as the parts of each message in a slightly better manner. Change the theme slightly between normal and print views (beyond previous infinite height) for better printed chat history. +Initial skeletons of a builtin data store related tool calls, built on browser's indexedDB, without +needing any proxy / additional helper to handle the store. One could use the ai assistant to store +ones (ie end users) own data or data of ai model. + #### ToDo Is the tool call promise land trap deep enough, need to think through and explore around this once later. @@ -539,7 +543,7 @@ Trap error responses. Handle multimodal handshaking with ai models. -Add fetch_rss and documents|data_store tool calling, through the simpleproxy.py if and where needed. +Add fetch_rss and documents|data_store [wip] tool calling, through the simpleproxy.py if and where needed. Save used config entries along with the auto saved chat sessions and inturn give option to reload the same when saved chat is loaded. diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 256754f5ab0c1..b63e94ab34ad9 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -64,7 +64,7 @@ export function setup(cb) { cb(ev.data.cid, ev.data.tcid, ev.data.name, ev.data.data) } gToolsDBWorker.onmessage = function (ev) { - cb(ev.data.cid, ev.data.tcid, ev.data.name, ev.data.data) + cb(ev.data.cid, ev.data.tcid, ev.data.name, JSON.stringify(ev.data.data)) } } diff --git a/tools/server/public_simplechat/toolsdbworker.mjs b/tools/server/public_simplechat/toolsdbworker.mjs index 698f281ade7c6..c64c85cc68fc6 100644 --- a/tools/server/public_simplechat/toolsdbworker.mjs +++ b/tools/server/public_simplechat/toolsdbworker.mjs @@ -42,7 +42,7 @@ self.onmessage = async function (ev) { let db = await db_open(); let dbTrans = db.transaction('theDB', 'readwrite'); let dbOS = dbTrans.objectStore('theDB'); - let args = JSON.parse(ev.data.args); + let args = ev.data.args; switch (ev.data.name) { case 'data_store_get': let reqGet = dbOS.get(args['key']) From 8bb0597f6fb0de9d65a3578cd1b788c8b50f8557 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 04:24:10 +0530 Subject: [PATCH 180/187] SimpleChatTC:DataStore: Dont ignore the error paths And indexedDB add isnt the one to be happy with updating existing key. --- .../public_simplechat/toolsdbworker.mjs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/toolsdbworker.mjs b/tools/server/public_simplechat/toolsdbworker.mjs index c64c85cc68fc6..b0a6b5a73d5da 100644 --- a/tools/server/public_simplechat/toolsdbworker.mjs +++ b/tools/server/public_simplechat/toolsdbworker.mjs @@ -20,15 +20,15 @@ function db_open() { console.debug("DBUG:WWDb:Conn:Upgrade needed...") dbConn.result.createObjectStore('theDB'); dbConn.result.onerror = (ev) => { - console.debug(`DBUG:WWDb:Db:Op failed [${ev}]...`) + console.info(`ERRR:WWDb:Db:Op failed [${ev}]...`) } }; dbConn.onsuccess = (ev) => { - console.debug("DBUG:WWDb:Conn:Opened...") + console.debug("INFO:WWDb:Conn:Opened...") resolve(dbConn.result); } dbConn.onerror = (ev) => { - console.debug(`DBUG:WWDb:Conn:Failed [${ev}]...`) + console.info(`ERRR:WWDb:Conn:Failed [${ev}]...`) reject(ev); } }); @@ -55,9 +55,27 @@ self.onmessage = async function (ev) { data: { 'status': 'ok', 'data': reqGet.result, 'msg': `DataStoreGet:Ok:${args['key']}:${reqGet.result}`} }); } + reqGet.onerror = (evGet) => { + console.info(`ERRR:WWDb:${ev.data.name}:transact failed:${reqGet.error}`) + self.postMessage({ + cid: ev.data.cid, + tcid: ev.data.tcid, + name: ev.data.name, + data: { 'status': 'error', 'msg': `DataStoreGet:Err:${args['key']}:${reqGet.error}`} + }); + } break; case 'data_store_set': let reqSet = dbOS.add(args['value'], args['key']); + reqSet.onerror = (evSet) => { + console.info(`ERRR:WWDb:${ev.data.name}:transact failed:${reqSet.error}`) + self.postMessage({ + cid: ev.data.cid, + tcid: ev.data.tcid, + name: ev.data.name, + data: { 'status': 'error', 'msg': `DataStoreSet:Err:${args['key']}:${reqSet.error}`} + }); + } reqSet.onsuccess = (evSet) => { console.info(`DBUG:WWDb:${ev.data.name}:transact success`) self.postMessage({ From ccdefe573d692d8400d3f689d0efb0c8a71d6ea9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 04:36:47 +0530 Subject: [PATCH 181/187] SimpleChatTC:DataStore:Put, stringify undefined, readme Update the descriptions of set and get to indicate the possible corner cases or rather semantic in such situations. Update the readme also a bit. The auto save and restore mentioned has nothing to do with the new data store mechanism. --- tools/server/public_simplechat/readme.md | 8 +++++++- tools/server/public_simplechat/tooldb.mjs | 4 ++-- tools/server/public_simplechat/tools.mjs | 4 +++- tools/server/public_simplechat/toolsdbworker.mjs | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index e506837450d1f..cb7b26058c79d 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -102,6 +102,8 @@ remember to * the white list of allowed.domains * the shared bearer token between server and client ui +* other builtin tool / function calls like calcultor, javascript runner, DataStore dont require simpleproxy.py + ### using the front end @@ -178,6 +180,8 @@ Once inside This also helps if you had forgotten to start the bundled simpleproxy.py server before hand. Start the simpleproxy.py server and refresh the client ui page, to get access to web access related tool calls. + * if you refreshed unknowingly, you can use the Restore feature to try load the previous chat + session and resume that session. This uses a basic local auto save logic that is in there. * Using NewChat one can start independent chat sessions. * two independent chat sessions are setup by default. @@ -406,6 +410,8 @@ The following tools/functions are currently provided by default * run_javascript_function_code - which can be used to run some javascript code in the browser context. +* data_store_get/set - allows for a basic data store to be used. + Currently the ai generated code / expression is run through a simple minded eval inside a web worker mechanism. Use of WebWorker helps avoid exposing browser global scope to the generated code directly. However any shared web worker scope isnt isolated. Either way always remember to cross check the tool @@ -450,7 +456,7 @@ The bundled simple proxy a non-browser entity. In future it can be further extended to help with other relatively simple yet useful tool calls like -data / documents_store, fetch_rss and so. +data / documents_store [wip], fetch_rss and so. * for now fetch_rss can be indirectly achieved using fetch_web_url_raw. diff --git a/tools/server/public_simplechat/tooldb.mjs b/tools/server/public_simplechat/tooldb.mjs index 0c348fc4b861b..c6bf47e7c8e5c 100644 --- a/tools/server/public_simplechat/tooldb.mjs +++ b/tools/server/public_simplechat/tooldb.mjs @@ -13,7 +13,7 @@ let dsget_meta = { "type": "function", "function": { "name": "data_store_get", - "description": "Retrieve the value associated with a given key, in few seconds", + "description": "Retrieve the value associated with a given key, in few seconds using a web worker. If key doesnt exist, then __UNDEFINED__ is returned as the value.", "parameters": { "type": "object", "properties": { @@ -45,7 +45,7 @@ let dsset_meta = { "type": "function", "function": { "name": "data_store_set", - "description": "Store a value under a given key, in few seconds using a web worker", + "description": "Store a value under a given key, in few seconds using a web worker. If the key already exists, its value will be updated to the new value", "parameters": { "type": "object", "properties": { diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index b63e94ab34ad9..e4f66ea7f3df4 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -64,7 +64,9 @@ export function setup(cb) { cb(ev.data.cid, ev.data.tcid, ev.data.name, ev.data.data) } gToolsDBWorker.onmessage = function (ev) { - cb(ev.data.cid, ev.data.tcid, ev.data.name, JSON.stringify(ev.data.data)) + cb(ev.data.cid, ev.data.tcid, ev.data.name, JSON.stringify(ev.data.data, (k,v)=>{ + return (v === undefined) ? '__UNDEFINED__' : v; + })); } } diff --git a/tools/server/public_simplechat/toolsdbworker.mjs b/tools/server/public_simplechat/toolsdbworker.mjs index b0a6b5a73d5da..6e17ec4a3b093 100644 --- a/tools/server/public_simplechat/toolsdbworker.mjs +++ b/tools/server/public_simplechat/toolsdbworker.mjs @@ -66,7 +66,7 @@ self.onmessage = async function (ev) { } break; case 'data_store_set': - let reqSet = dbOS.add(args['value'], args['key']); + let reqSet = dbOS.put(args['value'], args['key']); reqSet.onerror = (evSet) => { console.info(`ERRR:WWDb:${ev.data.name}:transact failed:${reqSet.error}`) self.postMessage({ From 3fda84d2c1a77e98cef6a4cae49c56f946d5931a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 05:51:38 +0530 Subject: [PATCH 182/187] SimpleChatTC:DataStore: Delete a record - the db web worker side --- .../public_simplechat/toolsdbworker.mjs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tools/server/public_simplechat/toolsdbworker.mjs b/tools/server/public_simplechat/toolsdbworker.mjs index 6e17ec4a3b093..9769706a767c3 100644 --- a/tools/server/public_simplechat/toolsdbworker.mjs +++ b/tools/server/public_simplechat/toolsdbworker.mjs @@ -44,6 +44,7 @@ self.onmessage = async function (ev) { let dbOS = dbTrans.objectStore('theDB'); let args = ev.data.args; switch (ev.data.name) { + case 'data_store_get': let reqGet = dbOS.get(args['key']) reqGet.onsuccess = (evGet) => { @@ -65,6 +66,7 @@ self.onmessage = async function (ev) { }); } break; + case 'data_store_set': let reqSet = dbOS.put(args['value'], args['key']); reqSet.onerror = (evSet) => { @@ -86,9 +88,33 @@ self.onmessage = async function (ev) { }); } break; + + case 'data_store_delete': + let reqDel = dbOS.delete(args['key']) + reqDel.onsuccess = (evDel) => { + console.info(`DBUG:WWDb:${ev.data.name}:transact success`) + self.postMessage({ + cid: ev.data.cid, + tcid: ev.data.tcid, + name: ev.data.name, + data: { 'status': 'ok', 'msg': `DataStoreDelete:Ok:${args['key']}:${reqDel.result}`} + }); + } + reqDel.onerror = (evDel) => { + console.info(`ERRR:WWDb:${ev.data.name}:transact failed:${reqDel.error}`) + self.postMessage({ + cid: ev.data.cid, + tcid: ev.data.tcid, + name: ev.data.name, + data: { 'status': 'error', 'msg': `DataStoreDelete:Err:${args['key']}:${reqDel.error}`} + }); + } + break; + default: console.info(`ERRR:WWDb:${ev.data.name}:OnMessage:Unknown func call...`) break; + } console.info(`DBUG:WWDb:${ev.data.name}:OnMessage end`) } catch (/** @type {any} */error) { From 308e317dc90d5020f577216988af8c3affb20186 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 05:59:45 +0530 Subject: [PATCH 183/187] SimpleChatTC:DataStore:Delete a record - the plumbing side --- tools/server/public_simplechat/tooldb.mjs | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tools/server/public_simplechat/tooldb.mjs b/tools/server/public_simplechat/tooldb.mjs index c6bf47e7c8e5c..2aa07853c5e82 100644 --- a/tools/server/public_simplechat/tooldb.mjs +++ b/tools/server/public_simplechat/tooldb.mjs @@ -77,6 +77,38 @@ function dsset_run(chatid, toolcallid, toolname, obj) { } +let dsdel_meta = { + "type": "function", + "function": { + "name": "data_store_delete", + "description": "Remove the entry associated with a given key, in few seconds using a web worker.", + "parameters": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The key that should be deleted along with its entry." + } + }, + "required": ["key"], + } + } + } + + +/** + * Implementation of the data store delete logic. Minimal skeleton for now. + * NOTE: Has access to the javascript web worker environment and can mess with it and beyond + * @param {string} chatid + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + */ +function dsdel_run(chatid, toolcallid, toolname, obj) { + gToolsDBWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) +} + + /** * @type {Object>} @@ -92,6 +124,11 @@ export let tc_switch = { "meta": dsset_meta, "result": "" }, + "data_store_delete": { + "handler": dsdel_run, + "meta": dsdel_meta, + "result": "" + }, } From 3135a42a1d60d88d8ae4ce5e399cf852fea4e0fc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 06:14:27 +0530 Subject: [PATCH 184/187] SimpleChatTC:DataStore:list - web worker side logic The basic skeleton added on the web worker side for listing keys. TODO: Avoid duplication of similar code to an extent across some of these db ops. --- .../public_simplechat/toolsdbworker.mjs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tools/server/public_simplechat/toolsdbworker.mjs b/tools/server/public_simplechat/toolsdbworker.mjs index 9769706a767c3..9b47a822caf2d 100644 --- a/tools/server/public_simplechat/toolsdbworker.mjs +++ b/tools/server/public_simplechat/toolsdbworker.mjs @@ -45,6 +45,28 @@ self.onmessage = async function (ev) { let args = ev.data.args; switch (ev.data.name) { + case 'data_store_list': + let reqList = dbOS.getAllKeys() + reqList.onsuccess = (evList) => { + console.info(`DBUG:WWDb:${ev.data.name}:transact success`) + self.postMessage({ + cid: ev.data.cid, + tcid: ev.data.tcid, + name: ev.data.name, + data: { 'status': 'ok', 'data': reqList.result, 'msg': `DataStoreList:Ok:${args['key']}:${reqList.result}`} + }); + } + reqList.onerror = (evList) => { + console.info(`ERRR:WWDb:${ev.data.name}:transact failed:${reqList.error}`) + self.postMessage({ + cid: ev.data.cid, + tcid: ev.data.tcid, + name: ev.data.name, + data: { 'status': 'error', 'msg': `DataStoreList:Err:${args['key']}:${reqList.error}`} + }); + } + break; + case 'data_store_get': let reqGet = dbOS.get(args['key']) reqGet.onsuccess = (evGet) => { From 8186d8ca795682eff55446ed7122f221e78867fa Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 06:24:01 +0530 Subject: [PATCH 185/187] SimpleChatTC:DataStore:List keys - the plumbing --- tools/server/public_simplechat/tooldb.mjs | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tools/server/public_simplechat/tooldb.mjs b/tools/server/public_simplechat/tooldb.mjs index 2aa07853c5e82..5eefc16e8926b 100644 --- a/tools/server/public_simplechat/tooldb.mjs +++ b/tools/server/public_simplechat/tooldb.mjs @@ -109,6 +109,33 @@ function dsdel_run(chatid, toolcallid, toolname, obj) { } +let dslist_meta = { + "type": "function", + "function": { + "name": "data_store_list", + "description": "List all keys wrt key-value pairs currently stored in the data store. This will take few seconds and uses a web worker.", + "parameters": { + "type": "object", + "properties": { + }, + } + } + } + + +/** + * Implementation of the data store list logic. Minimal skeleton for now. + * NOTE: Has access to the javascript web worker environment and can mess with it and beyond + * @param {string} chatid + * @param {string} toolcallid + * @param {string} toolname + * @param {any} obj + */ +function dslist_run(chatid, toolcallid, toolname, obj) { + gToolsDBWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) +} + + /** * @type {Object>} @@ -129,6 +156,11 @@ export let tc_switch = { "meta": dsdel_meta, "result": "" }, + "data_store_list": { + "handler": dslist_run, + "meta": dslist_meta, + "result": "" + }, } From eafbcb47a61b1b736664bf096282b7eed1409b58 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 06:28:27 +0530 Subject: [PATCH 186/187] SimpleChatTC:DataStore:Cleanup:Msg, duplicate on routing side Avoid the duplicate plumbing code and use a common ops plumbing helper. Remove args[key] oversight from DataStoreList msg on webworkr --- tools/server/public_simplechat/tooldb.mjs | 51 +++---------------- .../public_simplechat/toolsdbworker.mjs | 4 +- 2 files changed, 8 insertions(+), 47 deletions(-) diff --git a/tools/server/public_simplechat/tooldb.mjs b/tools/server/public_simplechat/tooldb.mjs index 5eefc16e8926b..f77a4e698553c 100644 --- a/tools/server/public_simplechat/tooldb.mjs +++ b/tools/server/public_simplechat/tooldb.mjs @@ -28,19 +28,6 @@ let dsget_meta = { } -/** - * Implementation of the data store get logic. Minimal skeleton for now. - * NOTE: Has access to the javascript web worker environment and can mess with it and beyond - * @param {string} chatid - * @param {string} toolcallid - * @param {string} toolname - * @param {any} obj - */ -function dsget_run(chatid, toolcallid, toolname, obj) { - gToolsDBWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) -} - - let dsset_meta = { "type": "function", "function": { @@ -64,19 +51,6 @@ let dsset_meta = { } -/** - * Implementation of the data store set logic. Minimal skeleton for now. - * NOTE: Has access to the javascript web worker environment and can mess with it and beyond - * @param {string} chatid - * @param {string} toolcallid - * @param {string} toolname - * @param {any} obj - */ -function dsset_run(chatid, toolcallid, toolname, obj) { - gToolsDBWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) -} - - let dsdel_meta = { "type": "function", "function": { @@ -96,19 +70,6 @@ let dsdel_meta = { } -/** - * Implementation of the data store delete logic. Minimal skeleton for now. - * NOTE: Has access to the javascript web worker environment and can mess with it and beyond - * @param {string} chatid - * @param {string} toolcallid - * @param {string} toolname - * @param {any} obj - */ -function dsdel_run(chatid, toolcallid, toolname, obj) { - gToolsDBWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) -} - - let dslist_meta = { "type": "function", "function": { @@ -124,14 +85,14 @@ let dslist_meta = { /** - * Implementation of the data store list logic. Minimal skeleton for now. + * Implementation of the minimal needed plumbing for data store related ops triggering. * NOTE: Has access to the javascript web worker environment and can mess with it and beyond * @param {string} chatid * @param {string} toolcallid * @param {string} toolname * @param {any} obj */ -function dslist_run(chatid, toolcallid, toolname, obj) { +function dsops_run(chatid, toolcallid, toolname, obj) { gToolsDBWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) } @@ -142,22 +103,22 @@ function dslist_run(chatid, toolcallid, toolname, obj) { */ export let tc_switch = { "data_store_get": { - "handler": dsget_run, + "handler": dsops_run, "meta": dsget_meta, "result": "" }, "data_store_set": { - "handler": dsset_run, + "handler": dsops_run, "meta": dsset_meta, "result": "" }, "data_store_delete": { - "handler": dsdel_run, + "handler": dsops_run, "meta": dsdel_meta, "result": "" }, "data_store_list": { - "handler": dslist_run, + "handler": dsops_run, "meta": dslist_meta, "result": "" }, diff --git a/tools/server/public_simplechat/toolsdbworker.mjs b/tools/server/public_simplechat/toolsdbworker.mjs index 9b47a822caf2d..2075a231b8bf9 100644 --- a/tools/server/public_simplechat/toolsdbworker.mjs +++ b/tools/server/public_simplechat/toolsdbworker.mjs @@ -53,7 +53,7 @@ self.onmessage = async function (ev) { cid: ev.data.cid, tcid: ev.data.tcid, name: ev.data.name, - data: { 'status': 'ok', 'data': reqList.result, 'msg': `DataStoreList:Ok:${args['key']}:${reqList.result}`} + data: { 'status': 'ok', 'data': reqList.result, 'msg': `DataStoreList:Ok:${reqList.result}`} }); } reqList.onerror = (evList) => { @@ -62,7 +62,7 @@ self.onmessage = async function (ev) { cid: ev.data.cid, tcid: ev.data.tcid, name: ev.data.name, - data: { 'status': 'error', 'msg': `DataStoreList:Err:${args['key']}:${reqList.error}`} + data: { 'status': 'error', 'msg': `DataStoreList:Err:${reqList.error}`} }); } break; From a52fadef6650d134412c7e77f57eaa277e87cd47 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 07:11:50 +0530 Subject: [PATCH 187/187] SimpleChatTC:DataStore: update readme --- tools/server/public_simplechat/readme.md | 29 ++++++++++++++++-------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index cb7b26058c79d..3c873d0a06d8b 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -102,7 +102,8 @@ remember to * the white list of allowed.domains * the shared bearer token between server and client ui -* other builtin tool / function calls like calcultor, javascript runner, DataStore dont require simpleproxy.py +* other builtin tool / function calls like calculator, javascript runner, DataStore dont require the + simpleproxy.py helper. @@ -390,6 +391,15 @@ needed to help generate better responses. this can also be used for * searching for specific topics and summarising the results * or so +* save collated data or generated analysis or more to the provided data store and retrieve +them later to augment the analysis / generation then. Also could be used to summarise chat +session till a given point and inturn save the summary into data store and later retrieve +the summary and continue the chat session using the summary and thus with a reduced context +window to worry about. + +* use your imagination and ai models capabilities as you see fit, without restrictions from +others. + The tool calling feature has been tested with Gemma3N, Granite4 and GptOss. ALERT: The simple minded way in which this is implemented, it provides some minimal safety @@ -410,7 +420,7 @@ The following tools/functions are currently provided by default * run_javascript_function_code - which can be used to run some javascript code in the browser context. -* data_store_get/set - allows for a basic data store to be used. +* data_store_get/set/delete/list - allows for a basic data store to be used. Currently the ai generated code / expression is run through a simple minded eval inside a web worker mechanism. Use of WebWorker helps avoid exposing browser global scope to the generated code directly. @@ -456,7 +466,7 @@ The bundled simple proxy a non-browser entity. In future it can be further extended to help with other relatively simple yet useful tool calls like -data / documents_store [wip], fetch_rss and so. +fetch_rss and so. * for now fetch_rss can be indirectly achieved using fetch_web_url_raw. @@ -482,8 +492,8 @@ Update the tc_switch to include a object entry for the tool, which inturn includ It should pass these along to the tools web worker, if used. * the result key (was used previously, may use in future, but for now left as is) -Look into tooljs.mjs for javascript and inturn web worker based tool calls and toolweb.mjs -for the simpleproxy.py based tool calls. +Look into tooljs.mjs and tooldb.mjs for javascript and inturn web worker based tool calls and +toolweb.mjs for the simpleproxy.py based tool calls. #### OLD: Mapping tool calls and responses to normal assistant - user chat flow @@ -537,9 +547,9 @@ The default Chat UI theme/look changed to help differentiate between different m history as well as the parts of each message in a slightly better manner. Change the theme slightly between normal and print views (beyond previous infinite height) for better printed chat history. -Initial skeletons of a builtin data store related tool calls, built on browser's indexedDB, without -needing any proxy / additional helper to handle the store. One could use the ai assistant to store -ones (ie end users) own data or data of ai model. +A builtin data store related tool calls, inturn built on browser's indexedDB, without needing any +proxy / additional helper to handle the store. One could use the ai assistant to store ones (ie end +users) own data or data of ai model. #### ToDo @@ -549,7 +559,8 @@ Trap error responses. Handle multimodal handshaking with ai models. -Add fetch_rss and documents|data_store [wip] tool calling, through the simpleproxy.py if and where needed. +Add fetch_rss and may be different document formats processing related tool calling, in turn through +the simpleproxy.py if and where needed. Save used config entries along with the auto saved chat sessions and inturn give option to reload the same when saved chat is loaded.