From 07970fd563ebc647e5b4fb32af25b98ebb8529a2 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 18:40:05 +0530 Subject: [PATCH 001/266] 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 55620be69f5e4d348f0ec9a9cc221d3d828e8853 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 20:18:16 +0530 Subject: [PATCH 002/266] 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 cb373757c3cce2c9f1e5e676697a3ae4cfd73727 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 23:02:30 +0530 Subject: [PATCH 003/266] 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 d6a8916f66dff3e7b2a12b76ec62c254cc279c5a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 23:31:36 +0530 Subject: [PATCH 004/266] 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 cf1a3e370274421ec80d42d04955c75f42054e5a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 00:02:32 +0530 Subject: [PATCH 005/266] 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 11d75d969a005ea191a420189d98d565abdfb84c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 00:18:37 +0530 Subject: [PATCH 006/266] 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 92ce36f89f958f080612624a3918b801c670c5e0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 01:06:23 +0530 Subject: [PATCH 007/266] 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 5c27e19b66d8008f52c1b5d81bbdeb7bd54ed40a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 01:17:19 +0530 Subject: [PATCH 008/266] 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 c20b911b5a777cc6e7129637593fc9173c78a974 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 01:46:23 +0530 Subject: [PATCH 009/266] 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 34a14439b1b276abc2e7fdd7f10a749115d6e7a7 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 02:35:01 +0530 Subject: [PATCH 010/266] 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 aba54f9c3240872d94fc68af2076b442b748cf57 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 02:58:03 +0530 Subject: [PATCH 011/266] 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 79ede619f0688675419ac84af258f5c42bf53e79 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 03:14:27 +0530 Subject: [PATCH 012/266] 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 31e4554683e780ce0d85e38f5a394181a0bbeefa Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 03:31:18 +0530 Subject: [PATCH 013/266] 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 2bf765aca66bac143323533071f328518f5326cd Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 04:03:53 +0530 Subject: [PATCH 014/266] 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 c11e839748c6f8ff83cdedb543e2896e92051728 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 04:22:15 +0530 Subject: [PATCH 015/266] 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 733d434848c7b209179c99e079fbc7276cec4082 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 23:27:08 +0530 Subject: [PATCH 016/266] 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 bac82e4903202d51480e7e502b2502aaf96415a5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 01:20:53 +0530 Subject: [PATCH 017/266] 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 ddfbd88ea6edbeffe6742a79bb803e8291f6981b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 01:36:43 +0530 Subject: [PATCH 018/266] 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 c89524be1b3060f84ff938be0011fd32b9dbd9b8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 02:53:08 +0530 Subject: [PATCH 019/266] 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 6041f721472dd6aea4cca383acf300ded74fa393 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 12:58:37 +0530 Subject: [PATCH 020/266] 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 bcdf4c78e534c526601357566f94389019a5ec8a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 13:01:59 +0530 Subject: [PATCH 021/266] 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 76748cf89cc323a17621200acb0b3ac0130e6205 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 16:08:11 +0530 Subject: [PATCH 022/266] 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 ff342e8931f81080bb9b1d1f1960a294162aaf22 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 17:19:51 +0530 Subject: [PATCH 023/266] 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 68341661b68a7fa9e8c84f8da6cc50eec619262e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 21:21:00 +0530 Subject: [PATCH 024/266] 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 966b8d61bfc7e563b61aafa98a2d08c5cef5e749 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 22:16:34 +0530 Subject: [PATCH 025/266] 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 547e02a349bac9e45f41e04e7437dcb1c63dccb0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 23:50:35 +0530 Subject: [PATCH 026/266] 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 beeb86d2b5f9b2ac8568be04af6c928433d0ef9f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 00:41:19 +0530 Subject: [PATCH 027/266] 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 074182a75d447f27fef3651003baee5a060eb7fd Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 01:19:37 +0530 Subject: [PATCH 028/266] 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 a766fdf363da6d9ff00cb9cc1b10a238e5c4d8cc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 02:57:17 +0530 Subject: [PATCH 029/266] 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 6dbbcb0b8ec3cfe6c5571c89067ff3090ca9d9c5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 03:18:09 +0530 Subject: [PATCH 030/266] 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 f669ba6e5e9e8a59493cd58a89fbdbf870bbcb5d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 03:32:58 +0530 Subject: [PATCH 031/266] 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 6fa44402913477c556533c59eb480a9740fb9568 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 22:11:25 +0530 Subject: [PATCH 032/266] 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 c3670cc4a249930c40d5d39a8739fdfd2ac20e90 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 22:31:31 +0530 Subject: [PATCH 033/266] 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 a5bf49b81b1a63601420d015e918d3b86814361e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 23:07:44 +0530 Subject: [PATCH 034/266] 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 7bd3ed8b3c4b1ee7ddc330aab1418684af78db2a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 00:54:21 +0530 Subject: [PATCH 035/266] 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 75f6e577f5dc20ea587c0672dae821343dc06eba Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 00:58:23 +0530 Subject: [PATCH 036/266] 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 05a244865b7a375f97f1206cb25ac2351472422d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 01:06:00 +0530 Subject: [PATCH 037/266] 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 7b7c57f05612379703188df07ad29da55f857345 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 01:34:22 +0530 Subject: [PATCH 038/266] 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 672e168f3b37523b7b418d2238c74bca732c56c9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 01:56:34 +0530 Subject: [PATCH 039/266] 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 06a54801a10d724a1b0962d0ddef60c6f8c860cf Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 02:28:42 +0530 Subject: [PATCH 040/266] 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 2899c8452d9cd097a9f154be58c3a0ff882a3d91 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 02:35:24 +0530 Subject: [PATCH 041/266] 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 6e7c9f5ddea962eb95849d3e008b440a1541ad3e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 16:48:40 +0530 Subject: [PATCH 042/266] 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 7119b5a756462a3f4b004a7133ac4542fb72f18c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 17:59:06 +0530 Subject: [PATCH 043/266] 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 0d8ce7021e257a01a1e0d273f9a759c6b9415821 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 18:28:49 +0530 Subject: [PATCH 044/266] 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 4ab4462688721c708a45b64563ed4382892da0ec Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 18:36:25 +0530 Subject: [PATCH 045/266] 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 d59fa84c4e93b80d25be5c29f95e325341456db0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 18:52:18 +0530 Subject: [PATCH 046/266] 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 5c4ee5137bd32902380c145d21ee57bb812ab76a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 19:03:48 +0530 Subject: [PATCH 047/266] 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 8b63a41e435aa9e68c627dede29449f28c2190d0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 19:33:24 +0530 Subject: [PATCH 048/266] 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 68421cc52fbd1df2d6aace02a5b9e82879ad6b28 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 21:02:22 +0530 Subject: [PATCH 049/266] 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 6cc1130ab2eba3874d2e4bdcb4299687b6b0395e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 21:36:11 +0530 Subject: [PATCH 050/266] 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 e707d91017e5d8eb80b82ee7712c1824c3ae67f3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 22:26:48 +0530 Subject: [PATCH 051/266] 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 674312b8f43605679005b6b4b2f7195982f26021 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 23:27:03 +0530 Subject: [PATCH 052/266] 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 df5cfac2fb3761839d909308a94ddf816f8222de Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 00:04:29 +0530 Subject: [PATCH 053/266] 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 f8749a36fc462fcb3d057f113b0b76ffe7e92b65 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 01:20:27 +0530 Subject: [PATCH 054/266] 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 9f234e3d96541755a7f87bb39ee0543d9ed0e831 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 03:28:48 +0530 Subject: [PATCH 055/266] 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 42b3fe19282e97e5d1348e915acdb2db1dcc3f7f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 03:35:55 +0530 Subject: [PATCH 056/266] 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 29a6deb67601a5bd946bbc42978c2dce75c348b3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 04:13:34 +0530 Subject: [PATCH 057/266] 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 8a28b3304fd36ebcb3e77d755f41381bd6a49e47 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 05:31:08 +0530 Subject: [PATCH 058/266] 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 e43342bc280ceefe38185c13dab21738c942c5fc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 18:20:20 +0530 Subject: [PATCH 059/266] 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 d0c3939036c7ba2d6b3e1580315e254c31324c95 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 21:48:18 +0530 Subject: [PATCH 060/266] 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 84fd55b85a90184dd8f90383780b1306faa76a04 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 22:10:27 +0530 Subject: [PATCH 061/266] 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 ada6fd151297519275fa696e8e337e889e9a1b00 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 22:52:07 +0530 Subject: [PATCH 062/266] 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 ae62f3acd87baa43b1a017937edf6b4367c327a8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 23:13:02 +0530 Subject: [PATCH 063/266] 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 1f685f1e6f5a76d65cfa80e809ddb3139ab5acd1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 23:31:07 +0530 Subject: [PATCH 064/266] 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 32bb28ee1810732bbe76f598bf0f84d6af427522 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 11:52:21 +0530 Subject: [PATCH 065/266] 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 807589a9dfff936601795a5267f1b53c1c4507a3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 17:45:33 +0530 Subject: [PATCH 066/266] 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 27bb63d636ba5b5e3884b78b4c245d2fca9fded4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:06:56 +0530 Subject: [PATCH 067/266] 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 dc58ac7b6f020b78d736f8833e0a9d080403d271 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:18:45 +0530 Subject: [PATCH 068/266] 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 d829e2fe5dd2e6d7a408fe540bff01006a3bc063 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:39:02 +0530 Subject: [PATCH 069/266] 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 5a3696eb3417cbfd6f6253d95f48d803bf6debc3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:54:20 +0530 Subject: [PATCH 070/266] 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 4770ef39b18d531678d4eaeabe06d0c2fb2a82e6 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:58:06 +0530 Subject: [PATCH 071/266] 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 3e057469edc0ff9ca9b143c201403b30faa77b12 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 19:02:55 +0530 Subject: [PATCH 072/266] 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 d297cf522e4eb48a5f7a6be2c11e6374d14a677e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 22:47:21 +0530 Subject: [PATCH 073/266] 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 a4d1028f24798226e2a0d22e39f66ec513b07100 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 00:11:01 +0530 Subject: [PATCH 074/266] 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 bf116f9b7b78599b4a5d8094538e8800c8a98d90 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 00:42:04 +0530 Subject: [PATCH 075/266] 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 702c1ca3ba89f0696179ce16572e0ff82b0fba11 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 01:30:46 +0530 Subject: [PATCH 076/266] 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 874aef37b7fc61024a977d8e9b5ddef62e2eed9c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 01:39:35 +0530 Subject: [PATCH 077/266] 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 00cb967d0afa86f4a4a58861d126b4f21419264e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 01:50:26 +0530 Subject: [PATCH 078/266] 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 adff879e3c1ca5bb54a5d77c89e284592fecc1ea Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 02:08:38 +0530 Subject: [PATCH 079/266] 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 cf324a99f512103961279c7e0f18aab4cb7936e8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 02:52:37 +0530 Subject: [PATCH 080/266] 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 926d871439e55942f4a0c4c58ae545ab948940bc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 03:02:53 +0530 Subject: [PATCH 081/266] 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 54e03ae71254de3046e63eb5f51e1123cbbee808 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 03:56:35 +0530 Subject: [PATCH 082/266] 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 a2c1d43a2e343a75ea5c228bf149010f12dc278f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 04:13:46 +0530 Subject: [PATCH 083/266] 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 724b3825a107ca47c24260d24981ef793ce92f99 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 05:22:13 +0530 Subject: [PATCH 084/266] 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 e1e4fc3eb62e2decc84354501cc80305a8993b6d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 05:36:43 +0530 Subject: [PATCH 085/266] 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 8f9f0980696a1f1048103ede07fc3cbc41c14958 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 18:01:25 +0530 Subject: [PATCH 086/266] 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 750bf4e0c4c3e579a6c82af29e2953ea64b6ede8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 18:25:50 +0530 Subject: [PATCH 087/266] 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 35f68324ef9e22154d0e172d7f6d0cde652b0ca7 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 22:16:30 +0530 Subject: [PATCH 088/266] 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 b78dbc569e8f4998ab8ef252b9beec4809f2ba8c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 22:46:08 +0530 Subject: [PATCH 089/266] 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 84708b8ca866d81e2a901d1024ffac10d7d504bd Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 18 Oct 2025 01:27:53 +0530 Subject: [PATCH 090/266] 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 7cd77f4926d41ae121c0acd4eff04938a12ca97d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 18 Oct 2025 18:40:09 +0530 Subject: [PATCH 091/266] 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 8abbff2ad376fe23c1eecc7d038982fff6cc38cd Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 00:41:14 +0530 Subject: [PATCH 092/266] 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 cbc0e7b6a92b945108fea9e53ebacf8e10474289 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 13:58:27 +0530 Subject: [PATCH 093/266] 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 218dc9b23fc67f4772653f58e9e4f1cefd9d32b1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 14:55:13 +0530 Subject: [PATCH 094/266] 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 abcf59d0715c4e80be61436f4fa2778e27628041 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 15:05:55 +0530 Subject: [PATCH 095/266] 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 4f16546d17cfe51b9864236f202a17e4e2339ff9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 15:35:34 +0530 Subject: [PATCH 096/266] 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 ac192262665b86a7a9abe34cdd5772047d53c691 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 15:48:58 +0530 Subject: [PATCH 097/266] 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 0853c3ed2d19c55b116077f5304b0f79302ae23a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 16:08:19 +0530 Subject: [PATCH 098/266] 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 2001b05d505fd896640d48bbddb1ae2796273b8c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 16:59:32 +0530 Subject: [PATCH 099/266] 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 231fd43cccf881bbc45e236179c809963191532b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 17:28:46 +0530 Subject: [PATCH 100/266] 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 82804940230fa41056bef6aa3493caed2f81cbf8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 18:59:18 +0530 Subject: [PATCH 101/266] 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 122484ac20ce09272cbe735e26f8da4d4c57c444 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 19:45:12 +0530 Subject: [PATCH 102/266] 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 913de696917e739a949867911ef8cd40ff1c1d05 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 19:55:12 +0530 Subject: [PATCH 103/266] 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 2f38893646f09ff967a33de7e39503e9523a5c04 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 21:01:23 +0530 Subject: [PATCH 104/266] 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 1ed31cbf111708f8c92ef9bf20860d4f9fdc8329 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 01:57:57 +0530 Subject: [PATCH 105/266] 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 d24b5137e45c8881f37b1c5248e3667a3b53d375 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 02:14:17 +0530 Subject: [PATCH 106/266] 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 abd82204f7768b00721562b15e840c24edec5c31 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 18:53:49 +0530 Subject: [PATCH 107/266] 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 8294be0b06d65ff765540937c70ac81a64af1705 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 19:37:18 +0530 Subject: [PATCH 108/266] 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 b902cdd4c84f8a891f51ae259452008b85ce61b1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 19:57:53 +0530 Subject: [PATCH 109/266] 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 ea7b7d9e9d44b24ab3d989975ed871f9fa89e6d1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 21:44:30 +0530 Subject: [PATCH 110/266] 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 c61b75b4c00e52d0e5815384a967cc25744be693 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 21:55:23 +0530 Subject: [PATCH 111/266] 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 6328a9b808ba1eb8c25d3f17c58d5de6912c219c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 22:00:12 +0530 Subject: [PATCH 112/266] 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 7d3d57a856fdb3ceb9fbac7945139ffd07e96b46 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 22:15:58 +0530 Subject: [PATCH 113/266] 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 f78f430349f0773dbef32b0c50f78f938e71bd82 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 23:06:44 +0530 Subject: [PATCH 114/266] 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 d517e6c37c596465b6a446b0850de20390bda8a4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 23:37:33 +0530 Subject: [PATCH 115/266] 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 d09e23b0c3258dd7d503042bd4c056e4e0b2aaac Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 23:44:39 +0530 Subject: [PATCH 116/266] 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 f994cd504d9942bb709af438a61f3897fabf6d18 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 00:07:22 +0530 Subject: [PATCH 117/266] 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 4f943e728f40424656377c087d8fe503370eaac3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 00:11:14 +0530 Subject: [PATCH 118/266] 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 f9ef84be4da9d364728d7fe964faf824ee5b63da Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 00:20:41 +0530 Subject: [PATCH 119/266] 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 412b67879387934349975382cb0f123a5eb5a260 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 08:54:15 +0530 Subject: [PATCH 120/266] 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 9a3bafe98147981345f7bdf58436db5bad4a2250 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 09:43:34 +0530 Subject: [PATCH 121/266] 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 ee3c8850d537d149dfca3136733203f6ca359f73 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 17:33:54 +0530 Subject: [PATCH 122/266] 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 9b5ee22029b9926a9ae05b0333fa13335765850d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 18:02:53 +0530 Subject: [PATCH 123/266] 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 013e479ba7ff7774afa877c42c6a764a41cb9c72 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 18:30:42 +0530 Subject: [PATCH 124/266] 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 2e1ff7167c5941af84e7997cec08edd6c9b2a587 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 19:01:50 +0530 Subject: [PATCH 125/266] 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 3d8a23e9552d1aa0b90cab867abfad3d6d044fae Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 19:30:48 +0530 Subject: [PATCH 126/266] 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 ce6a708ce17e487fce0924cfe8220f1ecbec2433 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 20:57:22 +0530 Subject: [PATCH 127/266] 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 5e2651c21688bc8b8a685537eac37f794df85a29 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 22:39:05 +0530 Subject: [PATCH 128/266] 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 ee56ea24e5678c5ac1b3e390c34e22112c2e2b65 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 22:49:07 +0530 Subject: [PATCH 129/266] 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 7ee75a6b126a52b439d62a6701f74fadbd9a4e27 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 23:18:06 +0530 Subject: [PATCH 130/266] 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 b87e348cb5001e52b473963a62049f6075a822bc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 00:36:54 +0530 Subject: [PATCH 131/266] 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 797b5a9284e7e4495e7fb36341d73e701721e0e8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 01:11:22 +0530 Subject: [PATCH 132/266] 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 e4c74f9651bf1712c2d2bde7675bf88849d7e327 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 01:31:30 +0530 Subject: [PATCH 133/266] 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 436d10981e3d60d43dc5457ee3217ed28cd36c11 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 03:00:09 +0530 Subject: [PATCH 134/266] 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 a20a731f50c1a36131f6c5130b69fc57b3b9bb4d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 03:40:55 +0530 Subject: [PATCH 135/266] 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 33537f5227fd7c574595d1324b64c7d29cd2fcbc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 04:18:36 +0530 Subject: [PATCH 136/266] 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 86f7f27fdc2a8a2b1f24a123efd58930e912fb3b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 05:05:24 +0530 Subject: [PATCH 137/266] 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 7a156d7725baeda6dcddd71232b4ca9a46c6a69f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 19:04:33 +0530 Subject: [PATCH 138/266] 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 aa69ccec803ef968a165f6b0a0f771e474603d22 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 20:51:10 +0530 Subject: [PATCH 139/266] 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 00678c97d2f60d4853e543fc2123df61118ca8be Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 20:55:44 +0530 Subject: [PATCH 140/266] 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 a5d7f4104ace13cd65285529a4d8532e5c62b1a6 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 12:01:56 +0530 Subject: [PATCH 141/266] 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 6bff69f9951cb142db970f33a0e5dd8d3d78b406 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 15:51:48 +0530 Subject: [PATCH 142/266] 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 0407c78271e36156da33b29fee5818f653da0522 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 15:54:47 +0530 Subject: [PATCH 143/266] 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 41addbb2ee2d1eec49f67f09cc4b41460c73c163 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 19:13:30 +0530 Subject: [PATCH 144/266] 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 ba304229d7709e034e6152a01c71420066ef7caa Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 21:12:48 +0530 Subject: [PATCH 145/266] 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 5352ccb71247c40a5fcfaf61869c5892679b9c10 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 23:12:06 +0530 Subject: [PATCH 146/266] 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 76246748610811e87d47029ae95265dd1e5444a6 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 23:25:05 +0530 Subject: [PATCH 147/266] 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 7e6a1c27fb1e5d0cbb99ccff547f037380138d2d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 09:09:05 +0530 Subject: [PATCH 148/266] 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 f7591c1944a05117aa870aafb22782c1c0b03bc5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 09:43:49 +0530 Subject: [PATCH 149/266] 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 2bfc0f6b816421e6b4beb53294ba805e904eae56 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 10:32:19 +0530 Subject: [PATCH 150/266] 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 8f7888fcdea15a8f63b093eed4d33aab931761d6 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 11:00:16 +0530 Subject: [PATCH 151/266] 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 2e2024e5482d03642e0528850b8501e7dbfb861a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 11:10:08 +0530 Subject: [PATCH 152/266] 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 d13ba3990ef30d399629c1ebc54a9bd9ef4bb10c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 11:29:13 +0530 Subject: [PATCH 153/266] 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 66d8b7fdcea1ee491e83de0b5118c648ee00c2ce Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 15:44:17 +0530 Subject: [PATCH 154/266] 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 3d3e37269ca22a2b63dc1d927e7ebe72f5e027aa Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 00:23:04 +0530 Subject: [PATCH 155/266] 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 2aa9d73b3c33f3e18cd0e00f4ded4f528df4c15c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 01:43:52 +0530 Subject: [PATCH 156/266] 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 6fc281afaae6aaaf18b36cbe40b45bc79fd6440b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 02:17:15 +0530 Subject: [PATCH 157/266] 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 99239394498934c62b537a93c34f02c7c669f414 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 02:37:34 +0530 Subject: [PATCH 158/266] 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 ca7156db7b86c0dd11d00dfaa5e403226bc54c9f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 02:50:28 +0530 Subject: [PATCH 159/266] 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 cec3187a98138b46354aa9858b3002ec1968a065 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 03:34:02 +0530 Subject: [PATCH 160/266] 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 1e3c00bf3d66667d7d6ebeae5fd0b3d8ea085b7c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 15:16:14 +0530 Subject: [PATCH 161/266] 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 1a8b2317950ecac5790c41a42dc7da032c150bc6 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 16:14:00 +0530 Subject: [PATCH 162/266] 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 32774ac1f15659377e5a34982da3654bb9dabb58 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 17:05:58 +0530 Subject: [PATCH 163/266] 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 3328c0df4f50a0f7da111a846cbb5f4f96ffaf1f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 17:55:45 +0530 Subject: [PATCH 164/266] 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 643d41b4292780917727e2a85fc1f52a21b046ad Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 18:14:26 +0530 Subject: [PATCH 165/266] 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 c77d8e17e7b6385945a6122355b3c36c9e5a4ddf Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 18:56:24 +0530 Subject: [PATCH 166/266] 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 8292814b7710c95a9e1e1ddaa95ceba1685c3e51 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 03:38:34 +0530 Subject: [PATCH 167/266] 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/266] 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 e05fd5320eaf32c5b582fdef06934235a47bc0f5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 15:39:32 +0530 Subject: [PATCH 169/266] 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 84d30067c532681f62c8b6881d3af98e9b65d0a0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 19:32:44 +0530 Subject: [PATCH 170/266] 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 1d5472ea766a7da65aeb07b698ed2dbc67f8a224 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 20:11:14 +0530 Subject: [PATCH 171/266] 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 25635b5db88579586d253a766e8fe96c19ff5836 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 21:14:21 +0530 Subject: [PATCH 172/266] 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 a95ab447cba5c0ec7690b430a67976cf5457bb8f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 22:06:57 +0530 Subject: [PATCH 173/266] 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 3665dae0dc3d5a1265b117cd3319d1109231c637 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 01:35:09 +0530 Subject: [PATCH 174/266] 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 e859563b784427c511b3ff4f18429cf6952a7ebe Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 01:13:12 +0530 Subject: [PATCH 175/266] 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 dfc06166b62dc1590f2df419541f4e77d390b1ba Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 01:53:28 +0530 Subject: [PATCH 176/266] 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 a443380924a16371e3c4612d5c92d59f9fbe1f7a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 02:29:46 +0530 Subject: [PATCH 177/266] 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 6a489a52a698558ab8749122b056d858301b1cea Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 03:13:32 +0530 Subject: [PATCH 178/266] 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 544226a0cbbbca7c507e4e958079a9c4e6638218 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 03:28:07 +0530 Subject: [PATCH 179/266] 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 219de66739d506cc4cfe50c1a4dba69d3495f241 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 04:24:10 +0530 Subject: [PATCH 180/266] 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 e584c82f459b2e99bfbc09a3aad08d62adeeb29a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 04:36:47 +0530 Subject: [PATCH 181/266] 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 e36e7aa772b99b293b8331851149d8ab47908f63 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 05:51:38 +0530 Subject: [PATCH 182/266] 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 8d65c2424dfef88596eec64528a80d33a95e5cee Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 05:59:45 +0530 Subject: [PATCH 183/266] 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 4c9808148d2c5561510defe924889a018d2ded0e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 06:14:27 +0530 Subject: [PATCH 184/266] 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 07153978904f30f77275612696b7b0d64a35ad2c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 06:24:01 +0530 Subject: [PATCH 185/266] 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 7bcdbd1e9d7ac36d17eebfda6efe2fac4b46226f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 06:28:27 +0530 Subject: [PATCH 186/266] 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 2441f0156aecd733f3ea346e0e0991ef4c00d95f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 07:11:50 +0530 Subject: [PATCH 187/266] 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. From e70b8c9f8b85fee879c4d9196de93a4183b43694 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 15:01:52 +0530 Subject: [PATCH 188/266] SimpleChatTC:ToolsWorker: Update note to flow with chat session id --- tools/server/public_simplechat/toolsworker.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/toolsworker.mjs b/tools/server/public_simplechat/toolsworker.mjs index 6706a44721d6a..6bd27728d26b2 100644 --- a/tools/server/public_simplechat/toolsworker.mjs +++ b/tools/server/public_simplechat/toolsworker.mjs @@ -5,8 +5,8 @@ // /** - * Expects to get a message with id, name and code to run - * Posts message with id, name and data captured from console.log outputs + * Expects to get a message with id (session and toolcall), name and code to run + * Posts message with id (session and toolcall), name and data captured from console.log outputs */ From 88bb974f6cc497eabf6f61d7eb1f2927891f0a92 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 15:04:10 +0530 Subject: [PATCH 189/266] SimpleChatTC:Seperate out actual nw handshake - initial go move the actual chat handshake with ai server into a seperate code to an extent. also initial anchor to trap handshake http error responses Rather come to think of it, its better to move this into SimpleChat class. Use finally to ensure any needed cleanup for handle_user_submit occurs within itself. --- tools/server/public_simplechat/simplechat.js | 58 +++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 044e889d2e8e3..e6261c2bd9d1a 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1042,7 +1042,6 @@ class MultiChatUI { let msg = `ERRR:SimpleChat\nMCUI:HandleUserSubmit:${this.curChatId}\n${reason.name}:${reason.message}`; console.error(msg.replace("\n", ":")); alert(msg); - this.ui_reset_userinput(); }); }); @@ -1110,8 +1109,33 @@ class MultiChatUI { } + /** + * @param {SimpleChat} chat + * @param {string} apiEP + */ + async handle_chat_hs(chat, apiEP) { + let theUrl = ApiEP.Url(gMe.baseURL, apiEP); + let theBody = chat.request_jsonstr(apiEP); + console.debug(`DBUG:SimpleChat:MCUI:${chat.chatId}:HandleUserSubmit:${theUrl}:ReqBody:${theBody}`); + + let theHeaders = chat.fetch_headers(apiEP); + let resp = await fetch(theUrl, { + method: "POST", + headers: theHeaders, + body: theBody, + }); + + if (resp.status >= 300) { + + } + + return chat.handle_response(resp, apiEP, this.elDivChat); + } + + /** * 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, @@ -1120,6 +1144,7 @@ class MultiChatUI { * 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 */ @@ -1151,30 +1176,23 @@ class MultiChatUI { } this.chat_show(chat.chatId); - let theUrl = ApiEP.Url(gMe.baseURL, apiEP); - let theBody = chat.request_jsonstr(apiEP); - this.elInUser.value = "working..."; this.elInUser.disabled = true; - console.debug(`DBUG:SimpleChat:MCUI:${chatId}:HandleUserSubmit:${theUrl}:ReqBody:${theBody}`); - let theHeaders = chat.fetch_headers(apiEP); - let resp = await fetch(theUrl, { - method: "POST", - headers: theHeaders, - body: theBody, - }); - let theResp = await chat.handle_response(resp, apiEP, this.elDivChat); - if (chatId == this.curChatId) { - 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"; + try { + let theResp = await this.handle_chat_hs(chat, apiEP) + if (chatId == this.curChatId) { + 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"; + } + } else { + console.debug(`DBUG:SimpleChat:MCUI:HandleUserSubmit:ChatId has changed:[${chatId}] [${this.curChatId}]`); } - } else { - console.debug(`DBUG:SimpleChat:MCUI:HandleUserSubmit:ChatId has changed:[${chatId}] [${this.curChatId}]`); + } finally { + this.ui_reset_userinput(); } - this.ui_reset_userinput(); } /** From 16f2ec84ad0ac47c1fff1374a9874f6100d607b8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 15:22:29 +0530 Subject: [PATCH 190/266] SimpleChatTC:Move chat server handshake to SimpleChat --- tools/server/public_simplechat/simplechat.js | 51 ++++++++++---------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index e6261c2bd9d1a..14f69b7ac9129 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -735,6 +735,31 @@ class SimpleChat { return theResp; } + /** + * Handle the chat handshake with the ai server + * @param {string} baseURL + * @param {string} apiEP + * @param {HTMLDivElement} elDivChat - used to show chat response as it is being generated/recieved in streaming mode + */ + async handle_chat_hs(baseURL, apiEP, elDivChat) { + let theUrl = ApiEP.Url(baseURL, apiEP); + let theBody = this.request_jsonstr(apiEP); + console.debug(`DBUG:SimpleChat:${this.chatId}:HandleChatHS:${theUrl}:ReqBody:${theBody}`); + + let theHeaders = this.fetch_headers(apiEP); + let resp = await fetch(theUrl, { + method: "POST", + headers: theHeaders, + body: theBody, + }); + + if (resp.status >= 300) { + + } + + return this.handle_response(resp, apiEP, elDivChat); + } + /** * Call the requested tool/function. * Returns undefined, if the call was placed successfully @@ -1109,30 +1134,6 @@ class MultiChatUI { } - /** - * @param {SimpleChat} chat - * @param {string} apiEP - */ - async handle_chat_hs(chat, apiEP) { - let theUrl = ApiEP.Url(gMe.baseURL, apiEP); - let theBody = chat.request_jsonstr(apiEP); - console.debug(`DBUG:SimpleChat:MCUI:${chat.chatId}:HandleUserSubmit:${theUrl}:ReqBody:${theBody}`); - - let theHeaders = chat.fetch_headers(apiEP); - let resp = await fetch(theUrl, { - method: "POST", - headers: theHeaders, - body: theBody, - }); - - if (resp.status >= 300) { - - } - - return chat.handle_response(resp, apiEP, this.elDivChat); - } - - /** * Handle user query submit request, wrt specified chat session. * @@ -1180,7 +1181,7 @@ class MultiChatUI { this.elInUser.disabled = true; try { - let theResp = await this.handle_chat_hs(chat, apiEP) + let theResp = await chat.handle_chat_hs(gMe.baseURL, apiEP, this.elDivChat) if (chatId == this.curChatId) { this.chat_show(chatId); if (theResp.trimmedContent.length > 0) { From f8503f40d7a7cf051aa35b1769d5bf07d21c0b46 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 15:48:02 +0530 Subject: [PATCH 191/266] SimpleChatTC:Raise Error on Ai Chat server handshake NotOk resp --- tools/server/public_simplechat/simplechat.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 14f69b7ac9129..6e6ffdd581d06 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -742,6 +742,13 @@ class SimpleChat { * @param {HTMLDivElement} elDivChat - used to show chat response as it is being generated/recieved in streaming mode */ async handle_chat_hs(baseURL, apiEP, elDivChat) { + class ChatHSError extends Error { + constructor(/** @type {string} */message) { + super(message); + this.name = 'ChatHSError' + } + } + let theUrl = ApiEP.Url(baseURL, apiEP); let theBody = this.request_jsonstr(apiEP); console.debug(`DBUG:SimpleChat:${this.chatId}:HandleChatHS:${theUrl}:ReqBody:${theBody}`); @@ -754,7 +761,7 @@ class SimpleChat { }); if (resp.status >= 300) { - + throw new ChatHSError(`HandleChatHS:GotResponse:NotOk:${resp.status}:${resp.statusText}`); } return this.handle_response(resp, apiEP, elDivChat); From 7463b8f767341281ad6808e00c60ddfc56fe3964 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 20:53:01 +0530 Subject: [PATCH 192/266] SimpleChatTC: Update readme --- tools/server/public_simplechat/readme.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 3c873d0a06d8b..b1bfc6007cdb7 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -417,15 +417,14 @@ 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. +* run_javascript_function_code - which can be used to run ai generated or otherwise javascript code using browser's js capabilities. * 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. -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. +All of the above are run from inside web worker contexts. 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. ##### using bundled simpleproxy.py (helps bypass browser cors restriction, ...) @@ -551,12 +550,13 @@ A builtin data store related tool calls, inturn built on browser's indexedDB, wi 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. +Trap http response errors and inform user the specific error returned by ai server. + + #### ToDo Is the tool call promise land trap deep enough, need to think through and explore around this once later. -Trap error responses. - Handle multimodal handshaking with ai models. Add fetch_rss and may be different document formats processing related tool calling, in turn through @@ -567,6 +567,8 @@ same when saved chat is loaded. MAYBE make the settings in general chat session specific, rather than the current global config flow. +Provide tool to allow for specified pdf files to be converted to equivalent plain text form, so that ai +can be used to work with the content in those PDFs. ### Debuging the handshake and beyond From 4c3398aafb17aff8df68ab80d57de164c2f3fbd4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 21:31:27 +0530 Subject: [PATCH 193/266] SimpleChatTC:SimpleProxy: Enable allowing or not requested feature --- .../public_simplechat/local.tools/simpleproxy.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 8b17d012c053b..b6018676f27cd 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -46,6 +46,8 @@ gConfigNeeded = [ '--allowed.domains', '--bearer.insecure' ] +gAllowedCalls = [ "urltext", "urlraw" ] + def bearer_transform(): """ @@ -146,6 +148,18 @@ 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 """ + queryParams = urllib.parse.parse_qs(pr.query) + url = queryParams['url'] + print(f"DBUG:HandleAUM:Url:{url}") + url = url[0] + if (not url) or (len(url) == 0): + ph.send_error(400, f"WARN:HandleAUM:MissingUrl/UnknownQuery?!") + return + urlParts = url.split('.',1) + if not (urlParts[0] in gAllowedCalls): + ph.send_error(403, f"WARN:HandleAUM:Forbidded:{urlParts[0]}") + return + print(f"INFO:HandleAUM:Availability ok for:{urlParts[0]}") ph.send_response_only(200, "bharatavarshe") ph.send_header('Access-Control-Allow-Origin', '*') ph.end_headers() From e8f0e6e78fafd3759c5cf7fd659ab8f4184f49aa Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 22:06:43 +0530 Subject: [PATCH 194/266] SimpleChatTC:SimpleProxy:Pdf2Text:Initial plumbing Get the pdf2text request for processing. --- .../local.tools/simpleproxy.py | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index b6018676f27cd..dd74e539f0d94 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -46,7 +46,7 @@ gConfigNeeded = [ '--allowed.domains', '--bearer.insecure' ] -gAllowedCalls = [ "urltext", "urlraw" ] +gAllowedCalls = [ "urltext", "urlraw", "pdf2text" ] def bearer_transform(): @@ -128,6 +128,12 @@ def do_GET(self): self.send_error(400, f"WARN:{acGot['Msg']}") else: handle_urltext(self, pr) + case '/pdf2text': + acGot = self.auth_check() + if not acGot['AllOk']: + self.send_error(400, f"WARN:{acGot['Msg']}") + else: + handle_pdf2text(self, pr) case '/aum': handle_aum(self, pr) case _: @@ -372,6 +378,35 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.send_error(502, f"WARN:UrlTextFailed:{exc}") +def do_pdf2text(fUrl: str): + import pypdf + + + +gAllowedPdfUrlTypes = [ "file", "http", "https" ] + +def handle_pdf2text(ph: ProxyHandler, pr: urllib.parse.ParseResult): + """ + Handle requests to pdf2text path, which is used to extract plain text + from the specified pdf file. + """ + queryParams = urllib.parse.parse_qs(pr.query) + url = queryParams['url'] + print(f"DBUG:HandlePdf2Text:Url:{url}") + url = url[0] + if (not url) or (len(url) == 0): + ph.send_error(400, f"WARN:HandlePdf2Text:MissingUrl!") + return + urlParts = url.split('://',1) + if not (urlParts[0] in gAllowedPdfUrlTypes): + ph.send_error(403, f"WARN:HandlePdf2Text:ForbiddedUrlType:{urlParts[0]}:AllowedUrlTypes:{gAllowedPdfUrlTypes}") + return + print(f"INFO:HandlePdf2Text:Processing:{url}") + ph.send_response_only(200, "Pdf2Text Response follows") + ph.end_headers() + + + def load_config(): """ Allow loading of a json based config file From c7a22b7c7d1b6f2239de01b1063fdf673bbfe15d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 22:16:02 +0530 Subject: [PATCH 195/266] SimpleChatTC:SimpleProxy:Pdf2Text: Move handling url to its own --- .../public_simplechat/local.tools/simpleproxy.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index dd74e539f0d94..57b108c41dd59 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -378,9 +378,12 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.send_error(502, f"WARN:UrlTextFailed:{exc}") -def do_pdf2text(fUrl: str): +def process_pdf2text(url: str): import pypdf - + urlParts = url.split('://',1) + if not (urlParts[0] in gAllowedPdfUrlTypes): + return { 'status': 403, 'msg': f"WARN:HandlePdf2Text:ForbiddedUrlType:{urlParts[0]}:AllowedUrlTypes:{gAllowedPdfUrlTypes}" } + return { 'status': 500, 'msg': 'Not yet implemented' } gAllowedPdfUrlTypes = [ "file", "http", "https" ] @@ -397,11 +400,11 @@ def handle_pdf2text(ph: ProxyHandler, pr: urllib.parse.ParseResult): if (not url) or (len(url) == 0): ph.send_error(400, f"WARN:HandlePdf2Text:MissingUrl!") return - urlParts = url.split('://',1) - if not (urlParts[0] in gAllowedPdfUrlTypes): - ph.send_error(403, f"WARN:HandlePdf2Text:ForbiddedUrlType:{urlParts[0]}:AllowedUrlTypes:{gAllowedPdfUrlTypes}") - return print(f"INFO:HandlePdf2Text:Processing:{url}") + gotP2T = process_pdf2text(url) + if (gotP2T['status'] != 200): + ph.send_error(gotP2T['status'], gotP2T['msg'] ) + return ph.send_response_only(200, "Pdf2Text Response follows") ph.end_headers() From 57874b106cc2c13f686b821f1606cf437464df04 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 22:24:13 +0530 Subject: [PATCH 196/266] SimpleChatTC:SimpleProxy:Pdf2Text: Initial go --- .../public_simplechat/local.tools/simpleproxy.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 57b108c41dd59..cb19bc8654467 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -380,10 +380,17 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): def process_pdf2text(url: str): import pypdf + import io urlParts = url.split('://',1) if not (urlParts[0] in gAllowedPdfUrlTypes): return { 'status': 403, 'msg': f"WARN:HandlePdf2Text:ForbiddedUrlType:{urlParts[0]}:AllowedUrlTypes:{gAllowedPdfUrlTypes}" } - return { 'status': 500, 'msg': 'Not yet implemented' } + fPdf = open(urlParts[1], 'rb') + dPdf = fPdf.read() + tPdf = "" + oPdf = pypdf.PdfReader(io.BytesIO(dPdf)) + for (pn, pd) in enumerate(oPdf.pages): + tPdf = tPdf + pd.extract_text() + return { 'status': 200, 'msg': "Pdf2Text Response follows", 'data': tPdf } gAllowedPdfUrlTypes = [ "file", "http", "https" ] @@ -405,8 +412,9 @@ def handle_pdf2text(ph: ProxyHandler, pr: urllib.parse.ParseResult): if (gotP2T['status'] != 200): ph.send_error(gotP2T['status'], gotP2T['msg'] ) return - ph.send_response_only(200, "Pdf2Text Response follows") + ph.send_response_only(gotP2T['status'], gotP2T['msg']) ph.end_headers() + ph.wfile.write(gotP2T['data']) From d9b30a9be1cc2ff54f0a47c19faf174b1e59371f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 22:32:57 +0530 Subject: [PATCH 197/266] SimpleChatTC:SimpleProxy:Pdf2Text: js side initial plumbing Expose pdf2text tool call to ai server and handshake with simple proxy for the same. --- tools/server/public_simplechat/toolweb.mjs | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index eeecf846b0f19..566b65c1a0de9 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -269,6 +269,63 @@ async function searchwebtext_setup(tcs) { } +// +// Pdf2Text +// + + +let pdf2text_meta = { + "type": "function", + "function": { + "name": "pdf2text", + "description": "Fetch pdf from requested web / file url through a proxy server and return its text content after converting pdf to text, in few seconds", + "parameters": { + "type": "object", + "properties": { + "url":{ + "type":"string", + "description":"url of the pdf that will be got and inturn converted to text to some extent" + } + }, + "required": ["url"] + } + } + } + + +/** + * Implementation of the pdf to text logic. + * Expects a simple minded proxy server to be running locally + * * listening on a configured port + * * expecting http requests + * * with a query token named url wrt pdf2text path, + * which gives the actual url to fetch + * * gets the requested pdf and converts to text, before returning same. + * 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 pdf2text_run(chatid, toolcallid, toolname, obj) { + return proxyserver_get_1arg(chatid, toolcallid, toolname, obj, 'pdf2text', 'url', encodeURIComponent(obj.url)); +} + + +/** + * Setup pdf2text for tool calling + * NOTE: Currently the logic is setup for the bundled simpleproxy.py + * @param {Object>} tcs + */ +async function pdf2text_setup(tcs) { + return proxyserver_tc_setup('Pdf2Text', 'pdf2text', 'pdf2text', { + "handler": pdf2text_run, + "meta": pdf2text_meta, + "result": "" + }, tcs); +} + + /** * Used to get hold of the web worker to use for running tool/function call related code @@ -284,5 +341,6 @@ export async function init(toolsWorker) { await fetchweburlraw_setup(tc_switch) await fetchweburltext_setup(tc_switch) await searchwebtext_setup(tc_switch) + await pdf2text_setup(tc_switch) return tc_switch } From 909993c111d6b368339c7dbfe4576ac72846925a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 23:12:27 +0530 Subject: [PATCH 198/266] SimpleChatTC:Pdf2Text: cleanup initial go Make the description bit more explicit with it supporting local file paths as part of the url scheme, as the tested ai model was cribbing about not supporting file url scheme. Need to check if this new description will make things better. Convert the text to bytes for writing to the http pipe. Ensure CORS is kept happy by passing AccessControlAllowOrigin in header. --- .../public_simplechat/local.tools/simpleproxy.py | 10 +++++++--- tools/server/public_simplechat/toolweb.mjs | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index cb19bc8654467..99a7004cd2e74 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -407,14 +407,18 @@ def handle_pdf2text(ph: ProxyHandler, pr: urllib.parse.ParseResult): if (not url) or (len(url) == 0): ph.send_error(400, f"WARN:HandlePdf2Text:MissingUrl!") return - print(f"INFO:HandlePdf2Text:Processing:{url}") + print(f"INFO:HandlePdf2Text:Processing:{url}...") gotP2T = process_pdf2text(url) if (gotP2T['status'] != 200): ph.send_error(gotP2T['status'], gotP2T['msg'] ) return - ph.send_response_only(gotP2T['status'], gotP2T['msg']) + ph.send_response(gotP2T['status'], gotP2T['msg']) + ph.send_header('Content-Type', 'text/text') + # Add CORS for browser fetch, just in case + ph.send_header('Access-Control-Allow-Origin', '*') ph.end_headers() - ph.wfile.write(gotP2T['data']) + print(f"INFO:HandlePdf2Text:ExtractedText:{url}...") + ph.wfile.write(gotP2T['data'].encode('utf-8')) diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 566b65c1a0de9..f2cac18967bc3 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -278,13 +278,13 @@ let pdf2text_meta = { "type": "function", "function": { "name": "pdf2text", - "description": "Fetch pdf from requested web / file url through a proxy server and return its text content after converting pdf to text, in few seconds", + "description": "Fetch pdf from requested web / file path url through a proxy server and return its text content after converting pdf to text, in few seconds", "parameters": { "type": "object", "properties": { "url":{ "type":"string", - "description":"url of the pdf that will be got and inturn converted to text to some extent" + "description":"local file / web (http/https) based url of the pdf that will be got and inturn converted to text to an extent" } }, "required": ["url"] From 3be01289f6ef311e22aeeac0d64811be389b00ac Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 23:52:42 +0530 Subject: [PATCH 199/266] SimpleChatTC:ResultMaxDataLength, Desc Allow user to limit the max amount of result data returned to ai after a tool call. Inturn it is set by default to 2K. Update the pdf2text tool description to try make the local file path support more explicit --- tools/server/public_simplechat/simplechat.js | 11 ++++++++++- tools/server/public_simplechat/toolweb.mjs | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 6e6ffdd581d06..1af93e2b200fd 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1091,7 +1091,11 @@ class MultiChatUI { clearTimeout(this.timers.toolcallResponseTimeout) this.timers.toolcallResponseTimeout = undefined let chat = this.simpleChats[cid]; - chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(tcid, name, data))) + let limitedData = data + if (gMe.tools.iResultMaxDataLength > 0) { + limitedData = data.slice(0, gMe.tools.iResultMaxDataLength) + `\n\n\nALERT: Data too long, was chopped ....` + } + chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(tcid, name, limitedData))) if (this.chat_show(cid)) { if (gMe.tools.auto > 0) { this.timers.toolcallResponseSubmitClick = setTimeout(()=>{ @@ -1336,6 +1340,11 @@ class Me { proxyAuthInsecure: "NeverSecure", searchUrl: SearchURLS.duckduckgo, toolNames: /** @type {Array} */([]), + /** + * Control the length of the tool call result data returned to ai after tool call. + * A value of 0 is treated as unlimited data. + */ + iResultMaxDataLength: 2048, /** * 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. diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index f2cac18967bc3..518ccd6f90626 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -278,13 +278,13 @@ let pdf2text_meta = { "type": "function", "function": { "name": "pdf2text", - "description": "Fetch pdf from requested web / file path url through a proxy server and return its text content after converting pdf to text, in few seconds", + "description": "Read pdf from requested local file / web url through a proxy server and return its text content after converting pdf to text, in few seconds", "parameters": { "type": "object", "properties": { "url":{ "type":"string", - "description":"local file / web (http/https) based url of the pdf that will be got and inturn converted to text to an extent" + "description":"local file path / web (http/https) based url of the pdf that will be got and inturn converted to text to an extent" } }, "required": ["url"] From cff87421e1efe88bd2484f0130f0a81462f05640 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 02:01:31 +0530 Subject: [PATCH 200/266] SimpleChatTC:Pdf2Text: Refine desc and MaxResultDataLength Needed to tweak the description further for the ai model to be able to understand that its ok to pass file:// scheme based urls Had forgotten how big the web site pages have become as also the need for more ResultDataLength wrt one shot PDF read to get atleast some good enough amount of content in it with large pdfs --- tools/server/public_simplechat/simplechat.js | 2 +- tools/server/public_simplechat/toolweb.mjs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 1af93e2b200fd..c7803737835b4 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1344,7 +1344,7 @@ class Me { * Control the length of the tool call result data returned to ai after tool call. * A value of 0 is treated as unlimited data. */ - iResultMaxDataLength: 2048, + iResultMaxDataLength: 1024*128, /** * 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. diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 518ccd6f90626..34079d04fc0dd 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -278,13 +278,13 @@ let pdf2text_meta = { "type": "function", "function": { "name": "pdf2text", - "description": "Read pdf from requested local file / web url through a proxy server and return its text content after converting pdf to text, in few seconds", + "description": "Read pdf from requested local file path / web url through a proxy server and return its text content after converting pdf to text, in few seconds", "parameters": { "type": "object", "properties": { "url":{ "type":"string", - "description":"local file path / web (http/https) based url of the pdf that will be got and inturn converted to text to an extent" + "description":"local file path (file://) / web (http/https) based url of the pdf that will be got and inturn converted to text to an extent" } }, "required": ["url"] From 6d742270df4924fca1948f917eb60c0e614fb6c4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 02:29:00 +0530 Subject: [PATCH 201/266] SimpleChatTC:Pdf2Text and otherwise readme update Half asleep as usual ;) --- tools/server/public_simplechat/readme.md | 32 ++++++++++++++++++---- tools/server/public_simplechat/toolweb.mjs | 5 +++- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index b1bfc6007cdb7..7b98b832ac68d 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -88,7 +88,8 @@ remember to * use a GenAi/LLM model which supports tool calling. -* if fetch web page or web search tool call is needed remember to run bundled local.tools/simpleproxy.py +* if fetch web page, web search or pdf-to-text 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 @@ -258,6 +259,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. + * iResultMaxDataLength - specify what amount of any tool call result should be sent back to the ai engine server. + + * specifying 0 disables this truncating of the results. + * 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. @@ -369,11 +374,11 @@ Given that browsers provide a implicit env for not only showing ui, but also run 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). +(javascript in this case), data store. -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. +Additionally if users want to work with web content or pdf content as part of their ai chat +session, Few functions related to web access as well as pdf 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 @@ -391,6 +396,8 @@ needed to help generate better responses. this can also be used for * searching for specific topics and summarising the results * or so +* one could also augment additional data / info by accessing text content from pdf files + * 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 @@ -441,6 +448,17 @@ the above set of web related tool calls work by handshaking with a bundled simpl (/caching in future) server logic, this helps bypass the CORS restrictions applied if trying to directly fetch from the browser js runtime environment. +* pdf2text - fetch/read specified pdf file and extract its textual content + + * local file access is enabled for this feature, so be careful as to where and under which user id + the simple proxy will be run. + + * this depends on the pypdf python based open source library + +Implementing some of the tool calls through the simpleproxy.py server and not directly in the browser +js env, allows one to isolate the core of these logic within a discardable VM or so, by running the +simpleproxy.py in such a vm. + 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 plain text content to some extent in a simple minded @@ -464,6 +482,8 @@ The bundled simple proxy so that websites will hopefully respect the request rather than blindly rejecting it as coming from a non-browser entity. +* allows getting specified local or web based pdf files and extract their text content for ai to use + In future it can be further extended to help with other relatively simple yet useful tool calls like fetch_rss and so. @@ -552,6 +572,8 @@ users) own data or data of ai model. Trap http response errors and inform user the specific error returned by ai server. +Initial go at a pdf2text tool call. For now it allows local pdf files to be read and their text content +extracted and passed to ai model for further processing, as decided by ai and end user. #### ToDo diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 34079d04fc0dd..7092790a8eaca 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -1,6 +1,6 @@ //@ts-check // ALERT - Simple Stupid flow - Using from a discardable VM is better -// Helpers to handle tools/functions calling related to web access +// Helpers to handle tools/functions calling related to web access, pdf, etal // which work in sync with the bundled simpleproxy.py server logic. // by Humans for All // @@ -27,6 +27,9 @@ function get_gme() { } +/** + * For now hash the shared secret with the year. + */ function bearer_transform() { let data = `${new Date().getUTCFullYear()}${get_gme().tools.proxyAuthInsecure}` return crypto.subtle.digest('sha-256', new TextEncoder().encode(data)).then(ab=>{ From 827d9cfc85704dbc3e98c4c04c2acc0619baff26 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 02:47:46 +0530 Subject: [PATCH 202/266] SimpleChatTC:SimpleProxyHS: make helper work with any num of args This makes the logic more generic, as well as prepares for additional parameters to be passed to the simpleproxy.py helper handshakes. Ex: Restrict extracted contents of a pdf to specified start and end page numbers or so. --- tools/server/public_simplechat/toolweb.mjs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 7092790a8eaca..a83ce2c38e90d 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -51,12 +51,11 @@ function bearer_transform() { * @param {string} toolname * @param {any} obj * @param {string} path - * @param {string} qkey - * @param {string} qvalue */ -async function proxyserver_get_1arg(chatid, toolcallid, toolname, obj, path, qkey, qvalue) { +async function proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, path) { if (gToolsWorker.onmessage != null) { - let newUrl = `${get_gme().tools.proxyUrl}/${path}?${qkey}=${qvalue}` + let params = new URLSearchParams(obj) + let newUrl = `${get_gme().tools.proxyUrl}/${path}?${params}` let btoken = await bearer_transform() fetch(newUrl, { headers: { 'Authorization': `Bearer ${btoken}` }}).then(resp => { if (!resp.ok) { @@ -132,7 +131,8 @@ let fetchweburlraw_meta = { * @param {any} obj */ function fetchweburlraw_run(chatid, toolcallid, toolname, obj) { - return proxyserver_get_1arg(chatid, toolcallid, toolname, obj, 'urlraw', 'url', encodeURIComponent(obj.url)); + // maybe filter out any key other than 'url' in obj + return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'urlraw'); } @@ -190,7 +190,8 @@ let fetchweburltext_meta = { * @param {any} obj */ function fetchweburltext_run(chatid, toolcallid, toolname, obj) { - return proxyserver_get_1arg(chatid, toolcallid, toolname, obj, 'urltext', 'url', encodeURIComponent(obj.url)); + // maybe filter out any key other than 'url' in obj + return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'urltext'); } @@ -253,7 +254,9 @@ function searchwebtext_run(chatid, toolcallid, toolname, obj) { /** @type {string} */ let searchUrl = get_gme().tools.searchUrl; searchUrl = searchUrl.replace("SEARCHWORDS", encodeURIComponent(obj.words)); - return proxyserver_get_1arg(chatid, toolcallid, toolname, obj, 'urltext', 'url', encodeURIComponent(searchUrl)); + delete(obj.words) + obj['url'] = searchUrl + return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'urltext'); } } @@ -311,7 +314,7 @@ let pdf2text_meta = { * @param {any} obj */ function pdf2text_run(chatid, toolcallid, toolname, obj) { - return proxyserver_get_1arg(chatid, toolcallid, toolname, obj, 'pdf2text', 'url', encodeURIComponent(obj.url)); + return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'pdf2text'); } From bd40771bd8232ce2665271357cdd21bace3c38b4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 03:05:30 +0530 Subject: [PATCH 203/266] SimpleChatTC:TC Result truncating only if needed As I was seeing the truncated message even for stripped plain text web acces, relooking at that initial go at truncating, revealed a oversight, which had the truncation logic trigger anytime the iResultMaxDataLength was greater than 0, irrespective of whether the actual result was smaller than the allowed limit or not, thus adding that truncated message to end of result unnecessarily. Have fixed that oversight Also recent any number of args based simpleprox handshake helper in toolweb seems to be working (atleast for the existing single arg based calls). --- tools/server/public_simplechat/readme.md | 2 +- tools/server/public_simplechat/simplechat.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 7b98b832ac68d..326c9120a3f5b 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -261,7 +261,7 @@ It is attached to the document object. Some of these can also be updated using t * iResultMaxDataLength - specify what amount of any tool call result should be sent back to the ai engine server. - * specifying 0 disables this truncating of the results. + * specifying 0 disables this truncating of the results, and inturn full result will be sent to the ai engine server. * 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 diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index c7803737835b4..317637a59eebe 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1093,7 +1093,9 @@ class MultiChatUI { let chat = this.simpleChats[cid]; let limitedData = data if (gMe.tools.iResultMaxDataLength > 0) { - limitedData = data.slice(0, gMe.tools.iResultMaxDataLength) + `\n\n\nALERT: Data too long, was chopped ....` + if (data.length > gMe.tools.iResultMaxDataLength) { + limitedData = data.slice(0, gMe.tools.iResultMaxDataLength) + `\n\n\nALERT: Data too long, was chopped ....` + } } chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(tcid, name, limitedData))) if (this.chat_show(cid)) { From 0dc5c7c562d007e40fe7e8885c0f36ff51e39a06 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 03:38:43 +0530 Subject: [PATCH 204/266] SimpleChatTC:Pdf2Text: Make it work with a subset of pages Initial go, need to review the code flow as well as test it out --- .../local.tools/simpleproxy.py | 21 ++++++++++++++++--- tools/server/public_simplechat/toolweb.mjs | 12 +++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 99a7004cd2e74..03b9a330eb0c0 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -378,7 +378,7 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.send_error(502, f"WARN:UrlTextFailed:{exc}") -def process_pdf2text(url: str): +def process_pdf2text(url: str, startPN: int, endPN: int): import pypdf import io urlParts = url.split('://',1) @@ -388,7 +388,12 @@ def process_pdf2text(url: str): dPdf = fPdf.read() tPdf = "" oPdf = pypdf.PdfReader(io.BytesIO(dPdf)) - for (pn, pd) in enumerate(oPdf.pages): + if (startPN < 0): + startPN = 0 + if (endPN < 0) or (endPN >= len(oPdf.pages)): + endPN = len(oPdf.pages)-1 + for i in range(startPN, endPN+1): + pd = oPdf.pages[i] tPdf = tPdf + pd.extract_text() return { 'status': 200, 'msg': "Pdf2Text Response follows", 'data': tPdf } @@ -407,8 +412,18 @@ def handle_pdf2text(ph: ProxyHandler, pr: urllib.parse.ParseResult): if (not url) or (len(url) == 0): ph.send_error(400, f"WARN:HandlePdf2Text:MissingUrl!") return + startP = queryParams['startPageNumber'][0] + if startP: + startP = int(startP) + else: + startP = -1 + endP = queryParams['endPageNumber'][0] + if endP: + endP = int(endP) + else: + endP = -1 print(f"INFO:HandlePdf2Text:Processing:{url}...") - gotP2T = process_pdf2text(url) + gotP2T = process_pdf2text(url, startP, endP) if (gotP2T['status'] != 200): ph.send_error(gotP2T['status'], gotP2T['msg'] ) return diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index a83ce2c38e90d..56ddd8ae67af6 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -284,14 +284,22 @@ let pdf2text_meta = { "type": "function", "function": { "name": "pdf2text", - "description": "Read pdf from requested local file path / web url through a proxy server and return its text content after converting pdf to text, in few seconds", + "description": "Read pdf from requested local file path / web url through a proxy server and return its text content after converting pdf to text, in few seconds. One is allowed to get a part of the pdf by specifying the starting and ending page numbers", "parameters": { "type": "object", "properties": { "url":{ "type":"string", "description":"local file path (file://) / web (http/https) based url of the pdf that will be got and inturn converted to text to an extent" - } + }, + "startPageNumber":{ + "type":"integer", + "description":"Specify the starting page number within the pdf, this is optional. If not specified set to first page." + }, + "endPageNumber":{ + "type":"integer", + "description":"Specify the ending page number within the pdf, this is optional. If not specified set to the last page." + }, }, "required": ["url"] } From 0e95ee9bcc5c2b457e87373a089f2a773a83a254 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 04:23:47 +0530 Subject: [PATCH 205/266] SimpleChatTC:Fixup auto toolcall wrt newer ChatShow flow This is a initial go wrt the new overall flow, should work, but need to cross check. --- tools/server/public_simplechat/simplechat.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 317637a59eebe..379851d79ad35 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -867,15 +867,16 @@ class MultiChatUI { /** * Reset/Setup Tool Call UI parts as needed * @param {ChatMessageEx} ar + * @param {boolean} bAuto */ - ui_reset_toolcall_as_needed(ar) { + ui_reset_toolcall_as_needed(ar, bAuto = false) { 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 - if (gMe.tools.auto > 0) { + if ((gMe.tools.auto > 0) && (bAuto)) { this.timers.toolcallTriggerClick = setTimeout(()=>{ this.elBtnTool.click() }, gMe.tools.auto*this.TimePeriods.ToolCallAutoTimeUnit) @@ -976,16 +977,18 @@ class MultiChatUI { } // Handle tool call ui, if reqd let bTC = false + let bAuto = false if (msg.ns.role === Roles.Assistant) { if (iFromLast == 0) { bTC = true + bAuto = true } else if ((iFromLast == 1) && (nextMsg != undefined)) { if (nextMsg.ns.role == Roles.ToolTemp) { bTC = true } } if (bTC) { - this.ui_reset_toolcall_as_needed(msg); + this.ui_reset_toolcall_as_needed(msg, bAuto); } } // Handle tool call non ui From d1a6c19cb5b11613b87b92b21ca7b5558a9bab4a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 13:06:40 +0530 Subject: [PATCH 206/266] SimpleChatTC:Update notes --- tools/server/public_simplechat/simplechat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 379851d79ad35..e8c1fc81d9a1e 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -867,7 +867,7 @@ class MultiChatUI { /** * Reset/Setup Tool Call UI parts as needed * @param {ChatMessageEx} ar - * @param {boolean} bAuto + * @param {boolean} bAuto - allows caller to explicitly control whether auto triggering should be setup. */ ui_reset_toolcall_as_needed(ar, bAuto = false) { if (ar.has_toolcall()) { From 78ff9a7aad9ff69008e32deeff40722b4f96c693 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 13:07:41 +0530 Subject: [PATCH 207/266] SimpleChatTC:SimpleProxy:UrlValidator module initial skeleton Copy validate_url and build initial skeleton --- .../local.tools/urlvalidator.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tools/server/public_simplechat/local.tools/urlvalidator.py diff --git a/tools/server/public_simplechat/local.tools/urlvalidator.py b/tools/server/public_simplechat/local.tools/urlvalidator.py new file mode 100644 index 0000000000000..dec5f11c5af08 --- /dev/null +++ b/tools/server/public_simplechat/local.tools/urlvalidator.py @@ -0,0 +1,45 @@ +# Handle URL validation +# by Humans for All + +import urllib.parse +import re +from dataclasses import dataclass + + +gMe = { +} + + +@dataclass(frozen=True) +class UrlVResponse: + """ + Used to return result wrt urlreq helper below. + """ + callOk: bool + statusCode: int + statusMsg: str = "" + + +def validator_ok(): + pass + + +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 UrlVResponse(False, 400, f"DBUG:{tag}:MissingAllowedDomains") + urlParts = urllib.parse.urlparse(url) + print(f"DBUG:ValidateUrl:{urlParts}, {urlParts.hostname}") + urlHName = urlParts.hostname + if not urlHName: + return UrlVResponse(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 UrlVResponse(False, 400, f"WARN:{tag}:requested hostname not allowed") + return UrlVResponse(True, 200) From da8c1951af7dc77dc6e0665d27e410fb75c0e53e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 13:39:37 +0530 Subject: [PATCH 208/266] SimpleChatTC:SimpleProxy:UrlValidator initial go Check if the specified scheme is allowed or not. If allowed then call corresponding validator to check remaining part of the url is fine or not --- .../local.tools/urlvalidator.py | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/urlvalidator.py b/tools/server/public_simplechat/local.tools/urlvalidator.py index dec5f11c5af08..59f796d430d48 100644 --- a/tools/server/public_simplechat/local.tools/urlvalidator.py +++ b/tools/server/public_simplechat/local.tools/urlvalidator.py @@ -13,26 +13,27 @@ @dataclass(frozen=True) class UrlVResponse: """ - Used to return result wrt urlreq helper below. + Used to return detailed results below. """ callOk: bool statusCode: int statusMsg: str = "" -def validator_ok(): - pass - - -def validate_url(url: str, tag: str): - """ - Implement a re based filter logic on the specified url. - """ - tag=f"VU:{tag}" +def validator_ok(tag: str): if (not gMe.get('--allowed.domains')): return UrlVResponse(False, 400, f"DBUG:{tag}:MissingAllowedDomains") - urlParts = urllib.parse.urlparse(url) - print(f"DBUG:ValidateUrl:{urlParts}, {urlParts.hostname}") + if (not gMe.get('--allowed.schemes')): + return UrlVResponse(False, 400, f"DBUG:{tag}:MissingAllowedSchemes") + return UrlVResponse(True, 100) + + +def validate_fileurl(urlParts: urllib.parse.ParseResult, tag: str): + return UrlVResponse(True, 100) + + +def validate_weburl(urlParts: urllib.parse.ParseResult, tag: str): + # Cross check hostname urlHName = urlParts.hostname if not urlHName: return UrlVResponse(False, 400, f"WARN:{tag}:Missing hostname in Url") @@ -43,3 +44,25 @@ def validate_url(url: str, tag: str): if not bMatched: return UrlVResponse(False, 400, f"WARN:{tag}:requested hostname not allowed") return UrlVResponse(True, 200) + + +def validate_url(url: str, tag: str): + """ + Implement a re based filter logic on the specified url. + """ + tag=f"VU:{tag}" + vok = validator_ok(tag) + if (not vok.callOk): + return vok + urlParts = urllib.parse.urlparse(url) + print(f"DBUG:{tag}:{urlParts}, {urlParts.hostname}") + # Cross check scheme + urlScheme = urlParts.scheme + if not urlScheme: + return UrlVResponse(False, 400, f"WARN:{tag}:Missing scheme in Url") + if not (urlScheme in gMe['--allowed.schemes']): + return UrlVResponse(False, 400, f"WARN:{tag}:requested scheme not allowed") + if urlScheme == 'file': + return validate_fileurl(urlParts, tag) + else: + return validate_weburl(urlParts, tag) From ed042c7cec46fe58d1817905097408b131e523ba Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 14:15:07 +0530 Subject: [PATCH 209/266] SimpleChatTC:SimpleProxy: Use urlvalidator Add --allowed.schemes config entry as a needed config. Setup the url validator. Use this wrt urltext, urlraw and pdf2text This allows user to control whether local file access is enabled or not. By default in the sample simpleproxy.json config file local file access is allowed. --- .../local.tools/simpleproxy.json | 5 ++ .../local.tools/simpleproxy.py | 52 +++++-------------- .../local.tools/urlvalidator.py | 13 +++++ 3 files changed, 31 insertions(+), 39 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json index 1bae207341ec0..72f7f81cf378e 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.json +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -1,4 +1,9 @@ { + "allowed.schemes": [ + "file", + "http", + "https" + ], "allowed.domains": [ ".*\\.wikipedia\\.org$", ".*\\.bing\\.com$", diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 03b9a330eb0c0..3b2247cbbd3c0 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -26,6 +26,7 @@ import html.parser import re import time +import urlvalidator as uv gMe = { @@ -40,11 +41,12 @@ '--port': 'int', '--config': 'str', '--debug': 'bool', + '--allowed.schemes': 'list', '--allowed.domains': 'list', '--bearer.insecure': 'str' } -gConfigNeeded = [ '--allowed.domains', '--bearer.insecure' ] +gConfigNeeded = [ '--allowed.schemes', '--allowed.domains', '--bearer.insecure' ] gAllowedCalls = [ "urltext", "urlraw", "pdf2text" ] @@ -195,27 +197,6 @@ def debug_dump(meta: dict, data: dict): 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. - """ - 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 - 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(ph: ProxyHandler, pr: urllib.parse.ParseResult, tag: str): """ Common part of the url request handling used by both urlraw and urltext. @@ -234,11 +215,9 @@ def handle_urlreq(ph: ProxyHandler, pr: urllib.parse.ParseResult, tag: str): url = queryParams['url'] print(f"DBUG:{tag}:Url:{url}") url = url[0] - if (not url) or (len(url) == 0): - return UrlReqResp(False, 400, f"WARN:{tag}:MissingUrl") - gotVU = validate_url(url, tag) + gotVU = uv.validate_url(url, tag) if not gotVU.callOk: - return gotVU + return UrlReqResp(gotVU.callOk, gotVU.statusCode, gotVU.statusMsg) 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;q=0.9") @@ -381,10 +360,11 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): def process_pdf2text(url: str, startPN: int, endPN: int): import pypdf import io - urlParts = url.split('://',1) - if not (urlParts[0] in gAllowedPdfUrlTypes): - return { 'status': 403, 'msg': f"WARN:HandlePdf2Text:ForbiddedUrlType:{urlParts[0]}:AllowedUrlTypes:{gAllowedPdfUrlTypes}" } - fPdf = open(urlParts[1], 'rb') + gotVU = uv.validate_url(url, "HandlePdf2Text") + if not gotVU.callOk: + return { 'status': gotVU.statusCode, 'msg': gotVU.statusMsg } + urlParts = urllib.parse.urlparse(url) + fPdf = open(urlParts.path, 'rb') dPdf = fPdf.read() tPdf = "" oPdf = pypdf.PdfReader(io.BytesIO(dPdf)) @@ -398,20 +378,13 @@ def process_pdf2text(url: str, startPN: int, endPN: int): return { 'status': 200, 'msg': "Pdf2Text Response follows", 'data': tPdf } -gAllowedPdfUrlTypes = [ "file", "http", "https" ] - def handle_pdf2text(ph: ProxyHandler, pr: urllib.parse.ParseResult): """ Handle requests to pdf2text path, which is used to extract plain text from the specified pdf file. """ queryParams = urllib.parse.parse_qs(pr.query) - url = queryParams['url'] - print(f"DBUG:HandlePdf2Text:Url:{url}") - url = url[0] - if (not url) or (len(url) == 0): - ph.send_error(400, f"WARN:HandlePdf2Text:MissingUrl!") - return + url = queryParams['url'][0] startP = queryParams['startPageNumber'][0] if startP: startP = int(startP) @@ -422,7 +395,7 @@ def handle_pdf2text(ph: ProxyHandler, pr: urllib.parse.ParseResult): endP = int(endP) else: endP = -1 - print(f"INFO:HandlePdf2Text:Processing:{url}...") + print(f"INFO:HandlePdf2Text:Processing:{url}:{startP}:{endP}...") gotP2T = process_pdf2text(url, startP, endP) if (gotP2T['status'] != 200): ph.send_error(gotP2T['status'], gotP2T['msg'] ) @@ -509,6 +482,7 @@ def process_args(args: list[str]): if gMe.get(k) == None: print(f"ERRR:ProcessArgs:{k}:missing, did you forget to pass the config file...") exit(104) + uv.validator_setup(gMe['--allowed.schemes'], gMe['--allowed.domains']) def run(): diff --git a/tools/server/public_simplechat/local.tools/urlvalidator.py b/tools/server/public_simplechat/local.tools/urlvalidator.py index 59f796d430d48..e3fb6b1b32438 100644 --- a/tools/server/public_simplechat/local.tools/urlvalidator.py +++ b/tools/server/public_simplechat/local.tools/urlvalidator.py @@ -10,6 +10,12 @@ } +def validator_setup(allowedSchemes: list[str], allowedDomains: list[str]): + global gMe + gMe['--allowed.schemes'] = allowedSchemes + gMe['--allowed.domains'] = allowedDomains + + @dataclass(frozen=True) class UrlVResponse: """ @@ -21,6 +27,9 @@ class UrlVResponse: def validator_ok(tag: str): + """ + Cross check validator is setup as needed + """ if (not gMe.get('--allowed.domains')): return UrlVResponse(False, 400, f"DBUG:{tag}:MissingAllowedDomains") if (not gMe.get('--allowed.schemes')): @@ -29,6 +38,8 @@ def validator_ok(tag: str): def validate_fileurl(urlParts: urllib.parse.ParseResult, tag: str): + if urlParts.netloc != '': + return UrlVResponse(False, 400, f"WARN:{tag}:Malformed file url") return UrlVResponse(True, 100) @@ -54,6 +65,8 @@ def validate_url(url: str, tag: str): vok = validator_ok(tag) if (not vok.callOk): return vok + if (not url): + return UrlVResponse(False, 400, f"WARN:{tag}:Missing url") urlParts = urllib.parse.urlparse(url) print(f"DBUG:{tag}:{urlParts}, {urlParts.hostname}") # Cross check scheme From a798d1afd812ae3dd611d15c1d7f9aa43fe92e26 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 16:24:36 +0530 Subject: [PATCH 210/266] SimpleChatTC:SimpleProxy: AuthAndRun hlpr for paths that check auth Also trap any exceptions while handling and send exception info to the client requesting service --- .../local.tools/simpleproxy.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 3b2247cbbd3c0..b3baf76459de4 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -24,9 +24,9 @@ import urllib.request from dataclasses import dataclass import html.parser -import re import time import urlvalidator as uv +from typing import Callable gMe = { @@ -109,6 +109,19 @@ def auth_check(self): return { 'AllOk': False, 'Msg': "Invalid auth" } return { 'AllOk': True, 'Msg': "Auth Ok" } + def auth_and_run(self, pr:urllib.parse.ParseResult, handler:Callable[['ProxyHandler', urllib.parse.ParseResult], None]): + """ + If authorisation is ok for the request, run the specified handler. + """ + acGot = self.auth_check() + if not acGot['AllOk']: + self.send_error(400, f"WARN:{acGot['Msg']}") + else: + try: + handler(self, pr) + except Exception as e: + self.send_error(400, f"ERRR:ProxyHandler:{e}") + def do_GET(self): """ Handle GET requests @@ -119,23 +132,11 @@ def do_GET(self): print(f"DBUG:ProxyHandler:GET:{pr}") match pr.path: case '/urlraw': - acGot = self.auth_check() - if not acGot['AllOk']: - self.send_error(400, f"WARN:{acGot['Msg']}") - else: - handle_urlraw(self, pr) + self.auth_and_run(pr, handle_urlraw) case '/urltext': - acGot = self.auth_check() - if not acGot['AllOk']: - self.send_error(400, f"WARN:{acGot['Msg']}") - else: - handle_urltext(self, pr) + self.auth_and_run(pr, handle_urltext) case '/pdf2text': - acGot = self.auth_check() - if not acGot['AllOk']: - self.send_error(400, f"WARN:{acGot['Msg']}") - else: - handle_pdf2text(self, pr) + self.auth_and_run(pr, handle_pdf2text) case '/aum': handle_aum(self, pr) case _: From 46016aaa79e423946206fa53984fb70144e8c6e0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 16:36:41 +0530 Subject: [PATCH 211/266] SimpleChatTC:SimpleProxy:Move pdf logic into its own module --- .../public_simplechat/local.tools/pdfmagic.py | 58 +++++++++++++++++++ .../local.tools/simpleproxy.py | 55 +----------------- 2 files changed, 60 insertions(+), 53 deletions(-) create mode 100644 tools/server/public_simplechat/local.tools/pdfmagic.py diff --git a/tools/server/public_simplechat/local.tools/pdfmagic.py b/tools/server/public_simplechat/local.tools/pdfmagic.py new file mode 100644 index 0000000000000..407674b0f6781 --- /dev/null +++ b/tools/server/public_simplechat/local.tools/pdfmagic.py @@ -0,0 +1,58 @@ +# Helper to manage pdf related requests +# by Humans for All + +import urllib.parse +import urlvalidator as uv +import simpleproxy as root + + +def process_pdf2text(url: str, startPN: int, endPN: int): + import pypdf + import io + gotVU = uv.validate_url(url, "HandlePdf2Text") + if not gotVU.callOk: + return { 'status': gotVU.statusCode, 'msg': gotVU.statusMsg } + urlParts = urllib.parse.urlparse(url) + fPdf = open(urlParts.path, 'rb') + dPdf = fPdf.read() + tPdf = "" + oPdf = pypdf.PdfReader(io.BytesIO(dPdf)) + if (startPN < 0): + startPN = 0 + if (endPN < 0) or (endPN >= len(oPdf.pages)): + endPN = len(oPdf.pages)-1 + for i in range(startPN, endPN+1): + pd = oPdf.pages[i] + tPdf = tPdf + pd.extract_text() + return { 'status': 200, 'msg': "Pdf2Text Response follows", 'data': tPdf } + + +def handle_pdf2text(ph: root.ProxyHandler, pr: urllib.parse.ParseResult): + """ + Handle requests to pdf2text path, which is used to extract plain text + from the specified pdf file. + """ + queryParams = urllib.parse.parse_qs(pr.query) + url = queryParams['url'][0] + startP = queryParams['startPageNumber'][0] + if startP: + startP = int(startP) + else: + startP = -1 + endP = queryParams['endPageNumber'][0] + if endP: + endP = int(endP) + else: + endP = -1 + print(f"INFO:HandlePdf2Text:Processing:{url}:{startP}:{endP}...") + gotP2T = process_pdf2text(url, startP, endP) + if (gotP2T['status'] != 200): + ph.send_error(gotP2T['status'], gotP2T['msg'] ) + return + ph.send_response(gotP2T['status'], gotP2T['msg']) + ph.send_header('Content-Type', 'text/text') + # Add CORS for browser fetch, just in case + ph.send_header('Access-Control-Allow-Origin', '*') + ph.end_headers() + print(f"INFO:HandlePdf2Text:ExtractedText:{url}...") + ph.wfile.write(gotP2T['data'].encode('utf-8')) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index b3baf76459de4..2c289a45ae4fd 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -27,6 +27,7 @@ import time import urlvalidator as uv from typing import Callable +import pdfmagic as mPdf gMe = { @@ -136,7 +137,7 @@ def do_GET(self): case '/urltext': self.auth_and_run(pr, handle_urltext) case '/pdf2text': - self.auth_and_run(pr, handle_pdf2text) + self.auth_and_run(pr, mPdf.handle_pdf2text) case '/aum': handle_aum(self, pr) case _: @@ -358,58 +359,6 @@ def handle_urltext(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.send_error(502, f"WARN:UrlTextFailed:{exc}") -def process_pdf2text(url: str, startPN: int, endPN: int): - import pypdf - import io - gotVU = uv.validate_url(url, "HandlePdf2Text") - if not gotVU.callOk: - return { 'status': gotVU.statusCode, 'msg': gotVU.statusMsg } - urlParts = urllib.parse.urlparse(url) - fPdf = open(urlParts.path, 'rb') - dPdf = fPdf.read() - tPdf = "" - oPdf = pypdf.PdfReader(io.BytesIO(dPdf)) - if (startPN < 0): - startPN = 0 - if (endPN < 0) or (endPN >= len(oPdf.pages)): - endPN = len(oPdf.pages)-1 - for i in range(startPN, endPN+1): - pd = oPdf.pages[i] - tPdf = tPdf + pd.extract_text() - return { 'status': 200, 'msg': "Pdf2Text Response follows", 'data': tPdf } - - -def handle_pdf2text(ph: ProxyHandler, pr: urllib.parse.ParseResult): - """ - Handle requests to pdf2text path, which is used to extract plain text - from the specified pdf file. - """ - queryParams = urllib.parse.parse_qs(pr.query) - url = queryParams['url'][0] - startP = queryParams['startPageNumber'][0] - if startP: - startP = int(startP) - else: - startP = -1 - endP = queryParams['endPageNumber'][0] - if endP: - endP = int(endP) - else: - endP = -1 - print(f"INFO:HandlePdf2Text:Processing:{url}:{startP}:{endP}...") - gotP2T = process_pdf2text(url, startP, endP) - if (gotP2T['status'] != 200): - ph.send_error(gotP2T['status'], gotP2T['msg'] ) - return - ph.send_response(gotP2T['status'], gotP2T['msg']) - ph.send_header('Content-Type', 'text/text') - # Add CORS for browser fetch, just in case - ph.send_header('Access-Control-Allow-Origin', '*') - ph.end_headers() - print(f"INFO:HandlePdf2Text:ExtractedText:{url}...") - ph.wfile.write(gotP2T['data'].encode('utf-8')) - - def load_config(): """ From a7db3c04ebc69eeeed4ee5530cad7aee0b88c0a9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 16:44:40 +0530 Subject: [PATCH 212/266] SimpleChatTC:SimpleProxy: Move web requests to its own module --- .../local.tools/simpleproxy.py | 181 +----------------- .../public_simplechat/local.tools/webmagic.py | 181 ++++++++++++++++++ 2 files changed, 184 insertions(+), 178 deletions(-) create mode 100644 tools/server/public_simplechat/local.tools/webmagic.py diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 2c289a45ae4fd..bd251563492b7 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -21,13 +21,11 @@ import sys import http.server import urllib.parse -import urllib.request -from dataclasses import dataclass -import html.parser import time import urlvalidator as uv from typing import Callable import pdfmagic as mPdf +import webmagic as mWeb gMe = { @@ -133,9 +131,9 @@ def do_GET(self): print(f"DBUG:ProxyHandler:GET:{pr}") match pr.path: case '/urlraw': - self.auth_and_run(pr, handle_urlraw) + self.auth_and_run(pr, mWeb.handle_urlraw) case '/urltext': - self.auth_and_run(pr, handle_urltext) + self.auth_and_run(pr, mWeb.handle_urltext) case '/pdf2text': self.auth_and_run(pr, mPdf.handle_pdf2text) case '/aum': @@ -175,18 +173,6 @@ def handle_aum(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.end_headers() -@dataclass(frozen=True) -class UrlReqResp: - """ - Used to return result wrt urlreq helper below. - """ - callOk: bool - httpStatus: int - httpStatusMsg: str = "" - contentType: str = "" - contentData: str = "" - - def debug_dump(meta: dict, data: dict): if not gMe['--debug']: return @@ -199,167 +185,6 @@ def debug_dump(meta: dict, data: dict): f.write(f"\n\n\n\n{k}:{data[k]}\n\n\n\n") -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. - """ - tag=f"UrlReq:{tag}" - queryParams = urllib.parse.parse_qs(pr.query) - url = queryParams['url'] - print(f"DBUG:{tag}:Url:{url}") - url = url[0] - gotVU = uv.validate_url(url, tag) - if not gotVU.callOk: - return UrlReqResp(gotVU.callOk, gotVU.statusCode, gotVU.statusMsg) - 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;q=0.9") - 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 - 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' - 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}") - - -def handle_urlraw(ph: ProxyHandler, pr: urllib.parse.ParseResult): - try: - # Get requested url - got = handle_urlreq(ph, pr, "HandleUrlRaw") - if not got.callOk: - ph.send_error(got.httpStatus, got.httpStatusMsg) - return - # 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')) - except Exception as exc: - ph.send_error(502, f"WARN:UrlRawFailed:{exc}") - - -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__() - 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): - """ - 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 - - def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]): - if tag in self.monitored: - self.inside[tag] = True - - def handle_endtag(self, tag: str): - if tag in self.monitored: - self.inside[tag] = False - - def handle_data(self, data: str): - if self.do_capture(): - self.text += f"{data}\n" - - def syncup(self): - self.textStripped = self.text - - def strip_adjacent_newlines(self): - oldLen = -99 - newLen = len(self.textStripped) - aStripped = self.textStripped; - while oldLen != newLen: - oldLen = newLen - aStripped = aStripped.replace("\n\n\n","\n") - newLen = len(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): - try: - # Get requested url - got = handle_urlreq(ph, pr, "HandleUrlText") - if not got.callOk: - ph.send_error(got.httpStatus, got.httpStatusMsg) - return - # Extract Text - 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(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}") - - - def load_config(): """ Allow loading of a json based config file diff --git a/tools/server/public_simplechat/local.tools/webmagic.py b/tools/server/public_simplechat/local.tools/webmagic.py new file mode 100644 index 0000000000000..a4f82f5448ec4 --- /dev/null +++ b/tools/server/public_simplechat/local.tools/webmagic.py @@ -0,0 +1,181 @@ +# Helper to manage web related requests +# by Humans for All + +import urllib.parse +import urllib.request +import simpleproxy as root +import urlvalidator as uv +from dataclasses import dataclass +import html.parser + + +@dataclass(frozen=True) +class UrlReqResp: + """ + Used to return result wrt urlreq helper below. + """ + callOk: bool + httpStatus: int + httpStatusMsg: str = "" + contentType: str = "" + contentData: str = "" + + +def handle_urlreq(ph: root.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. + """ + tag=f"UrlReq:{tag}" + queryParams = urllib.parse.parse_qs(pr.query) + url = queryParams['url'] + print(f"DBUG:{tag}:Url:{url}") + url = url[0] + gotVU = uv.validate_url(url, tag) + if not gotVU.callOk: + return UrlReqResp(gotVU.callOk, gotVU.statusCode, gotVU.statusMsg) + 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;q=0.9") + 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 + 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' + root.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}") + + +def handle_urlraw(ph: root.ProxyHandler, pr: urllib.parse.ParseResult): + try: + # Get requested url + got = handle_urlreq(ph, pr, "HandleUrlRaw") + if not got.callOk: + ph.send_error(got.httpStatus, got.httpStatusMsg) + return + # 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')) + except Exception as exc: + ph.send_error(502, f"WARN:UrlRawFailed:{exc}") + + +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__() + 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): + """ + 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 + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]): + if tag in self.monitored: + self.inside[tag] = True + + def handle_endtag(self, tag: str): + if tag in self.monitored: + self.inside[tag] = False + + def handle_data(self, data: str): + if self.do_capture(): + self.text += f"{data}\n" + + def syncup(self): + self.textStripped = self.text + + def strip_adjacent_newlines(self): + oldLen = -99 + newLen = len(self.textStripped) + aStripped = self.textStripped; + while oldLen != newLen: + oldLen = newLen + aStripped = aStripped.replace("\n\n\n","\n") + newLen = len(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: root.ProxyHandler, pr: urllib.parse.ParseResult): + try: + # Get requested url + got = handle_urlreq(ph, pr, "HandleUrlText") + if not got.callOk: + ph.send_error(got.httpStatus, got.httpStatusMsg) + return + # Extract Text + 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(textHtml.get_stripped_text().encode('utf-8')) + root.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}") From f1800c655c450b943c3ef0ab61f05562e64ab32f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 17:24:36 +0530 Subject: [PATCH 213/266] SimpleChatTC:SimpleProxy: Avoid circular deps wrt Type Checking also move debug dump helper to its own module also remember to specify the Class name in quotes, similar to refering to a class within a member of th class wrt python type checking. --- .../public_simplechat/local.tools/pdfmagic.py | 7 +++++-- .../local.tools/simpleproxy.py | 12 ------------ .../public_simplechat/local.tools/webmagic.py | 17 +++++++++++------ tools/server/public_simplechat/readme.md | 5 +++++ 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/pdfmagic.py b/tools/server/public_simplechat/local.tools/pdfmagic.py index 407674b0f6781..29e78e6f0d12d 100644 --- a/tools/server/public_simplechat/local.tools/pdfmagic.py +++ b/tools/server/public_simplechat/local.tools/pdfmagic.py @@ -3,7 +3,10 @@ import urllib.parse import urlvalidator as uv -import simpleproxy as root +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from simpleproxy import ProxyHandler def process_pdf2text(url: str, startPN: int, endPN: int): @@ -27,7 +30,7 @@ def process_pdf2text(url: str, startPN: int, endPN: int): return { 'status': 200, 'msg': "Pdf2Text Response follows", 'data': tPdf } -def handle_pdf2text(ph: root.ProxyHandler, pr: urllib.parse.ParseResult): +def handle_pdf2text(ph: 'ProxyHandler', pr: urllib.parse.ParseResult): """ Handle requests to pdf2text path, which is used to extract plain text from the specified pdf file. diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index bd251563492b7..eda88750d0f9f 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -173,18 +173,6 @@ def handle_aum(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.end_headers() -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 load_config(): """ Allow loading of a json based config file diff --git a/tools/server/public_simplechat/local.tools/webmagic.py b/tools/server/public_simplechat/local.tools/webmagic.py index a4f82f5448ec4..18944b8711a45 100644 --- a/tools/server/public_simplechat/local.tools/webmagic.py +++ b/tools/server/public_simplechat/local.tools/webmagic.py @@ -3,10 +3,15 @@ import urllib.parse import urllib.request -import simpleproxy as root import urlvalidator as uv from dataclasses import dataclass import html.parser +import debug +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from simpleproxy import ProxyHandler + @dataclass(frozen=True) @@ -21,7 +26,7 @@ class UrlReqResp: contentData: str = "" -def handle_urlreq(ph: root.ProxyHandler, 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. @@ -58,13 +63,13 @@ def handle_urlreq(ph: root.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' - root.debug_dump({ 'url': req.full_url, 'headers': req.headers, 'ctype': contentType }, { 'cdata': contentData }) + 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}") -def handle_urlraw(ph: root.ProxyHandler, pr: urllib.parse.ParseResult): +def handle_urlraw(ph: 'ProxyHandler', pr: urllib.parse.ParseResult): try: # Get requested url got = handle_urlreq(ph, pr, "HandleUrlRaw") @@ -159,7 +164,7 @@ def get_stripped_text(self): return self.textStripped -def handle_urltext(ph: root.ProxyHandler, pr: urllib.parse.ParseResult): +def handle_urltext(ph: 'ProxyHandler', pr: urllib.parse.ParseResult): try: # Get requested url got = handle_urlreq(ph, pr, "HandleUrlText") @@ -176,6 +181,6 @@ def handle_urltext(ph: root.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')) - root.debug_dump({ 'RawText': 'yes', 'StrippedText': 'yes' }, { 'RawText': textHtml.text, 'StrippedText': textHtml.get_stripped_text() }) + 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}") diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 326c9120a3f5b..b64a146b2389c 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -575,6 +575,11 @@ Trap http response errors and inform user the specific error returned by ai serv Initial go at a pdf2text tool call. For now it allows local pdf files to be read and their text content extracted and passed to ai model for further processing, as decided by ai and end user. +SimpleProxy +* Convert from a single monolithic file into a collection of modules. +* UrlValidator to cross check scheme and domain of requested urls, + the whitelist inturn picked from config json + #### ToDo Is the tool call promise land trap deep enough, need to think through and explore around this once later. From ad229baa61ed9d92594adde5bad8186323b68ca3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 18:30:11 +0530 Subject: [PATCH 214/266] SimpleChatTC:SimpleProxy:Pdf2Text cleanup page number handling Its not necessary to request a page number range always. Take care of page number starting from 1 and underlying data having 0 as the starting index --- .../public_simplechat/local.tools/pdfmagic.py | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/pdfmagic.py b/tools/server/public_simplechat/local.tools/pdfmagic.py index 29e78e6f0d12d..d89496e36644a 100644 --- a/tools/server/public_simplechat/local.tools/pdfmagic.py +++ b/tools/server/public_simplechat/local.tools/pdfmagic.py @@ -10,6 +10,16 @@ def process_pdf2text(url: str, startPN: int, endPN: int): + """ + Extract textual content from given pdf. + + * Validate the got url. + * Extract textual contents of the pdf from given start page number to end page number (inclusive). + * if -1 | 0 is specified wrt startPN, the actual starting page number (rather 1) will be used. + * if -1 | 0 is specified wrt endPN, the actual ending page number will be used. + + NOTE: Page numbers start from 1, while the underlying list data structure index starts from 0 + """ import pypdf import io gotVU = uv.validate_url(url, "HandlePdf2Text") @@ -20,12 +30,12 @@ def process_pdf2text(url: str, startPN: int, endPN: int): dPdf = fPdf.read() tPdf = "" oPdf = pypdf.PdfReader(io.BytesIO(dPdf)) - if (startPN < 0): - startPN = 0 - if (endPN < 0) or (endPN >= len(oPdf.pages)): - endPN = len(oPdf.pages)-1 + if (startPN <= 0): + startPN = 1 + if (endPN <= 0) or (endPN > len(oPdf.pages)): + endPN = len(oPdf.pages) for i in range(startPN, endPN+1): - pd = oPdf.pages[i] + pd = oPdf.pages[i-1] tPdf = tPdf + pd.extract_text() return { 'status': 200, 'msg': "Pdf2Text Response follows", 'data': tPdf } @@ -37,16 +47,12 @@ def handle_pdf2text(ph: 'ProxyHandler', pr: urllib.parse.ParseResult): """ queryParams = urllib.parse.parse_qs(pr.query) url = queryParams['url'][0] - startP = queryParams['startPageNumber'][0] - if startP: - startP = int(startP) - else: - startP = -1 - endP = queryParams['endPageNumber'][0] - if endP: - endP = int(endP) - else: - endP = -1 + startP = queryParams.get('startPageNumber', -1) + if isinstance(startP, list): + startP = int(startP[0]) + endP = queryParams.get('endPageNumber', -1) + if isinstance(endP, list): + endP = int(endP[0]) print(f"INFO:HandlePdf2Text:Processing:{url}:{startP}:{endP}...") gotP2T = process_pdf2text(url, startP, endP) if (gotP2T['status'] != 200): From 366c1c5603ba452933d50586c0f5c9aef39568a4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 20:13:10 +0530 Subject: [PATCH 215/266] SimpleChatTC:SimpleProxy: getting local / web file module ++ Added logic to help get a file from either the local file system or from the web, based on the url specified. Update pdfmagic module to use the same, so that it can support both local as well as web based pdf. Bring in the debug module, which I had forgotten to commit, after moving debug helper code from simpleproxy.py to the debug module --- .../public_simplechat/local.tools/debug.py | 24 ++++++ .../local.tools/filemagic.py | 79 +++++++++++++++++++ .../public_simplechat/local.tools/pdfmagic.py | 9 ++- .../local.tools/simpleproxy.py | 3 + 4 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 tools/server/public_simplechat/local.tools/debug.py create mode 100644 tools/server/public_simplechat/local.tools/filemagic.py diff --git a/tools/server/public_simplechat/local.tools/debug.py b/tools/server/public_simplechat/local.tools/debug.py new file mode 100644 index 0000000000000..bf42be631c211 --- /dev/null +++ b/tools/server/public_simplechat/local.tools/debug.py @@ -0,0 +1,24 @@ +# Helpers for debugging +# by Humans for All + + +import time + +gMe = { '--debug' : False } + + +def setup(bEnable): + global gMe + gMe['--debug'] = bEnable + + +def 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") diff --git a/tools/server/public_simplechat/local.tools/filemagic.py b/tools/server/public_simplechat/local.tools/filemagic.py new file mode 100644 index 0000000000000..dfadb08095895 --- /dev/null +++ b/tools/server/public_simplechat/local.tools/filemagic.py @@ -0,0 +1,79 @@ +# Handle file related helpers, be it a local file or one on the internet +# by Humans for All + +import urllib.request +import urllib.parse +import debug +from dataclasses import dataclass + + +@dataclass(frozen=True) +class Response: + """ + Used to return result wrt urlreq helper below. + """ + callOk: bool + statusCode: int + statusMsg: str = "" + contentType: str = "" + contentData: bytes = b"" + + + +def get_from_web(url: str, tag: str, inContentType: str, inHeaders: dict[str, str]): + """ + Get the url specified from web. + + If passed header doesnt contain certain useful http header entries, + some predefined defaults will be used in place. + """ + try: + hUA = inHeaders.get('User-Agent', None) + if not hUA: + hUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0' + hAL = inHeaders.get('Accept-Language', None) + if not hAL: + hAL = "en-US,en;q=0.9" + hA = inHeaders.get('Accept', None) + if not hA: + hA = "text/html,*/*" + headers = { + 'User-Agent': hUA, + 'Accept': hA, + 'Accept-Language': hAL + } + 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() + statusCode = response.status or 200 + contentType = response.getheader('Content-Type') or inContentType + debug.dump({ 'url': req.full_url, 'headers': req.headers, 'ctype': contentType }, { 'cdata': contentData }) + return Response(True, statusCode, "", contentType, contentData) + except Exception as exc: + return Response(False, 502, f"WARN:{tag}:Failed:{exc}") + + +def get_from_local(urlParts: urllib.parse.ParseResult, tag: str, inContentType: str): + """ + Get the requested file from the local filesystem + """ + try: + fPdf = open(urlParts.path, 'rb') + dPdf = fPdf.read() + return Response(True, 200, "", inContentType, dPdf) + except Exception as exc: + return Response(False, 502, f"WARN:{tag}:Failed:{exc}") + + +def get_file(url: str, tag: str, inContentType: str, inHeaders: dict[str, str]={}): + """ + Based on the scheme specified in the passed url, + either get from local file system or from the web. + """ + urlParts = urllib.parse.urlparse(url) + if urlParts.scheme == "file": + return get_from_local(urlParts, tag, inContentType) + else: + return get_from_web(url, tag, inContentType, inHeaders) diff --git a/tools/server/public_simplechat/local.tools/pdfmagic.py b/tools/server/public_simplechat/local.tools/pdfmagic.py index d89496e36644a..336a61250a70e 100644 --- a/tools/server/public_simplechat/local.tools/pdfmagic.py +++ b/tools/server/public_simplechat/local.tools/pdfmagic.py @@ -3,6 +3,7 @@ import urllib.parse import urlvalidator as uv +import filemagic as mFile from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -25,11 +26,11 @@ def process_pdf2text(url: str, startPN: int, endPN: int): gotVU = uv.validate_url(url, "HandlePdf2Text") if not gotVU.callOk: return { 'status': gotVU.statusCode, 'msg': gotVU.statusMsg } - urlParts = urllib.parse.urlparse(url) - fPdf = open(urlParts.path, 'rb') - dPdf = fPdf.read() + gotFile = mFile.get_file(url, "ProcessPdf2Text", "application/pdf", {}) + if not gotFile.callOk: + return { 'status': gotFile.statusCode, 'msg': gotFile.statusMsg, 'data': gotFile.contentData} tPdf = "" - oPdf = pypdf.PdfReader(io.BytesIO(dPdf)) + oPdf = pypdf.PdfReader(io.BytesIO(gotFile.contentData)) if (startPN <= 0): startPN = 1 if (endPN <= 0) or (endPN > len(oPdf.pages)): diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index eda88750d0f9f..4a74c6a254d18 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -26,6 +26,7 @@ from typing import Callable import pdfmagic as mPdf import webmagic as mWeb +import debug as mDebug gMe = { @@ -245,9 +246,11 @@ def process_args(args: list[str]): if gMe.get(k) == None: print(f"ERRR:ProcessArgs:{k}:missing, did you forget to pass the config file...") exit(104) + mDebug.setup(gMe['--debug']) uv.validator_setup(gMe['--allowed.schemes'], gMe['--allowed.domains']) + def run(): try: gMe['serverAddr'] = ('', gMe['--port']) From 5b9e8e4af0f20b21e1100ee441fd8d512c714691 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 22:11:07 +0530 Subject: [PATCH 216/266] SimpleChatTC:SimpleProxy:Pdf2Text update /cleanup readme --- tools/server/public_simplechat/readme.md | 45 ++++++++++++++---------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index b64a146b2389c..9a8b586e6e040 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -94,13 +94,17 @@ remember to * cd tools/server/public_simplechat/local.tools; python3 ./simpleproxy.py --config simpleproxy.json - * 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. + * remember that this is a relatively minimal dumb proxy logic which can fetch html or pdf content and + inturn optionally provide plain text version of the content by stripping off non textual/core contents. + Be careful when accessing web through this and use it only with known safe sites. * look into local.tools/simpleproxy.json for specifying + * the white list of allowed.schemes + * you may want to use this to disable local file access and or disable http access, + and inturn retaining only https based urls or so. * the white list of allowed.domains + * review and update this to match your needs. * the shared bearer token between server and client ui * other builtin tool / function calls like calculator, javascript runner, DataStore dont require the @@ -389,15 +393,15 @@ like 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 +* access content (including html, pdf, text based...) from local file system or the 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 + * searching for specific topics and summarising the search results and or fetching and + analysing found data to generate summary or to explore / answer queries around that data ... * or so -* one could also augment additional data / info by accessing text content from pdf files - * 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 @@ -444,16 +448,18 @@ Either way always remember to cross check the tool requests and generated respon * search_web_text - search for the specified words using the configured search engine and return the plain textual content from the search result page. +* pdf2text - fetch/read specified pdf file and extract its textual content + * this depends on the pypdf python based open source library + 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. -* pdf2text - fetch/read specified pdf file and extract its textual content - - * local file access is enabled for this feature, so be careful as to where and under which user id - the simple proxy will be run. +Local file access is also enabled for web fetch and pdf tool calls, if one uses the file:/// scheme +in the url, so be careful as to where and under which user id the simple proxy will be run. - * this depends on the pypdf python based open source library +* one can always disable local file access by removing 'file' from the list of allowed.schemes in +simpleproxy.json config file. Implementing some of the tool calls through the simpleproxy.py server and not directly in the browser js env, allows one to isolate the core of these logic within a discardable VM or so, by running the @@ -463,7 +469,7 @@ Depending on the path specified wrt the proxy server, it executes the correspond 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 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. +also dropping the html tags. Similarly for pdf2text. The client ui logic does a simple check to see if the bundled simpleproxy is running at specified proxyUrl before enabling these web and related tool calls. @@ -475,7 +481,8 @@ The bundled simple proxy * 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. There is also - a provision for shared bearer token to be specified by the end user. + a provision for shared bearer token to be specified by the end user. One could even control what + schemes are supported wrt the urls. * 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 @@ -572,13 +579,15 @@ users) own data or data of ai model. Trap http response errors and inform user the specific error returned by ai server. -Initial go at a pdf2text tool call. For now it allows local pdf files to be read and their text content -extracted and passed to ai model for further processing, as decided by ai and end user. +Initial go at a pdf2text tool call. It allows web / local pdf files to be read and their text content +extracted and passed to ai model for further processing, as decided by ai and end user. One could +either work with the full pdf or a subset of adjacent pages. SimpleProxy * Convert from a single monolithic file into a collection of modules. * UrlValidator to cross check scheme and domain of requested urls, the whitelist inturn picked from config json +* Helpers to fetch file from local file system or the web, transparently #### ToDo @@ -594,8 +603,6 @@ same when saved chat is loaded. MAYBE make the settings in general chat session specific, rather than the current global config flow. -Provide tool to allow for specified pdf files to be converted to equivalent plain text form, so that ai -can be used to work with the content in those PDFs. ### Debuging the handshake and beyond From e974137f0649648114cc1f9f65706b2dce24c825 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 22:33:57 +0530 Subject: [PATCH 217/266] SimpleChatTC:SimpleProxy:Switch web flow to use file helpers This also indirectly adds support for local file system access through the web / fetch (ie urlraw and urltext) service request paths. --- .../public_simplechat/local.tools/filemagic.py | 4 ++-- .../public_simplechat/local.tools/webmagic.py | 17 ++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/filemagic.py b/tools/server/public_simplechat/local.tools/filemagic.py index dfadb08095895..54e067e2582b8 100644 --- a/tools/server/public_simplechat/local.tools/filemagic.py +++ b/tools/server/public_simplechat/local.tools/filemagic.py @@ -20,7 +20,7 @@ class Response: -def get_from_web(url: str, tag: str, inContentType: str, inHeaders: dict[str, str]): +def get_from_web(url: str, tag: str, inContentType: str, inHeaders: dict[str, str|None]): """ Get the url specified from web. @@ -67,7 +67,7 @@ def get_from_local(urlParts: urllib.parse.ParseResult, tag: str, inContentType: return Response(False, 502, f"WARN:{tag}:Failed:{exc}") -def get_file(url: str, tag: str, inContentType: str, inHeaders: dict[str, str]={}): +def get_file(url: str, tag: str, inContentType: str, inHeaders: dict[str, str|None]={}): """ Based on the scheme specified in the passed url, either get from local file system or from the web. diff --git a/tools/server/public_simplechat/local.tools/webmagic.py b/tools/server/public_simplechat/local.tools/webmagic.py index 18944b8711a45..9d910a02a53c2 100644 --- a/tools/server/public_simplechat/local.tools/webmagic.py +++ b/tools/server/public_simplechat/local.tools/webmagic.py @@ -7,6 +7,7 @@ from dataclasses import dataclass import html.parser import debug +import filemagic as mFile from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -48,23 +49,17 @@ def handle_urlreq(ph: 'ProxyHandler', pr: urllib.parse.ParseResult, tag: str): if not gotVU.callOk: return UrlReqResp(gotVU.callOk, gotVU.statusCode, gotVU.statusMsg) 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;q=0.9") - hA = ph.headers.get('Accept', "text/html,*/*") + hUA = ph.headers.get('User-Agent', None) + hAL = ph.headers.get('Accept-Language', None) + hA = ph.headers.get('Accept', None) headers = { 'User-Agent': hUA, 'Accept': hA, 'Accept-Language': hAL } - 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' - debug.dump({ 'url': req.full_url, 'headers': req.headers, 'ctype': contentType }, { 'cdata': contentData }) - return UrlReqResp(True, statusCode, "", contentType, contentData) + gotFile = mFile.get_file(url, tag, "text/html", headers) + return UrlReqResp(gotFile.callOk, gotFile.statusCode, gotFile.statusMsg, gotFile.contentType, gotFile.contentData.decode('utf-8')) except Exception as exc: return UrlReqResp(False, 502, f"WARN:{tag}:Failed:{exc}") From ab5e9dd2d6909c31b4b3df3ea341e0eb34c695fe Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 01:11:35 +0530 Subject: [PATCH 218/266] SimpleChatTC:SimpleProxy:Add generic arxiv.org entry to allowed --- tools/server/public_simplechat/local.tools/simpleproxy.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.json b/tools/server/public_simplechat/local.tools/simpleproxy.json index 72f7f81cf378e..77261bd1a238c 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.json +++ b/tools/server/public_simplechat/local.tools/simpleproxy.json @@ -17,6 +17,7 @@ "^duckduckgo\\.com$", ".*\\.google\\.com$", "^google\\.com$", + ".*\\.arxiv\\.org$", "^arxiv\\.org$", ".*\\.nature\\.com$", ".*\\.science\\.org$", From ebe892645cec505490931e182bff05173ee44ad1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 01:45:38 +0530 Subject: [PATCH 219/266] SimpleChatTC: Cleanup - remove older now unused show chat logic --- tools/server/public_simplechat/simplechat.js | 41 -------------------- 1 file changed, 41 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index e8c1fc81d9a1e..1bbb0890f5068 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -477,47 +477,6 @@ class SimpleChat { 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 - */ - showTOREMOVE(div, elInUser, bClear=true, bShowInfoAll=false) { - if (bClear) { - div.replaceChildren(); - } - let last = undefined; - 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); - } else { - if (bClear) { - div.innerHTML = gUsageMsg; - gMe.setup_load(div, this); - gMe.show_info(div, bShowInfoAll); - } - } - return last; - } - /** * Setup the fetch headers. * It picks the headers from gMe.headers. From d2544d17ecc0de67c47074e377c8e2500d3f9201 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 03:24:07 +0530 Subject: [PATCH 220/266] SimpleChatTC:Cleanup Usage Note and its presentation a bit Make it a details block and update the content a bit --- tools/server/public_simplechat/simplechat.css | 4 ++++ tools/server/public_simplechat/simplechat.js | 11 +++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.css b/tools/server/public_simplechat/simplechat.css index 1f913fa272d49..b0edd777daee6 100644 --- a/tools/server/public_simplechat/simplechat.css +++ b/tools/server/public_simplechat/simplechat.css @@ -28,6 +28,10 @@ background-color: lightpink; } +#UsageNote { + margin: 0.0vmin; +} + .chat-message { border-style: solid; border-color: grey; diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 1bbb0890f5068..d578001e826e4 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -278,16 +278,18 @@ class ChatMessageEx { let gUsageMsg = ` -

      Usage

      +
      + Usage Note
        -
      • System prompt above, to try control ai response characteristics.
      • +
      • System prompt above, helps control ai response characteristics.
        • Completion mode - no system prompt normally.
      • Use shift+enter for inserting enter/newline.
      • -
      • Enter your query to ai assistant in textarea provided below.
      • -
      • If ai assistant requests a tool call, varify same before triggering it.
      • +
      • Enter your query/response to ai assistant in textarea provided below.
      • +
      • settings-tools-enable should be true to enable tool calling.
        • +
        • If ai assistant requests a tool call, verify same before triggering.
        • submit tool response placed into user query textarea
      • Default ContextWindow = [System, Last9 Query+Resp, Cur Query].
      • @@ -295,6 +297,7 @@ let gUsageMsg = `
      • ChatHistInCtxt, MaxTokens, ModelCtxt window to expand
    + `; From 58d631df1460c31b008c074c61a60c97ecdc72ee Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 12:37:59 +0530 Subject: [PATCH 221/266] SimpleChatTC:Cleanup:UsageNote, Initial SettingsInfo shown Usage Note * Cleanup / fix some wording. * Pick chat history handshaked len from config Ensure the settings info is uptodate wrt available tool names by chaining a reshowing with tools manager initialisation. --- tools/server/public_simplechat/simplechat.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index d578001e826e4..297f76366b2c9 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -277,7 +277,8 @@ class ChatMessageEx { } -let gUsageMsg = ` +function usage_note() { + let sUsageNote = `
    Usage Note
      @@ -286,19 +287,20 @@ let gUsageMsg = `
    • Completion mode - no system prompt normally.
  • Use shift+enter for inserting enter/newline.
  • -
  • Enter your query/response to ai assistant in textarea provided below.
  • -
  • settings-tools-enable should be true to enable tool calling.
  • +
  • Enter your query/response to ai assistant in text area provided below.
  • +
  • settings-tools-enabled should be true to enable tool calling.
    • If ai assistant requests a tool call, verify same before triggering.
    • -
    • submit tool response placed into user query textarea
    • +
    • submit tool response placed into user query/response text area
    -
  • Default ContextWindow = [System, Last9 Query+Resp, Cur Query].
  • +
  • ContextWindow = [System, Last[${gMe.chatProps.iRecentUserMsgCnt-1}] User Query/Resp, Cur Query].
    • ChatHistInCtxt, MaxTokens, ModelCtxt window to expand
    -
    -`; + `; + return sUsageNote; +} /** @typedef {ChatMessageEx[]} ChatMessages */ @@ -1001,7 +1003,7 @@ class MultiChatUI { /** @type{HTMLElement} */(this.elLastChatMessage).scrollIntoView(false); // Stupid ts-check js-doc intersection ??? } else { if (bClear) { - this.elDivChat.innerHTML = gUsageMsg; + this.elDivChat.innerHTML = usage_note(); gMe.setup_load(this.elDivChat, chat); gMe.show_info(this.elDivChat, bShowInfoAll); } @@ -1457,7 +1459,7 @@ function startme() { document["du"] = du; // @ts-ignore document["tools"] = tools; - tools.init().then((toolNames)=>gMe.tools.toolNames=toolNames) + tools.init().then((toolNames)=>gMe.tools.toolNames=toolNames).then(()=>gMe.multiChat.chat_show(gMe.multiChat.curChatId)) for (let cid of gMe.defaultChatIds) { gMe.multiChat.new_chat_session(cid); } From 45eac4093209b4b6ec9239b830a3d697986c81ed Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 12:12:41 +0530 Subject: [PATCH 222/266] SimpleChatTC:PdfText:Cleanup rename to follow a common convention Rename path and tags/identifiers from Pdf2Text to PdfText Rename the function call to pdf_to_text, this should also help indicate semantic more unambiguously, just in case, especially for smaller models. --- .../public_simplechat/local.tools/pdfmagic.py | 19 ++++++++------- .../local.tools/simpleproxy.py | 6 ++--- tools/server/public_simplechat/readme.md | 6 ++--- tools/server/public_simplechat/toolweb.mjs | 24 +++++++++---------- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/pdfmagic.py b/tools/server/public_simplechat/local.tools/pdfmagic.py index 336a61250a70e..971ba2c796290 100644 --- a/tools/server/public_simplechat/local.tools/pdfmagic.py +++ b/tools/server/public_simplechat/local.tools/pdfmagic.py @@ -10,11 +10,12 @@ from simpleproxy import ProxyHandler -def process_pdf2text(url: str, startPN: int, endPN: int): +def process_pdftext(url: str, startPN: int, endPN: int): """ Extract textual content from given pdf. * Validate the got url. + * Get the pdf file. * Extract textual contents of the pdf from given start page number to end page number (inclusive). * if -1 | 0 is specified wrt startPN, the actual starting page number (rather 1) will be used. * if -1 | 0 is specified wrt endPN, the actual ending page number will be used. @@ -23,10 +24,10 @@ def process_pdf2text(url: str, startPN: int, endPN: int): """ import pypdf import io - gotVU = uv.validate_url(url, "HandlePdf2Text") + gotVU = uv.validate_url(url, "HandlePdfText") if not gotVU.callOk: return { 'status': gotVU.statusCode, 'msg': gotVU.statusMsg } - gotFile = mFile.get_file(url, "ProcessPdf2Text", "application/pdf", {}) + gotFile = mFile.get_file(url, "ProcessPdfText", "application/pdf", {}) if not gotFile.callOk: return { 'status': gotFile.statusCode, 'msg': gotFile.statusMsg, 'data': gotFile.contentData} tPdf = "" @@ -38,12 +39,12 @@ def process_pdf2text(url: str, startPN: int, endPN: int): for i in range(startPN, endPN+1): pd = oPdf.pages[i-1] tPdf = tPdf + pd.extract_text() - return { 'status': 200, 'msg': "Pdf2Text Response follows", 'data': tPdf } + return { 'status': 200, 'msg': "PdfText Response follows", 'data': tPdf } -def handle_pdf2text(ph: 'ProxyHandler', pr: urllib.parse.ParseResult): +def handle_pdftext(ph: 'ProxyHandler', pr: urllib.parse.ParseResult): """ - Handle requests to pdf2text path, which is used to extract plain text + Handle requests to pdftext path, which is used to extract plain text from the specified pdf file. """ queryParams = urllib.parse.parse_qs(pr.query) @@ -54,8 +55,8 @@ def handle_pdf2text(ph: 'ProxyHandler', pr: urllib.parse.ParseResult): endP = queryParams.get('endPageNumber', -1) if isinstance(endP, list): endP = int(endP[0]) - print(f"INFO:HandlePdf2Text:Processing:{url}:{startP}:{endP}...") - gotP2T = process_pdf2text(url, startP, endP) + print(f"INFO:HandlePdfText:Processing:{url}:{startP}:{endP}...") + gotP2T = process_pdftext(url, startP, endP) if (gotP2T['status'] != 200): ph.send_error(gotP2T['status'], gotP2T['msg'] ) return @@ -64,5 +65,5 @@ def handle_pdf2text(ph: 'ProxyHandler', pr: urllib.parse.ParseResult): # Add CORS for browser fetch, just in case ph.send_header('Access-Control-Allow-Origin', '*') ph.end_headers() - print(f"INFO:HandlePdf2Text:ExtractedText:{url}...") + print(f"INFO:HandlePdfText:ExtractedText:{url}...") ph.wfile.write(gotP2T['data'].encode('utf-8')) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 4a74c6a254d18..862951f56ab80 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -48,7 +48,7 @@ gConfigNeeded = [ '--allowed.schemes', '--allowed.domains', '--bearer.insecure' ] -gAllowedCalls = [ "urltext", "urlraw", "pdf2text" ] +gAllowedCalls = [ "urltext", "urlraw", "pdftext" ] def bearer_transform(): @@ -135,8 +135,8 @@ def do_GET(self): self.auth_and_run(pr, mWeb.handle_urlraw) case '/urltext': self.auth_and_run(pr, mWeb.handle_urltext) - case '/pdf2text': - self.auth_and_run(pr, mPdf.handle_pdf2text) + case '/pdftext': + self.auth_and_run(pr, mPdf.handle_pdftext) case '/aum': handle_aum(self, pr) case _: diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 9a8b586e6e040..e3a835df0da11 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -448,7 +448,7 @@ Either way always remember to cross check the tool requests and generated respon * search_web_text - search for the specified words using the configured search engine and return the plain textual content from the search result page. -* pdf2text - fetch/read specified pdf file and extract its textual content +* pdf_to_text - fetch/read specified pdf file and extract its textual content * this depends on the pypdf python based open source library the above set of web related tool calls work by handshaking with a bundled simple local web proxy @@ -469,7 +469,7 @@ Depending on the path specified wrt the proxy server, it executes the correspond 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 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 -also dropping the html tags. Similarly for pdf2text. +also dropping the html tags. Similarly for pdftext. The client ui logic does a simple check to see if the bundled simpleproxy is running at specified proxyUrl before enabling these web and related tool calls. @@ -579,7 +579,7 @@ users) own data or data of ai model. Trap http response errors and inform user the specific error returned by ai server. -Initial go at a pdf2text tool call. It allows web / local pdf files to be read and their text content +Initial go at a pdftext tool call. It allows web / local pdf files to be read and their text content extracted and passed to ai model for further processing, as decided by ai and end user. One could either work with the full pdf or a subset of adjacent pages. diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 56ddd8ae67af6..ba9ad93bfbe14 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -276,14 +276,14 @@ async function searchwebtext_setup(tcs) { // -// Pdf2Text +// PdfText // -let pdf2text_meta = { +let pdftext_meta = { "type": "function", "function": { - "name": "pdf2text", + "name": "pdf_to_text", "description": "Read pdf from requested local file path / web url through a proxy server and return its text content after converting pdf to text, in few seconds. One is allowed to get a part of the pdf by specifying the starting and ending page numbers", "parameters": { "type": "object", @@ -312,7 +312,7 @@ let pdf2text_meta = { * Expects a simple minded proxy server to be running locally * * listening on a configured port * * expecting http requests - * * with a query token named url wrt pdf2text path, + * * with a query token named url wrt pdftext path, * which gives the actual url to fetch * * gets the requested pdf and converts to text, before returning same. * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful @@ -321,20 +321,20 @@ let pdf2text_meta = { * @param {string} toolname * @param {any} obj */ -function pdf2text_run(chatid, toolcallid, toolname, obj) { - return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'pdf2text'); +function pdftext_run(chatid, toolcallid, toolname, obj) { + return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'pdftext'); } /** - * Setup pdf2text for tool calling + * Setup pdftext for tool calling * NOTE: Currently the logic is setup for the bundled simpleproxy.py * @param {Object>} tcs */ -async function pdf2text_setup(tcs) { - return proxyserver_tc_setup('Pdf2Text', 'pdf2text', 'pdf2text', { - "handler": pdf2text_run, - "meta": pdf2text_meta, +async function pdftext_setup(tcs) { + return proxyserver_tc_setup('PdfText', 'pdftext', 'pdf_to_text', { + "handler": pdftext_run, + "meta": pdftext_meta, "result": "" }, tcs); } @@ -355,6 +355,6 @@ export async function init(toolsWorker) { await fetchweburlraw_setup(tc_switch) await fetchweburltext_setup(tc_switch) await searchwebtext_setup(tc_switch) - await pdf2text_setup(tc_switch) + await pdftext_setup(tc_switch) return tc_switch } From 108290dd958006efeaa58359a73437a3ec18777b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 13:02:56 +0530 Subject: [PATCH 223/266] SimpleChatTC: ToolCalling enabled, Sliding window adjust Chances are for ai models which dont support tool calling, things will be such that the tool calls meta data shared will be silently ignored without much issue. So enabling tool calling feature by default, so that in case one is using a ai model with tool calling the feature is readily available for use. Revert SlidingWindow ChatHistory in Context from last 10 to last 5 (2 more then origianl, given more context support in todays models) by default, given that now tool handshakes go through the tools related side channel in the http handshake and arent morphed into normal user-assistant channel of the handshake. --- tools/server/public_simplechat/readme.md | 10 ++++++++++ tools/server/public_simplechat/simplechat.js | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index e3a835df0da11..6a237918ee763 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -589,6 +589,16 @@ SimpleProxy the whitelist inturn picked from config json * Helpers to fetch file from local file system or the web, transparently +Chances are for ai models which dont support tool calling, things will be such that the tool calls +meta data shared will be silently ignored without much issue. So enabling tool calling feature by default, +so that in case one is using a ai model with tool calling the feature is readily available for use. + +Revert SlidingWindow ChatHistory in Context from last 10 to last 5 (rather 2 more then origianl, +given more context support in todays models) by default, given that now tool handshakes go through +the tools related side channel in the http handshake and arent morphed into normal user-assistant +channel of the handshake. + + #### ToDo Is the tool call promise land trap deep enough, need to think through and explore around this once later. diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 297f76366b2c9..b145938af628a 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1304,7 +1304,7 @@ class Me { this.defaultChatIds = [ "Default", "Other" ]; this.multiChat = new MultiChatUI(); this.tools = { - enabled: false, + enabled: true, proxyUrl: "http://127.0.0.1:3128", proxyAuthInsecure: "NeverSecure", searchUrl: SearchURLS.duckduckgo, @@ -1328,7 +1328,7 @@ class Me { this.chatProps = { apiEP: ApiEP.Type.Chat, stream: true, - iRecentUserMsgCnt: 10, + iRecentUserMsgCnt: 5, bCompletionFreshChatAlways: true, bCompletionInsertStandardRolePrefix: false, bTrimGarbage: true, From 64a0f7b6fb69f729acaf5d8201d572d41a7a1b4b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 14:35:23 +0530 Subject: [PATCH 224/266] SimpleChatTC:SimpleProxy: Validate deps wrt enabled service paths helps ensure only service paths that can be serviced are enabled Use same to check for pypdf wrt pdftext --- .../local.tools/simpleproxy.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 862951f56ab80..50a5691703081 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -48,7 +48,11 @@ gConfigNeeded = [ '--allowed.schemes', '--allowed.domains', '--bearer.insecure' ] -gAllowedCalls = [ "urltext", "urlraw", "pdftext" ] +gAllowedCalls = { + "urltext": [], + "urlraw": [], + "pdftext": [ "pypdf" ] + } def bearer_transform(): @@ -157,6 +161,7 @@ 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 """ + import importlib queryParams = urllib.parse.parse_qs(pr.query) url = queryParams['url'] print(f"DBUG:HandleAUM:Url:{url}") @@ -165,9 +170,15 @@ def handle_aum(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.send_error(400, f"WARN:HandleAUM:MissingUrl/UnknownQuery?!") return urlParts = url.split('.',1) - if not (urlParts[0] in gAllowedCalls): - ph.send_error(403, f"WARN:HandleAUM:Forbidded:{urlParts[0]}") + if gAllowedCalls.get(urlParts[0], None) == None: + ph.send_error(403, f"WARN:HandleAUM:Forbidden:{urlParts[0]}") return + for dep in gAllowedCalls[urlParts[0]]: + try: + importlib.import_module(dep) + except ImportError as exc: + ph.send_error(400, f"WARN:HandleAUM:{urlParts[0]}:Support module [{dep}] missing or has issues") + return print(f"INFO:HandleAUM:Availability ok for:{urlParts[0]}") ph.send_response_only(200, "bharatavarshe") ph.send_header('Access-Control-Allow-Origin', '*') From 8e2c3762fe8f6e5e60191bfd35cd17f693731868 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 18:57:29 +0530 Subject: [PATCH 225/266] SimpleChatTC:SettingsDefault:Enable cache prompt api option --- tools/server/public_simplechat/readme.md | 19 ++++++++++++++----- tools/server/public_simplechat/simplechat.js | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 6a237918ee763..9244a3064506d 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -583,21 +583,30 @@ Initial go at a pdftext tool call. It allows web / local pdf files to be read an extracted and passed to ai model for further processing, as decided by ai and end user. One could either work with the full pdf or a subset of adjacent pages. -SimpleProxy +SimpleProxy updates * Convert from a single monolithic file into a collection of modules. * UrlValidator to cross check scheme and domain of requested urls, the whitelist inturn picked from config json * Helpers to fetch file from local file system or the web, transparently +* Help check for needed modules before a particular service path is acknowledged as available + through /aum service path -Chances are for ai models which dont support tool calling, things will be such that the tool calls -meta data shared will be silently ignored without much issue. So enabling tool calling feature by default, -so that in case one is using a ai model with tool calling the feature is readily available for use. +Settings/Config default changes -Revert SlidingWindow ChatHistory in Context from last 10 to last 5 (rather 2 more then origianl, +* Chances are for ai models which dont support tool calling, things will be such that the tool calls +meta data shared will be silently ignored without much issue. So enabling tool calling feature by +default, so that in case one is using a ai model with tool calling the feature is readily available +for use. + +* Revert SlidingWindow ChatHistory in Context from last 10 to last 5 (rather 2 more then origianl, given more context support in todays models) by default, given that now tool handshakes go through the tools related side channel in the http handshake and arent morphed into normal user-assistant channel of the handshake. +* Enable CachePrompt api option given that tool calling based interactions could involve chat sessions +having ai responses built over multiple steps of tool callings etal. So independent of our client side +sliding window based drop off or even before they kick in, this can help in many cases. + #### ToDo diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index b145938af628a..9fecef808db92 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1356,7 +1356,7 @@ class Me { "temperature": 0.7, "max_tokens": 2048, "n_predict": 2048, - "cache_prompt": false, + "cache_prompt": true, //"frequency_penalty": 1.2, //"presence_penalty": 1.2, }; From fe2443bedae1595414c896d5d3ad5b78ca5f581f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 20:02:56 +0530 Subject: [PATCH 226/266] SimpleChatTC:WebTools And Search - headers and search drops - js Allow the web tools handshake helper to pass additional header entries provided by its caller. Make use of this to send a list of tag and id pairs wrt web search tool. Which will be used to drop div's matching the specified id. --- tools/server/public_simplechat/simplechat.js | 20 +++++++++++++++----- tools/server/public_simplechat/toolweb.mjs | 14 +++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 9fecef808db92..26e33904ffd48 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1290,10 +1290,19 @@ class MultiChatUI { * 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 + duckduckgo: { + 'template': "https://duckduckgo.com/html/?q=SEARCHWORDS", + 'drop': [ { 'tag': 'div', 'id': "header" } ] + }, + bing: { + 'template': "https://www.bing.com/search?q=SEARCHWORDS", // doesnt seem to like google chrome clients in particular + }, + brave: { + 'template': "https://search.brave.com/search?q=SEARCHWORDS", + }, + google: { + 'template': "https://www.google.com/search?q=SEARCHWORDS", // doesnt seem to like any client in general + }, } @@ -1307,7 +1316,8 @@ class Me { enabled: true, proxyUrl: "http://127.0.0.1:3128", proxyAuthInsecure: "NeverSecure", - searchUrl: SearchURLS.duckduckgo, + searchUrl: SearchURLS.duckduckgo.template, + searchDrops: SearchURLS.duckduckgo.drop, toolNames: /** @type {Array} */([]), /** * Control the length of the tool call result data returned to ai after tool call. diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index ba9ad93bfbe14..e473e250dd06f 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -49,15 +49,18 @@ function bearer_transform() { * @param {string} chatid * @param {string} toolcallid * @param {string} toolname - * @param {any} obj + * @param {any} objSearchParams * @param {string} path + * @param {any} objHeaders */ -async function proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, path) { +async function proxyserver_get_anyargs(chatid, toolcallid, toolname, objSearchParams, path, objHeaders={}) { if (gToolsWorker.onmessage != null) { - let params = new URLSearchParams(obj) + let params = new URLSearchParams(objSearchParams) let newUrl = `${get_gme().tools.proxyUrl}/${path}?${params}` + let headers = new Headers(objHeaders) let btoken = await bearer_transform() - fetch(newUrl, { headers: { 'Authorization': `Bearer ${btoken}` }}).then(resp => { + headers.append('Authorization', `Bearer ${btoken}`) + fetch(newUrl, { headers: headers}).then(resp => { if (!resp.ok) { throw new Error(`${resp.status}:${resp.statusText}`); } @@ -256,7 +259,8 @@ function searchwebtext_run(chatid, toolcallid, toolname, obj) { searchUrl = searchUrl.replace("SEARCHWORDS", encodeURIComponent(obj.words)); delete(obj.words) obj['url'] = searchUrl - return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'urltext'); + let headers = { 'Search-Drops': get_gme().tools.searchDrops } + return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'urltext', headers); } } From 34fe99eba02987fd4f6af086a1b390f7b8c8f33f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 20:59:18 +0530 Subject: [PATCH 227/266] SimpleChatTC:WebTools: urltext-tag-drops python side - skel Rename search-drops to urltext-tag-drops, to indicate its more generic semantic. Rather search drops specified in UI by user will be mapped to urltext-tag-drops header entry of a urltext web fetch request. Implement a crude urltext-tag-drops logic in TextHtmlParser. If there is any mismatch with opening and closing tags in the html being parsed and inturn wrt the type of tag being targetted for dropping, things can mess up. --- .../public_simplechat/local.tools/webmagic.py | 30 ++++++++++++++++--- tools/server/public_simplechat/toolweb.mjs | 2 +- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/webmagic.py b/tools/server/public_simplechat/local.tools/webmagic.py index 9d910a02a53c2..d371fa736ef34 100644 --- a/tools/server/public_simplechat/local.tools/webmagic.py +++ b/tools/server/public_simplechat/local.tools/webmagic.py @@ -8,6 +8,7 @@ import html.parser import debug import filemagic as mFile +import json from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -95,36 +96,52 @@ class TextHtmlParser(html.parser.HTMLParser): This helps return a relatively clean textual representation of the html file/content being parsed. """ - def __init__(self): + def __init__(self, tagDrops: dict): super().__init__() + self.tagDrops = tagDrops self.inside = { 'body': False, 'script': False, 'style': False, 'header': False, 'footer': False, - 'nav': False + 'nav': False, } self.monitored = [ 'body', 'script', 'style', 'header', 'footer', 'nav' ] self.bCapture = False self.text = "" self.textStripped = "" + self.droptagType = None + self.droptagCount = 0 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']): + 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'] or (self.droptagCount > 0)): return True return False def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]): if tag in self.monitored: self.inside[tag] = True + for tagMeta in self.tagDrops: + if tag != tagMeta.tag: + continue + for attr in attrs: + if attr[0] != 'id': + continue + if attr[1] == tagMeta.id: + self.droptagCount += 1 + self.droptagType = tag def handle_endtag(self, tag: str): if tag in self.monitored: self.inside[tag] = False + if tag == self.droptagType: + self.droptagCount -= 1 + if self.droptagCount < 0: + self.droptagCount = 0 def handle_data(self, data: str): if self.do_capture(): @@ -167,7 +184,12 @@ def handle_urltext(ph: 'ProxyHandler', pr: urllib.parse.ParseResult): ph.send_error(got.httpStatus, got.httpStatusMsg) return # Extract Text - textHtml = TextHtmlParser() + tagDrops = ph.headers.get('urltext-tag-drops') + if not tagDrops: + tagDrops = {} + else: + tagDrops = json.loads(tagDrops) + textHtml = TextHtmlParser(tagDrops) textHtml.feed(got.contentData) # Send back to client ph.send_response(got.httpStatus) diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index e473e250dd06f..f52aca4357038 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -259,7 +259,7 @@ function searchwebtext_run(chatid, toolcallid, toolname, obj) { searchUrl = searchUrl.replace("SEARCHWORDS", encodeURIComponent(obj.words)); delete(obj.words) obj['url'] = searchUrl - let headers = { 'Search-Drops': get_gme().tools.searchDrops } + let headers = { 'urltext-tag-drops': get_gme().tools.searchDrops } return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'urltext', headers); } } From babfb961db45c2598083b1849479735ffd5161b5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 21:42:48 +0530 Subject: [PATCH 228/266] SimpleChatTC:WebTools:UrlText:HtmlParser: tag drops - refine Update the initial skeleton wrt the tag drops logic * had forgotten to convert object to json string at the client end * had confused between js and python and tried accessing the dict elements using . notation rather than [] notation in python. * if the id filtered tag to be dropped is found, from then on track all other tags of the same type (independent of id), so that start and end tags can be matched. bcas end tag call wont have attribute, so all other tags of same type need to be tracked, for proper winding and unwinding to try find matching end tag * remember to reset the tracked drop tag type to None once matching end tag at same depth is found. should avoid some unnecessary unwinding. * set/fix the type wrt tagDrops explicitly to needed depth and ensure the dummy one and any explicitly got one is of right type. Tested with duckduckgo search engine and now the div based unneeded header is avoided in returned search result. --- .../public_simplechat/local.tools/webmagic.py | 30 ++++++++++++++----- tools/server/public_simplechat/readme.md | 7 +++++ tools/server/public_simplechat/toolweb.mjs | 2 +- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/webmagic.py b/tools/server/public_simplechat/local.tools/webmagic.py index d371fa736ef34..2e3d5811cc2af 100644 --- a/tools/server/public_simplechat/local.tools/webmagic.py +++ b/tools/server/public_simplechat/local.tools/webmagic.py @@ -9,7 +9,7 @@ import debug import filemagic as mFile import json -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, cast if TYPE_CHECKING: from simpleproxy import ProxyHandler @@ -93,12 +93,21 @@ class TextHtmlParser(html.parser.HTMLParser): html content, that logic wont be triggered, so also such client side dynamic content wont be got. + Supports one to specify a list of tags and their corresponding id attributes, so that contents + within such specified blocks will be dropped. + + * this works properly only if the html being processed has proper opening and ending tags + around the area of interest. + * remember to specify non overlapping tag blocks, if more than one specified for dropping. + * this path not tested, but should logically work + This helps return a relatively clean textual representation of the html file/content being parsed. """ - def __init__(self, tagDrops: dict): + def __init__(self, tagDrops: list[dict[str, Any]]): super().__init__() self.tagDrops = tagDrops + print(f"DBUG:TextHtmlParser:{self.tagDrops}") self.inside = { 'body': False, 'script': False, @@ -126,20 +135,27 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]): if tag in self.monitored: self.inside[tag] = True for tagMeta in self.tagDrops: - if tag != tagMeta.tag: + if tag != tagMeta['tag']: + continue + if (self.droptagCount > 0) and (self.droptagType == tag): + self.droptagCount += 1 continue for attr in attrs: if attr[0] != 'id': continue - if attr[1] == tagMeta.id: + if attr[1] == tagMeta['id']: self.droptagCount += 1 self.droptagType = tag + print(f"DBUG:THP:Start:Tag found [{tag}:{attr[1]}]...") def handle_endtag(self, tag: str): if tag in self.monitored: self.inside[tag] = False - if tag == self.droptagType: + if self.droptagType and (tag == self.droptagType): self.droptagCount -= 1 + if self.droptagCount == 0: + self.droptagType = None + print("DBUG:THP:End:Tag found...") if self.droptagCount < 0: self.droptagCount = 0 @@ -186,9 +202,9 @@ def handle_urltext(ph: 'ProxyHandler', pr: urllib.parse.ParseResult): # Extract Text tagDrops = ph.headers.get('urltext-tag-drops') if not tagDrops: - tagDrops = {} + tagDrops = [] else: - tagDrops = json.loads(tagDrops) + tagDrops = cast(list[dict[str,Any]], json.loads(tagDrops)) textHtml = TextHtmlParser(tagDrops) textHtml.feed(got.contentData) # Send back to client diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 9244a3064506d..13c2f5787218f 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -590,6 +590,13 @@ SimpleProxy updates * Helpers to fetch file from local file system or the web, transparently * Help check for needed modules before a particular service path is acknowledged as available through /aum service path +* urltext and related - logic to drop contents of specified tag with a given id + * allow its use for the web search tool flow + * setup wrt default duckduckgo search result urltext plain text cleanup and found working. + * this works properly only if the html being processed has proper opening and ending tags + around the area of interest. + * remember to specify non overlapping tag blocks, if more than one specified for dropping. + * this path not tested, but should logically work Settings/Config default changes diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index f52aca4357038..d4c2788340134 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -259,7 +259,7 @@ function searchwebtext_run(chatid, toolcallid, toolname, obj) { searchUrl = searchUrl.replace("SEARCHWORDS", encodeURIComponent(obj.words)); delete(obj.words) obj['url'] = searchUrl - let headers = { 'urltext-tag-drops': get_gme().tools.searchDrops } + let headers = { 'urltext-tag-drops': JSON.stringify(get_gme().tools.searchDrops) } return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'urltext', headers); } } From 58a59630c692b1413a35027b2592b7b82f2102f9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 23:13:12 +0530 Subject: [PATCH 229/266] SimpleChatTC:Cleanup in general Update readme wrt searchDrops, auto settings ui creation Rename tools-auto to tools-autoSecs, to make it easy to realise that the value represents seconds. --- tools/server/public_simplechat/readme.md | 8 ++++++-- tools/server/public_simplechat/simplechat.js | 12 ++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 13c2f5787218f..a3c7c3ec3a4af 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -263,6 +263,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. + * searchDrops - allows one to drop contents of html tags with specified id from the plain text search result. + + * specify a list of dicts, where each dict should contain a 'tag' entry specifying the tag to filter like div or p or ... and also a 'id' entry which specifies the id of interest. + * iResultMaxDataLength - specify what amount of any tool call result should be sent back to the ai engine server. * specifying 0 disables this truncating of the results, and inturn full result will be sent to the ai engine server. @@ -271,7 +275,7 @@ It is attached to the document object. Some of these can also be updated using t 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. + * autoSecs - 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. @@ -285,7 +289,7 @@ It is attached to the document object. Some of these can also be updated using t 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, boolean, object 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. diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 26e33904ffd48..4e9ed0620154e 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -762,7 +762,7 @@ class MultiChatUI { this.curChatId = ""; this.TimePeriods = { - ToolCallAutoTimeUnit: 1000 + ToolCallAutoSecsTimeUnit: 1000 } this.timers = { @@ -840,10 +840,10 @@ 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) && (bAuto)) { + if ((gMe.tools.autoSecs > 0) && (bAuto)) { this.timers.toolcallTriggerClick = setTimeout(()=>{ this.elBtnTool.click() - }, gMe.tools.auto*this.TimePeriods.ToolCallAutoTimeUnit) + }, gMe.tools.autoSecs*this.TimePeriods.ToolCallAutoSecsTimeUnit) } } else { this.elDivTool.hidden = true @@ -1066,10 +1066,10 @@ class MultiChatUI { } chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(tcid, name, limitedData))) if (this.chat_show(cid)) { - if (gMe.tools.auto > 0) { + if (gMe.tools.autoSecs > 0) { this.timers.toolcallResponseSubmitClick = setTimeout(()=>{ this.elBtnUser.click() - }, gMe.tools.auto*this.TimePeriods.ToolCallAutoTimeUnit) + }, gMe.tools.autoSecs*this.TimePeriods.ToolCallAutoSecsTimeUnit) } } this.ui_reset_userinput(false) @@ -1333,7 +1333,7 @@ class Me { * 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 + autoSecs: 0 }; this.chatProps = { apiEP: ApiEP.Type.Chat, From 9b9b9bef498b7a7ea883fef118898394a6e1078e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 4 Nov 2025 22:25:39 +0530 Subject: [PATCH 230/266] SimpleChatTC:Cleanup: General T2 Pretty print SimpleProxy gMe config Dont ignore the got http response status text. Update readme wrt why autoSecs --- tools/server/public_simplechat/local.tools/filemagic.py | 4 +++- tools/server/public_simplechat/local.tools/simpleproxy.py | 3 ++- tools/server/public_simplechat/local.tools/webmagic.py | 1 - tools/server/public_simplechat/readme.md | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/server/public_simplechat/local.tools/filemagic.py b/tools/server/public_simplechat/local.tools/filemagic.py index 54e067e2582b8..8ba6695a0f0c3 100644 --- a/tools/server/public_simplechat/local.tools/filemagic.py +++ b/tools/server/public_simplechat/local.tools/filemagic.py @@ -48,9 +48,11 @@ def get_from_web(url: str, tag: str, inContentType: str, inHeaders: dict[str, st with urllib.request.urlopen(req, timeout=10) as response: contentData = response.read() statusCode = response.status or 200 + statusMsg = response.msg or "" contentType = response.getheader('Content-Type') or inContentType + print(f"DBUG:FM:GFW:Resp:{response.status}:{response.msg}") debug.dump({ 'url': req.full_url, 'headers': req.headers, 'ctype': contentType }, { 'cdata': contentData }) - return Response(True, statusCode, "", contentType, contentData) + return Response(True, statusCode, statusMsg, contentType, contentData) except Exception as exc: return Response(False, 502, f"WARN:{tag}:Failed:{exc}") diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index 50a5691703081..bcb11bf2c12c8 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -228,6 +228,7 @@ def process_args(args: list[str]): retained for literal_eval """ import ast + import json global gMe iArg = 1 while iArg < len(args): @@ -252,7 +253,7 @@ def process_args(args: list[str]): except KeyError: print(f"ERRR:ProcessArgs:{iArg}:{cArg}:UnknownCommand") exit(103) - print(gMe) + print(json.dumps(gMe, indent=4)) for k in gConfigNeeded: if gMe.get(k) == None: print(f"ERRR:ProcessArgs:{k}:missing, did you forget to pass the config file...") diff --git a/tools/server/public_simplechat/local.tools/webmagic.py b/tools/server/public_simplechat/local.tools/webmagic.py index 2e3d5811cc2af..dc0d45a78999d 100644 --- a/tools/server/public_simplechat/local.tools/webmagic.py +++ b/tools/server/public_simplechat/local.tools/webmagic.py @@ -2,7 +2,6 @@ # by Humans for All import urllib.parse -import urllib.request import urlvalidator as uv from dataclasses import dataclass import html.parser diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index a3c7c3ec3a4af..198628ba6c9da 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -279,6 +279,8 @@ It is attached to the document object. Some of these can also be updated using t 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. + this is specified in seconds, so that users by default will normally not overload any website through the proxy server. + 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, by default. From c39af13e2d04d96f73aa8d726b21c7a8f5c4c988 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 4 Nov 2025 23:55:51 +0530 Subject: [PATCH 231/266] SimpleChatTC:UI:ClearChat, Unicode icons for Clear, settings Allow user to clear the existing chat. The user does have the option to load the just cleared chat, if required. Add icons wrt clearing chat and settings. --- tools/server/public_simplechat/index.html | 5 ++++- tools/server/public_simplechat/readme.md | 18 ++++++++++++------ tools/server/public_simplechat/simplechat.js | 6 ++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/tools/server/public_simplechat/index.html b/tools/server/public_simplechat/index.html index 3cd840569c3a7..797530d72fb79 100644 --- a/tools/server/public_simplechat/index.html +++ b/tools/server/public_simplechat/index.html @@ -24,7 +24,10 @@

    SimpleChat

    - +
    + + +
    diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 198628ba6c9da..44437e0d107c7 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -182,11 +182,14 @@ Once inside 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. - 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 +* ClearChat/Refresh + * use the clearchat button to clear the currently active chat session. + * just refresh the page, to reset wrt the chat history and system prompts across chat sessions + 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. + * if you refreshed/cleared unknowingly, you can use the Restore feature to try load 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. @@ -434,7 +437,8 @@ 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 ai generated or otherwise javascript code using browser's js capabilities. +* run_javascript_function_code - which can be used to run ai generated or otherwise javascript code + using browser's js capabilities. * data_store_get/set/delete/list - allows for a basic data store to be used. @@ -620,6 +624,8 @@ channel of the handshake. having ai responses built over multiple steps of tool callings etal. So independent of our client side sliding window based drop off or even before they kick in, this can help in many cases. +* UI - add ClearChat button and logic. Also add unicode icons for same as well as for Settings. + #### ToDo diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 4e9ed0620154e..17c27579fffd0 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -326,6 +326,7 @@ class SimpleChat { clear() { this.xchat = []; this.iLastSys = -1; + this.latestResponse = new ChatMessageEx(); } ods_key() { @@ -797,6 +798,7 @@ 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.elBtnClearChat = /** @type{HTMLButtonElement} */(document.getElementById("clearchat")); this.elDivTool = /** @type{HTMLDivElement} */(document.getElementById("tool-div")); this.elBtnTool = /** @type{HTMLButtonElement} */(document.getElementById("tool-btn")); this.elInToolName = /** @type{HTMLInputElement} */(document.getElementById("toolname-in")); @@ -1030,6 +1032,10 @@ class MultiChatUI { this.elDivChat.replaceChildren(); gMe.show_settings(this.elDivChat); }); + this.elBtnClearChat.addEventListener("click", (ev)=>{ + this.simpleChats[this.curChatId].clear() + this.chat_show(this.curChatId) + }); this.elBtnUser.addEventListener("click", (ev)=>{ clearTimeout(this.timers.toolcallResponseSubmitClick) From 7105f4608d1b9f220424bd212369ddd2e6de83cc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 5 Nov 2025 19:29:45 +0530 Subject: [PATCH 232/266] SimpleChatTC:FetchPdfAsText: Renamed function call Some ai's dont seem to be prefering to use this direct helper provided for fetching pdf as text, on its own. Instead ai (gptoss) seems to be keen on fetching raw pdf and extract text etal, so now renaming the function call to try and make its semantic more readily obivious hopefully. It sometimes (not always) seem to assum fetch_web_url_text, can convert pdf to text and return it. Maybe I need to place the specific fetch pdf as text before the generic fetch web url text and so... With the rename, the pdf specific fetch seems to be getting used more. --- tools/server/public_simplechat/readme.md | 4 +++- tools/server/public_simplechat/toolweb.mjs | 26 +++++++++++----------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 44437e0d107c7..27696c04bfa57 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -458,7 +458,7 @@ Either way always remember to cross check the tool requests and generated respon * search_web_text - search for the specified words using the configured search engine and return the plain textual content from the search result page. -* pdf_to_text - fetch/read specified pdf file and extract its textual content +* fetch_pdf_as_text - fetch/read specified pdf file and extract its textual content * this depends on the pypdf python based open source library the above set of web related tool calls work by handshaking with a bundled simple local web proxy @@ -626,6 +626,8 @@ sliding window based drop off or even before they kick in, this can help in many * UI - add ClearChat button and logic. Also add unicode icons for same as well as for Settings. +* renamed pdf_to_text to fetch_pdf_as_text so that ai model can understand the semantic better. + #### ToDo diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index d4c2788340134..0163d880a65b5 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -280,21 +280,21 @@ async function searchwebtext_setup(tcs) { // -// PdfText +// FetchPdfText // -let pdftext_meta = { +let fetchpdftext_meta = { "type": "function", "function": { - "name": "pdf_to_text", - "description": "Read pdf from requested local file path / web url through a proxy server and return its text content after converting pdf to text, in few seconds. One is allowed to get a part of the pdf by specifying the starting and ending page numbers", + "name": "fetch_pdf_as_text", + "description": "Fetch pdf from requested local file path / web url through a proxy server and return its text content after converting pdf to text, in few seconds. One is allowed to get a part of the pdf by specifying the starting and ending page numbers", "parameters": { "type": "object", "properties": { "url":{ "type":"string", - "description":"local file path (file://) / web (http/https) based url of the pdf that will be got and inturn converted to text to an extent" + "description":"local file path (file://) / web (http/https) based url of the pdf that will be got and inturn converted to text" }, "startPageNumber":{ "type":"integer", @@ -312,7 +312,7 @@ let pdftext_meta = { /** - * Implementation of the pdf to text logic. + * Implementation of the fetch pdf as text logic. * Expects a simple minded proxy server to be running locally * * listening on a configured port * * expecting http requests @@ -325,20 +325,20 @@ let pdftext_meta = { * @param {string} toolname * @param {any} obj */ -function pdftext_run(chatid, toolcallid, toolname, obj) { +function fetchpdftext_run(chatid, toolcallid, toolname, obj) { return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'pdftext'); } /** - * Setup pdftext for tool calling + * Setup fetchpdftext for tool calling * NOTE: Currently the logic is setup for the bundled simpleproxy.py * @param {Object>} tcs */ -async function pdftext_setup(tcs) { - return proxyserver_tc_setup('PdfText', 'pdftext', 'pdf_to_text', { - "handler": pdftext_run, - "meta": pdftext_meta, +async function fetchpdftext_setup(tcs) { + return proxyserver_tc_setup('FetchPdfAsText', 'pdftext', 'fetch_pdf_as_text', { + "handler": fetchpdftext_run, + "meta": fetchpdftext_meta, "result": "" }, tcs); } @@ -359,6 +359,6 @@ export async function init(toolsWorker) { await fetchweburlraw_setup(tc_switch) await fetchweburltext_setup(tc_switch) await searchwebtext_setup(tc_switch) - await pdftext_setup(tc_switch) + await fetchpdftext_setup(tc_switch) return tc_switch } From 87a76056a7fb00671fc84f77f0ea29f8c83d1eac Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 5 Nov 2025 21:47:18 +0530 Subject: [PATCH 233/266] SimpleChatTC:System Date and Time --- tools/server/public_simplechat/datautils.mjs | 3 + tools/server/public_simplechat/readme.md | 8 ++- tools/server/public_simplechat/tooljs.mjs | 75 ++++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/datautils.mjs b/tools/server/public_simplechat/datautils.mjs index 75159d6b1676b..a4b305c6cc049 100644 --- a/tools/server/public_simplechat/datautils.mjs +++ b/tools/server/public_simplechat/datautils.mjs @@ -125,6 +125,9 @@ export function trim_hist_garbage_at_end(sIn, maxType, maxUniq, maxMatchLenThres let iNum = 0; let iOth = 0; // Learn + /** + * @type {Object} + */ let hist = {}; let iUniq = 0; for(let i=0; i>} */ export let tc_switch = { + "sys_date_time": { + "handler": sysdatetime_run, + "meta": sysdatetime_meta, + "result": "" + }, "run_javascript_function_code": { "handler": js_run, "meta": js_meta, From 721936e07223a07f73a4666cef305ecb44d2becc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 5 Nov 2025 14:42:39 +0530 Subject: [PATCH 234/266] SimpleChatTC:Cleanup:Make main chat related classes importable Have main classes defined independent of and away from runtime flow Move out the entry point including runtime instantiation of the core Me class (which inturn brings other class instances as neede) into its own main.js file. With this one should be able to import simplechat.js into other files, where one might need the SimpleChat or MultiChat or Me class definitions. --- tools/server/public_simplechat/index.html | 3 +- tools/server/public_simplechat/main.js | 33 ++++++ tools/server/public_simplechat/simplechat.js | 104 ++++++++----------- 3 files changed, 81 insertions(+), 59 deletions(-) create mode 100644 tools/server/public_simplechat/main.js diff --git a/tools/server/public_simplechat/index.html b/tools/server/public_simplechat/index.html index 797530d72fb79..d34a80dfb8e43 100644 --- a/tools/server/public_simplechat/index.html +++ b/tools/server/public_simplechat/index.html @@ -11,12 +11,13 @@ - + diff --git a/tools/server/public_simplechat/main.js b/tools/server/public_simplechat/main.js new file mode 100644 index 0000000000000..05efb4d061b33 --- /dev/null +++ b/tools/server/public_simplechat/main.js @@ -0,0 +1,33 @@ +// @ts-check +// A simple completions and chat/completions test related web front end logic +// by Humans for All + + +import * as mChatMagic from './simplechat.js' +import * as tools from "./tools.mjs" +import * as du from "./datautils.mjs"; + + + +/** @type {mChatMagic.Me} */ +let gMe; + +function startme() { + console.log("INFO:SimpleChat:StartMe:Starting..."); + gMe = new mChatMagic.Me(); + gMe.debug_disable(); + // @ts-ignore + document["gMe"] = gMe; + // @ts-ignore + document["du"] = du; + // @ts-ignore + document["tools"] = tools; + tools.init().then((toolNames)=>gMe.tools.toolNames=toolNames).then(()=>gMe.multiChat.chat_show(gMe.multiChat.curChatId)) + for (let cid of gMe.defaultChatIds) { + gMe.multiChat.new_chat_session(cid); + } + gMe.multiChat.setup_ui(gMe.defaultChatIds[0], true); + gMe.multiChat.show_sessions(); +} + +document.addEventListener("DOMContentLoaded", startme); diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 17c27579fffd0..da4766ef43bee 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1,5 +1,6 @@ // @ts-check -// A simple completions and chat/completions test related web front end logic +// Core classes which provide a simple implementation of handshake with ai server's completions and chat/completions endpoints +// as well as related web front end logic for basic usage and testing. // by Humans for All import * as du from "./datautils.mjs"; @@ -277,7 +278,10 @@ class ChatMessageEx { } -function usage_note() { +/** + * @param {number} iRecentUserMsgCnt + */ +function usage_note(iRecentUserMsgCnt) { let sUsageNote = `
    Usage Note @@ -293,7 +297,7 @@ function usage_note() {
  • If ai assistant requests a tool call, verify same before triggering.
  • submit tool response placed into user query/response text area
  • -
  • ContextWindow = [System, Last[${gMe.chatProps.iRecentUserMsgCnt-1}] User Query/Resp, Cur Query].
  • +
  • ContextWindow = [System, Last[${iRecentUserMsgCnt}] User Query/Resp, Cur Query].
    • ChatHistInCtxt, MaxTokens, ModelCtxt window to expand
    @@ -311,8 +315,9 @@ class SimpleChat { /** * @param {string} chatId + * @param {Me} me */ - constructor(chatId) { + constructor(chatId, me) { this.chatId = chatId; /** * Maintain in a form suitable for common LLM web service chat/completions' messages entry @@ -321,6 +326,7 @@ class SimpleChat { this.xchat = []; this.iLastSys = -1; this.latestResponse = new ChatMessageEx(); + this.me = me; } clear() { @@ -485,14 +491,14 @@ class SimpleChat { /** * Setup the fetch headers. - * It picks the headers from gMe.headers. + * It picks the headers from this.me.headers. * It inserts Authorization only if its non-empty. * @param {string} apiEP */ fetch_headers(apiEP) { let headers = new Headers(); - for(let k in gMe.headers) { - let v = gMe.headers[k]; + for(let k in this.me.headers) { + let v = this.me.headers[k]; if ((k == "Authorization") && (v.trim() == "")) { continue; } @@ -509,13 +515,13 @@ class SimpleChat { * @param {Object} obj */ request_jsonstr_extend(obj) { - for(let k in gMe.apiRequestOptions) { - obj[k] = gMe.apiRequestOptions[k]; + for(let k in this.me.apiRequestOptions) { + obj[k] = this.me.apiRequestOptions[k]; } - if (gMe.chatProps.stream) { + if (this.me.chatProps.stream) { obj["stream"] = true; } - if (gMe.tools.enabled) { + if (this.me.tools.enabled) { obj["tools"] = tools.meta(); } return JSON.stringify(obj); @@ -526,7 +532,7 @@ class SimpleChat { */ request_messages_jsonstr() { let req = { - messages: this.recent_chat_ns(gMe.chatProps.iRecentUserMsgCnt), + messages: this.recent_chat_ns(this.me.chatProps.iRecentUserMsgCnt), } return this.request_jsonstr_extend(req); } @@ -538,7 +544,7 @@ class SimpleChat { request_prompt_jsonstr(bInsertStandardRolePrefix) { let prompt = ""; let iCnt = 0; - for(const msg of this.recent_chat(gMe.chatProps.iRecentUserMsgCnt)) { + for(const msg of this.recent_chat(this.me.chatProps.iRecentUserMsgCnt)) { iCnt += 1; if (iCnt > 1) { prompt += "\n"; @@ -562,7 +568,7 @@ class SimpleChat { if (apiEP == ApiEP.Type.Chat) { return this.request_messages_jsonstr(); } else { - return this.request_prompt_jsonstr(gMe.chatProps.bCompletionInsertStandardRolePrefix); + return this.request_prompt_jsonstr(this.me.chatProps.bCompletionInsertStandardRolePrefix); } } @@ -676,7 +682,7 @@ class SimpleChat { */ async handle_response(resp, apiEP, elDiv) { let theResp = null; - if (gMe.chatProps.stream) { + if (this.me.chatProps.stream) { try { theResp = await this.handle_response_multipart(resp, apiEP, elDiv); this.latestResponse.clear(); @@ -690,7 +696,7 @@ class SimpleChat { } else { theResp = await this.handle_response_oneshot(resp, apiEP); } - if (gMe.chatProps.bTrimGarbage) { + if (this.me.chatProps.bTrimGarbage) { let origMsg = theResp.ns.content; theResp.ns.content = du.trim_garbage_at_end(origMsg); theResp.trimmedContent = origMsg.substring(theResp.ns.content.length); @@ -756,7 +762,11 @@ class SimpleChat { class MultiChatUI { - constructor() { + /** + * @param {Me} me + */ + constructor(me) { + this.me = me /** @type {Object} */ this.simpleChats = {}; /** @type {string} */ @@ -842,10 +852,10 @@ 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.autoSecs > 0) && (bAuto)) { + if ((this.me.tools.autoSecs > 0) && (bAuto)) { this.timers.toolcallTriggerClick = setTimeout(()=>{ this.elBtnTool.click() - }, gMe.tools.autoSecs*this.TimePeriods.ToolCallAutoSecsTimeUnit) + }, this.me.tools.autoSecs*this.TimePeriods.ToolCallAutoSecsTimeUnit) } } else { this.elDivTool.hidden = true @@ -992,7 +1002,7 @@ class MultiChatUI { this.ui_reset_toolcall_as_needed(new ChatMessageEx()); } this.elLastChatMessage = null - let chatToShow = chat.recent_chat(gMe.chatProps.iRecentUserMsgCnt); + let chatToShow = chat.recent_chat(this.me.chatProps.iRecentUserMsgCnt); for(const [i, x] of chatToShow.entries()) { let iFromLast = (chatToShow.length - 1)-i let nextMsg = undefined @@ -1005,9 +1015,9 @@ class MultiChatUI { /** @type{HTMLElement} */(this.elLastChatMessage).scrollIntoView(false); // Stupid ts-check js-doc intersection ??? } else { if (bClear) { - this.elDivChat.innerHTML = usage_note(); - gMe.setup_load(this.elDivChat, chat); - gMe.show_info(this.elDivChat, bShowInfoAll); + this.elDivChat.innerHTML = usage_note(this.me.chatProps.iRecentUserMsgCnt-1); + this.me.setup_load(this.elDivChat, chat); + this.me.show_info(this.elDivChat, bShowInfoAll); } } return true @@ -1030,7 +1040,7 @@ class MultiChatUI { this.elBtnSettings.addEventListener("click", (ev)=>{ this.elDivChat.replaceChildren(); - gMe.show_settings(this.elDivChat); + this.me.show_settings(this.elDivChat); }); this.elBtnClearChat.addEventListener("click", (ev)=>{ this.simpleChats[this.curChatId].clear() @@ -1043,7 +1053,7 @@ class MultiChatUI { if (this.elInUser.disabled) { return; } - this.handle_user_submit(this.curChatId, gMe.chatProps.apiEP).catch((/** @type{Error} */reason)=>{ + this.handle_user_submit(this.curChatId, this.me.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); @@ -1065,17 +1075,17 @@ class MultiChatUI { this.timers.toolcallResponseTimeout = undefined let chat = this.simpleChats[cid]; let limitedData = data - if (gMe.tools.iResultMaxDataLength > 0) { - if (data.length > gMe.tools.iResultMaxDataLength) { - limitedData = data.slice(0, gMe.tools.iResultMaxDataLength) + `\n\n\nALERT: Data too long, was chopped ....` + if (this.me.tools.iResultMaxDataLength > 0) { + if (data.length > this.me.tools.iResultMaxDataLength) { + limitedData = data.slice(0, this.me.tools.iResultMaxDataLength) + `\n\n\nALERT: Data too long, was chopped ....` } } chat.add(new ChatMessageEx(Roles.ToolTemp, ChatMessageEx.createToolCallResultAllInOne(tcid, name, limitedData))) if (this.chat_show(cid)) { - if (gMe.tools.autoSecs > 0) { + if (this.me.tools.autoSecs > 0) { this.timers.toolcallResponseSubmitClick = setTimeout(()=>{ this.elBtnUser.click() - }, gMe.tools.autoSecs*this.TimePeriods.ToolCallAutoSecsTimeUnit) + }, this.me.tools.autoSecs*this.TimePeriods.ToolCallAutoSecsTimeUnit) } } this.ui_reset_userinput(false) @@ -1113,7 +1123,7 @@ class MultiChatUI { * @param {boolean} bSwitchSession */ new_chat_session(chatId, bSwitchSession=false) { - this.simpleChats[chatId] = new SimpleChat(chatId); + this.simpleChats[chatId] = new SimpleChat(chatId, this.me); if (bSwitchSession) { this.handle_session_switch(chatId); } @@ -1143,7 +1153,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.chatProps.bCompletionFreshChatAlways)) { + if ((apiEP == ApiEP.Type.Completion) && (this.me.chatProps.bCompletionFreshChatAlways)) { chat.clear(); } @@ -1167,7 +1177,7 @@ class MultiChatUI { this.elInUser.disabled = true; try { - let theResp = await chat.handle_chat_hs(gMe.baseURL, apiEP, this.elDivChat) + let theResp = await chat.handle_chat_hs(this.me.baseURL, apiEP, this.elDivChat) if (chatId == this.curChatId) { this.chat_show(chatId); if (theResp.trimmedContent.length > 0) { @@ -1206,7 +1216,7 @@ class MultiChatUI { 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) + }, this.me.tools.toolCallResponseTimeoutMS) } } @@ -1312,12 +1322,12 @@ const SearchURLS = { } -class Me { +export class Me { constructor() { this.baseURL = "http://127.0.0.1:8080"; this.defaultChatIds = [ "Default", "Other" ]; - this.multiChat = new MultiChatUI(); + this.multiChat = new MultiChatUI(this); this.tools = { enabled: true, proxyUrl: "http://127.0.0.1:3128", @@ -1462,25 +1472,3 @@ class Me { } -/** @type {Me} */ -let gMe; - -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().then((toolNames)=>gMe.tools.toolNames=toolNames).then(()=>gMe.multiChat.chat_show(gMe.multiChat.curChatId)) - for (let cid of gMe.defaultChatIds) { - gMe.multiChat.new_chat_session(cid); - } - gMe.multiChat.setup_ui(gMe.defaultChatIds[0], true); - gMe.multiChat.show_sessions(); -} - -document.addEventListener("DOMContentLoaded", startme); From 3ff68e3bd122e8942f83aa815295719643649d72 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 5 Nov 2025 17:32:34 +0530 Subject: [PATCH 235/266] SimpleChatTC:Cleanup:MeInTools: update tools, toolweb Now gMe can be used in toolweb with proper knowledge of available members and can also be cross checked by tools --- tools/server/public_simplechat/main.js | 2 +- tools/server/public_simplechat/readme.md | 4 ++ tools/server/public_simplechat/tools.mjs | 8 +++- tools/server/public_simplechat/toolweb.mjs | 47 ++++++++++------------ 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/tools/server/public_simplechat/main.js b/tools/server/public_simplechat/main.js index 05efb4d061b33..0da0ea508a463 100644 --- a/tools/server/public_simplechat/main.js +++ b/tools/server/public_simplechat/main.js @@ -22,7 +22,7 @@ function startme() { document["du"] = du; // @ts-ignore document["tools"] = tools; - tools.init().then((toolNames)=>gMe.tools.toolNames=toolNames).then(()=>gMe.multiChat.chat_show(gMe.multiChat.curChatId)) + tools.init(gMe).then((toolNames)=>gMe.tools.toolNames=toolNames).then(()=>gMe.multiChat.chat_show(gMe.multiChat.curChatId)) for (let cid of gMe.defaultChatIds) { gMe.multiChat.new_chat_session(cid); } diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 1fc680532f440..65f77897ec8b4 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -632,6 +632,10 @@ sliding window based drop off or even before they kick in, this can help in many * sys_date_time tool call has been added. +* SimpleChat - Move the main chat related classes into its own js module file, independent of the +main runtime entry point. This allows these classes to be referenced from other modules like tools +related modules with full access to their details for developers and static check tools. + #### ToDo diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index e4f66ea7f3df4..7fb9a8c2fa72a 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -8,6 +8,7 @@ import * as tjs from './tooljs.mjs' import * as tweb from './toolweb.mjs' import * as tdb from './tooldb.mjs' +import * as mChatMagic from './simplechat.js' let gToolsWorker = new Worker('./toolsworker.mjs', { type: 'module' }); @@ -19,7 +20,10 @@ let gToolsDBWorker = new Worker('./toolsdbworker.mjs', { type: 'module' }); export let tc_switch = {} -export async function init() { +/** + * @param {mChatMagic.Me} me + */ +export async function init(me) { /** * @type {string[]} */ @@ -36,7 +40,7 @@ export async function init() { toolNames.push(key) } }) - let tNs = await tweb.init(gToolsWorker) + let tNs = await tweb.init(gToolsWorker, me) for (const key in tNs) { tc_switch[key] = tNs[key] toolNames.push(key) diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 0163d880a65b5..1a0000a25fae3 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -5,8 +5,14 @@ // by Humans for All // +import * as mChatMagic from './simplechat.js' + let gToolsWorker = /** @type{Worker} */(/** @type {unknown} */(null)); +/** + * @type {mChatMagic.Me} + */ +let gMe = /** @type{mChatMagic.Me} */(/** @type {unknown} */(null)); /** @@ -19,22 +25,13 @@ function message_toolsworker(mev) { } -/** - * Retrieve the global Me instance - */ -function get_gme() { - return (/** @type {Object>} */(/** @type {unknown} */(document)))['gMe'] -} - - /** * For now hash the shared secret with the year. */ -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('') - }) +async function bearer_transform() { + let data = `${new Date().getUTCFullYear()}${gMe.tools.proxyAuthInsecure}` + const ab = await crypto.subtle.digest('sha-256', new TextEncoder().encode(data)); + return Array.from(new Uint8Array(ab)).map(b => b.toString(16).padStart(2, '0')).join(''); } /** @@ -56,7 +53,7 @@ function bearer_transform() { async function proxyserver_get_anyargs(chatid, toolcallid, toolname, objSearchParams, path, objHeaders={}) { if (gToolsWorker.onmessage != null) { let params = new URLSearchParams(objSearchParams) - let newUrl = `${get_gme().tools.proxyUrl}/${path}?${params}` + let newUrl = `${gMe.tools.proxyUrl}/${path}?${params}` let headers = new Headers(objHeaders) let btoken = await bearer_transform() headers.append('Authorization', `Bearer ${btoken}`) @@ -84,7 +81,7 @@ async function proxyserver_get_anyargs(chatid, toolcallid, toolname, objSearchPa * @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(`${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 @@ -253,15 +250,13 @@ let searchwebtext_meta = { * @param {any} 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)); - delete(obj.words) - obj['url'] = searchUrl - let headers = { 'urltext-tag-drops': JSON.stringify(get_gme().tools.searchDrops) } - return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'urltext', headers); - } + /** @type {string} */ + let searchUrl = gMe.tools.searchUrl; + searchUrl = searchUrl.replace("SEARCHWORDS", encodeURIComponent(obj.words)); + delete(obj.words) + obj['url'] = searchUrl + let headers = { 'urltext-tag-drops': JSON.stringify(gMe.tools.searchDrops) } + return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'urltext', headers); } @@ -349,13 +344,15 @@ async function fetchpdftext_setup(tcs) { * 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 + * @param {mChatMagic.Me} me */ -export async function init(toolsWorker) { +export async function init(toolsWorker, me) { /** * @type {Object>} tcs */ let tc_switch = {} gToolsWorker = toolsWorker + gMe = me await fetchweburlraw_setup(tc_switch) await fetchweburltext_setup(tc_switch) await searchwebtext_setup(tc_switch) From 2618d3a65f61f8ba21f2156a471328069d69227f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 01:12:30 +0530 Subject: [PATCH 236/266] SimpleChatTC:MeInTools: WebWorkers in Me Given that Me is now passed to the tools logic during setup, have the web worker handles in Me itself, instead of in tool related modules. Move setup of web worker related main thread callbacks, as well as posting messages directly to these main thread callbacks, into Me. --- tools/server/public_simplechat/simplechat.js | 39 ++++++++++++++++++-- tools/server/public_simplechat/tooldb.mjs | 14 ++++--- tools/server/public_simplechat/tooljs.mjs | 20 +++++----- tools/server/public_simplechat/tools.mjs | 35 +++++++----------- tools/server/public_simplechat/toolweb.mjs | 22 +++-------- 5 files changed, 72 insertions(+), 58 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index da4766ef43bee..21be956c5b4e3 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1069,8 +1069,8 @@ class MultiChatUI { this.handle_tool_run(this.curChatId); }) - // Handle messages from Tools web worker - tools.setup((cid, tcid, name, data)=>{ + // Handle messages from tools web workers + this.me.workers_cb((cid, tcid, name, data)=>{ clearTimeout(this.timers.toolcallResponseTimeout) this.timers.toolcallResponseTimeout = undefined let chat = this.simpleChats[cid]; @@ -1386,6 +1386,10 @@ export class Me { //"frequency_penalty": 1.2, //"presence_penalty": 1.2, }; + this.workers = { + js: /** @type {Worker} */(/** @type {unknown} */(undefined)), + db: /** @type {Worker} */(/** @type {unknown} */(undefined)), + } } /** @@ -1469,6 +1473,35 @@ export class Me { }) } -} + /** + * Setup the callback that will be called when ever message + * is recieved from the Tools Web Workers. + * @param {(chatId: string, toolCallId: string, name: string, data: string) => void} cb + */ + workers_cb(cb) { + this.workers.js.onmessage = function (ev) { + cb(ev.data.cid, ev.data.tcid, ev.data.name, ev.data.data) + } + this.workers.db.onmessage = function (ev) { + cb(ev.data.cid, ev.data.tcid, ev.data.name, JSON.stringify(ev.data.data, (k,v)=>{ + return (v === undefined) ? '__UNDEFINED__' : v; + })); + } + } + /** + * Send a message to specified tools web worker's monitor in main thread directly + * @param {Worker} worker + * @param {string} chatid + * @param {string} toolcallid + * @param {string} toolname + * @param {string} data + */ + workers_postmessage_for_main(worker, chatid, toolcallid, toolname, data) { + let mev = new MessageEvent('message', {data: {cid: chatid, tcid: toolcallid, name: toolname, data: data}}); + if (worker.onmessage != null) { + worker.onmessage(mev) + } + } +} diff --git a/tools/server/public_simplechat/tooldb.mjs b/tools/server/public_simplechat/tooldb.mjs index f77a4e698553c..fd02facffc736 100644 --- a/tools/server/public_simplechat/tooldb.mjs +++ b/tools/server/public_simplechat/tooldb.mjs @@ -1,12 +1,14 @@ //@ts-check // ALERT - Simple Stupid flow - Using from a discardable VM is better // Helpers to handle tools/functions calling wrt data store -// using a web worker. +// using a db specific web worker. // by Humans for All // +import * as mChatMagic from './simplechat.js' -let gToolsDBWorker = /** @type{Worker} */(/** @type {unknown} */(null)); + +let gMe = /** @type{mChatMagic.Me} */(/** @type {unknown} */(null)); let dsget_meta = { @@ -93,7 +95,7 @@ let dslist_meta = { * @param {any} obj */ function dsops_run(chatid, toolcallid, toolname, obj) { - gToolsDBWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) + gMe.workers.db.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) } @@ -128,8 +130,8 @@ export let tc_switch = { /** * 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 + * @param {mChatMagic.Me} me */ -export async function init(toolsWorker) { - gToolsDBWorker = toolsWorker +export async function init(me) { + gMe = me } diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index cc0d6e4cb3512..b3d18ce370975 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -3,12 +3,14 @@ // Helpers to handle tools/functions calling wrt // * javascript interpreter // * simple arithmatic calculator -// using a web worker. +// using the js specific web worker. // by Humans for All // +import * as mChatMagic from './simplechat.js' -let gToolsWorker = /** @type{Worker} */(/** @type {unknown} */(null)); + +let gMe = /** @type{mChatMagic.Me} */(/** @type {unknown} */(null)); let sysdatetime_meta = { @@ -75,9 +77,7 @@ function sysdatetime_run(chatid, toolcallid, toolname, obj) { break; } } - if (gToolsWorker.onmessage != null) { - gToolsWorker.onmessage(new MessageEvent('message', {data: {cid: chatid, tcid: toolcallid, name: toolname, data: sDT}})) - } + gMe.workers_postmessage_for_main(gMe.workers.js, chatid, toolcallid, toolname, sDT); } @@ -109,7 +109,7 @@ let js_meta = { * @param {any} obj */ function js_run(chatid, toolcallid, toolname, obj) { - gToolsWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: obj["code"]}) + gMe.workers.js.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: obj["code"]}) } @@ -141,7 +141,7 @@ let calc_meta = { * @param {any} obj */ function calc_run(chatid, toolcallid, toolname, obj) { - gToolsWorker.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: `console.log(${obj["arithexpr"]})`}) + gMe.workers.js.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: `console.log(${obj["arithexpr"]})`}) } @@ -170,8 +170,8 @@ export let tc_switch = { /** * 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 + * @param {mChatMagic.Me} me */ -export async function init(toolsWorker) { - gToolsWorker = toolsWorker +export async function init(me) { + gMe = me } diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 7fb9a8c2fa72a..5f1c46301fa83 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -11,8 +11,6 @@ import * as tdb from './tooldb.mjs' import * as mChatMagic from './simplechat.js' -let gToolsWorker = new Worker('./toolsworker.mjs', { type: 'module' }); -let gToolsDBWorker = new Worker('./toolsdbworker.mjs', { type: 'module' }); /** * Maintain currently available tool/function calls * @type {Object>} @@ -20,27 +18,37 @@ let gToolsDBWorker = new Worker('./toolsdbworker.mjs', { type: 'module' }); export let tc_switch = {} +/** + * @param {mChatMagic.Me} me + */ +function setup_workers(me) { + me.workers.js = new Worker('./toolsworker.mjs', { type: 'module' }); + me.workers.db = new Worker('./toolsdbworker.mjs', { type: 'module' }); +} + + /** * @param {mChatMagic.Me} me */ export async function init(me) { + setup_workers(me); /** * @type {string[]} */ let toolNames = [] - await tjs.init(gToolsWorker).then(()=>{ + await tjs.init(me).then(()=>{ for (const key in tjs.tc_switch) { tc_switch[key] = tjs.tc_switch[key] toolNames.push(key) } }) - await tdb.init(gToolsDBWorker).then(()=>{ + await tdb.init(me).then(()=>{ for (const key in tdb.tc_switch) { tc_switch[key] = tdb.tc_switch[key] toolNames.push(key) } }) - let tNs = await tweb.init(gToolsWorker, me) + let tNs = await tweb.init(me) for (const key in tNs) { tc_switch[key] = tNs[key] toolNames.push(key) @@ -58,23 +66,6 @@ export function meta() { } -/** - * Setup the callback that will be called when ever message - * is recieved from the Tools Web Worker. - * @param {(chatId: string, toolCallId: string, name: string, data: string) => void} cb - */ -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, JSON.stringify(ev.data.data, (k,v)=>{ - return (v === undefined) ? '__UNDEFINED__' : v; - })); - } -} - - /** * Try call the specified tool/function call. * Returns undefined, if the call was placed successfully diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index 1a0000a25fae3..a2181e46472c4 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -2,29 +2,19 @@ // ALERT - Simple Stupid flow - Using from a discardable VM is better // Helpers to handle tools/functions calling related to web access, pdf, etal // which work in sync with the bundled simpleproxy.py server logic. +// Uses the js specific web worker path. // by Humans for All // import * as mChatMagic from './simplechat.js' -let gToolsWorker = /** @type{Worker} */(/** @type {unknown} */(null)); /** * @type {mChatMagic.Me} */ let gMe = /** @type{mChatMagic.Me} */(/** @type {unknown} */(null)); -/** - * Send a message to Tools WebWorker's monitor in main thread directly - * @param {MessageEvent} mev - */ -function message_toolsworker(mev) { - // @ts-ignore - gToolsWorker.onmessage(mev) -} - - /** * For now hash the shared secret with the year. */ @@ -51,7 +41,7 @@ async function bearer_transform() { * @param {any} objHeaders */ async function proxyserver_get_anyargs(chatid, toolcallid, toolname, objSearchParams, path, objHeaders={}) { - if (gToolsWorker.onmessage != null) { + if (gMe.workers.js.onmessage != null) { let params = new URLSearchParams(objSearchParams) let newUrl = `${gMe.tools.proxyUrl}/${path}?${params}` let headers = new Headers(objHeaders) @@ -63,9 +53,9 @@ async function proxyserver_get_anyargs(chatid, toolcallid, toolname, objSearchPa } return resp.text() }).then(data => { - message_toolsworker(new MessageEvent('message', {data: {cid: chatid, tcid: toolcallid, name: toolname, data: data}})) + gMe.workers_postmessage_for_main(gMe.workers.js, chatid, toolcallid, toolname, data); }).catch((err)=>{ - message_toolsworker(new MessageEvent('message', {data: {cid: chatid, tcid: toolcallid, name: toolname, data: `Error:${err}`}})) + gMe.workers_postmessage_for_main(gMe.workers.js, chatid, toolcallid, toolname, `Error:${err}`); }) } } @@ -343,15 +333,13 @@ async function fetchpdftext_setup(tcs) { /** * 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 * @param {mChatMagic.Me} me */ -export async function init(toolsWorker, me) { +export async function init(me) { /** * @type {Object>} tcs */ let tc_switch = {} - gToolsWorker = toolsWorker gMe = me await fetchweburlraw_setup(tc_switch) await fetchweburltext_setup(tc_switch) From 20610abfe5cf0b3c7b554a7ca2c08b4d0e2316d5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 02:23:37 +0530 Subject: [PATCH 237/266] SimpleChatTC:Rather bring in Tools Class So that all tools related management logic sits in tools module itself, but is accessible from Me by having a instance of Tools. The Workers moved into Tools class. The tc_switch moved into Tools class. The setup_workers, init, meta and tool_call moved into Tools class. --- tools/server/public_simplechat/tools.mjs | 130 ++++++++++++----------- 1 file changed, 69 insertions(+), 61 deletions(-) diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 5f1c46301fa83..0ec037aa35c1c 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -11,80 +11,88 @@ import * as tdb from './tooldb.mjs' import * as mChatMagic from './simplechat.js' -/** - * Maintain currently available tool/function calls - * @type {Object>} - */ -export let tc_switch = {} +class Tools { -/** - * @param {mChatMagic.Me} me - */ -function setup_workers(me) { - me.workers.js = new Worker('./toolsworker.mjs', { type: 'module' }); - me.workers.db = new Worker('./toolsdbworker.mjs', { type: 'module' }); -} + constructor() { + /** + * Maintain currently available tool/function calls + * @type {Object>} + */ + this.tc_switch = {} + + this.workers = { + js: /** @type {Worker} */(/** @type {unknown} */(undefined)), + db: /** @type {Worker} */(/** @type {unknown} */(undefined)), + } + + } + setup_workers() { + this.workers.js = new Worker('./toolsworker.mjs', { type: 'module' }); + this.workers.db = new Worker('./toolsdbworker.mjs', { type: 'module' }); + } -/** - * @param {mChatMagic.Me} me - */ -export async function init(me) { - setup_workers(me); /** - * @type {string[]} + * @param {mChatMagic.Me} me */ - let toolNames = [] - await tjs.init(me).then(()=>{ - for (const key in tjs.tc_switch) { - tc_switch[key] = tjs.tc_switch[key] - toolNames.push(key) - } - }) - await tdb.init(me).then(()=>{ - for (const key in tdb.tc_switch) { - tc_switch[key] = tdb.tc_switch[key] + async init(me) { + this.setup_workers(); + /** + * @type {string[]} + */ + let toolNames = [] + await tjs.init(me).then(()=>{ + for (const key in tjs.tc_switch) { + this.tc_switch[key] = tjs.tc_switch[key] + toolNames.push(key) + } + }) + await tdb.init(me).then(()=>{ + for (const key in tdb.tc_switch) { + this.tc_switch[key] = tdb.tc_switch[key] + toolNames.push(key) + } + }) + let tNs = await tweb.init(me) + for (const key in tNs) { + this.tc_switch[key] = tNs[key] toolNames.push(key) } - }) - let tNs = await tweb.init(me) - for (const key in tNs) { - tc_switch[key] = tNs[key] - toolNames.push(key) + return toolNames } - return toolNames -} - -export function meta() { - let tools = [] - for (const key in tc_switch) { - tools.push(tc_switch[key]["meta"]) + meta() { + let tools = [] + for (const key in this.tc_switch) { + tools.push(this.tc_switch[key]["meta"]) + } + return tools } - return tools -} - -/** - * 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(chatid, toolcallid, toolname, toolargs) { - for (const fn in tc_switch) { - if (fn == toolname) { - try { - 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}` + /** + * 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 + */ + async tool_call(chatid, toolcallid, toolname, toolargs) { + for (const fn in this.tc_switch) { + if (fn == toolname) { + try { + this.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}` + } } } + return `Unknown Tool/Function Call:${toolname}` } - return `Unknown Tool/Function Call:${toolname}` + + + } From 0a28fb7fcc3d558763516c4f0e89530076c4a931 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 02:38:09 +0530 Subject: [PATCH 238/266] SimpleChatTC:ToolsManager: Instantiate in Me and Use Rename Tools to ToolsManager to convey its semantic better. Move setup of workers onmessage callback as well as directly passing result to these callbacks into ToolsManager. Now that Workers have been moved into ToolsManager, and ToolsManager has been instantiated as a member of Me, use the same in place of prev workers of Me. --- tools/server/public_simplechat/simplechat.js | 44 +++----------------- tools/server/public_simplechat/tooldb.mjs | 2 +- tools/server/public_simplechat/tooljs.mjs | 6 +-- tools/server/public_simplechat/tools.mjs | 36 +++++++++++++++- tools/server/public_simplechat/toolweb.mjs | 6 +-- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 21be956c5b4e3..d5e69698050af 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -5,7 +5,7 @@ import * as du from "./datautils.mjs"; import * as ui from "./ui.mjs" -import * as tools from "./tools.mjs" +import * as mTools from "./tools.mjs" class Roles { @@ -522,7 +522,7 @@ class SimpleChat { obj["stream"] = true; } if (this.me.tools.enabled) { - obj["tools"] = tools.meta(); + obj["tools"] = this.me.toolsMgr.meta(); } return JSON.stringify(obj); } @@ -751,7 +751,7 @@ class SimpleChat { return "Tool/Function call name not specified" } try { - return await tools.tool_call(this.chatId, toolcallid, toolname, toolargs) + return await this.me.toolsMgr.tool_call(this.chatId, toolcallid, toolname, toolargs) } catch (/** @type {any} */error) { return `Tool/Function call raised an exception:${error.name}:${error.message}` } @@ -1070,7 +1070,7 @@ class MultiChatUI { }) // Handle messages from tools web workers - this.me.workers_cb((cid, tcid, name, data)=>{ + this.me.toolsMgr.workers_cb((cid, tcid, name, data)=>{ clearTimeout(this.timers.toolcallResponseTimeout) this.timers.toolcallResponseTimeout = undefined let chat = this.simpleChats[cid]; @@ -1386,10 +1386,7 @@ export class Me { //"frequency_penalty": 1.2, //"presence_penalty": 1.2, }; - this.workers = { - js: /** @type {Worker} */(/** @type {unknown} */(undefined)), - db: /** @type {Worker} */(/** @type {unknown} */(undefined)), - } + this.toolsMgr = new mTools.ToolsManager() } /** @@ -1473,35 +1470,4 @@ export class Me { }) } - /** - * Setup the callback that will be called when ever message - * is recieved from the Tools Web Workers. - * @param {(chatId: string, toolCallId: string, name: string, data: string) => void} cb - */ - workers_cb(cb) { - this.workers.js.onmessage = function (ev) { - cb(ev.data.cid, ev.data.tcid, ev.data.name, ev.data.data) - } - this.workers.db.onmessage = function (ev) { - cb(ev.data.cid, ev.data.tcid, ev.data.name, JSON.stringify(ev.data.data, (k,v)=>{ - return (v === undefined) ? '__UNDEFINED__' : v; - })); - } - } - - /** - * Send a message to specified tools web worker's monitor in main thread directly - * @param {Worker} worker - * @param {string} chatid - * @param {string} toolcallid - * @param {string} toolname - * @param {string} data - */ - workers_postmessage_for_main(worker, chatid, toolcallid, toolname, data) { - let mev = new MessageEvent('message', {data: {cid: chatid, tcid: toolcallid, name: toolname, data: data}}); - if (worker.onmessage != null) { - worker.onmessage(mev) - } - } - } diff --git a/tools/server/public_simplechat/tooldb.mjs b/tools/server/public_simplechat/tooldb.mjs index fd02facffc736..365006dced8e0 100644 --- a/tools/server/public_simplechat/tooldb.mjs +++ b/tools/server/public_simplechat/tooldb.mjs @@ -95,7 +95,7 @@ let dslist_meta = { * @param {any} obj */ function dsops_run(chatid, toolcallid, toolname, obj) { - gMe.workers.db.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) + gMe.toolsMgr.workers.db.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, args: obj}) } diff --git a/tools/server/public_simplechat/tooljs.mjs b/tools/server/public_simplechat/tooljs.mjs index b3d18ce370975..ad7cc4d0b20f4 100644 --- a/tools/server/public_simplechat/tooljs.mjs +++ b/tools/server/public_simplechat/tooljs.mjs @@ -77,7 +77,7 @@ function sysdatetime_run(chatid, toolcallid, toolname, obj) { break; } } - gMe.workers_postmessage_for_main(gMe.workers.js, chatid, toolcallid, toolname, sDT); + gMe.toolsMgr.workers_postmessage_for_main(gMe.toolsMgr.workers.js, chatid, toolcallid, toolname, sDT); } @@ -109,7 +109,7 @@ let js_meta = { * @param {any} obj */ function js_run(chatid, toolcallid, toolname, obj) { - gMe.workers.js.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: obj["code"]}) + gMe.toolsMgr.workers.js.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: obj["code"]}) } @@ -141,7 +141,7 @@ let calc_meta = { * @param {any} obj */ function calc_run(chatid, toolcallid, toolname, obj) { - gMe.workers.js.postMessage({ cid: chatid, tcid: toolcallid, name: toolname, code: `console.log(${obj["arithexpr"]})`}) + gMe.toolsMgr.workers.js.postMessage({ cid: chatid, tcid: 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 0ec037aa35c1c..da22de2ed0d45 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -12,7 +12,7 @@ import * as mChatMagic from './simplechat.js' -class Tools { +export class ToolsManager { constructor() { /** @@ -34,6 +34,8 @@ class Tools { } /** + * Initialise the ToolsManager, + * including all the different tools groups. * @param {mChatMagic.Me} me */ async init(me) { @@ -62,6 +64,9 @@ class Tools { return toolNames } + /** + * Prepare the tools meta data that can be passed to the ai server. + */ meta() { let tools = [] for (const key in this.tc_switch) { @@ -93,6 +98,35 @@ class Tools { return `Unknown Tool/Function Call:${toolname}` } + /** + * Setup the callback that will be called when ever message + * is recieved from the Tools Web Workers. + * @param {(chatId: string, toolCallId: string, name: string, data: string) => void} cb + */ + workers_cb(cb) { + this.workers.js.onmessage = function (ev) { + cb(ev.data.cid, ev.data.tcid, ev.data.name, ev.data.data) + } + this.workers.db.onmessage = function (ev) { + cb(ev.data.cid, ev.data.tcid, ev.data.name, JSON.stringify(ev.data.data, (k,v)=>{ + return (v === undefined) ? '__UNDEFINED__' : v; + })); + } + } + /** + * Send a message to specified tools web worker's monitor in main thread directly + * @param {Worker} worker + * @param {string} chatid + * @param {string} toolcallid + * @param {string} toolname + * @param {string} data + */ + workers_postmessage_for_main(worker, chatid, toolcallid, toolname, data) { + let mev = new MessageEvent('message', {data: {cid: chatid, tcid: toolcallid, name: toolname, data: data}}); + if (worker.onmessage != null) { + worker.onmessage(mev) + } + } } diff --git a/tools/server/public_simplechat/toolweb.mjs b/tools/server/public_simplechat/toolweb.mjs index a2181e46472c4..7ca4205ea3609 100644 --- a/tools/server/public_simplechat/toolweb.mjs +++ b/tools/server/public_simplechat/toolweb.mjs @@ -41,7 +41,7 @@ async function bearer_transform() { * @param {any} objHeaders */ async function proxyserver_get_anyargs(chatid, toolcallid, toolname, objSearchParams, path, objHeaders={}) { - if (gMe.workers.js.onmessage != null) { + if (gMe.toolsMgr.workers.js.onmessage != null) { let params = new URLSearchParams(objSearchParams) let newUrl = `${gMe.tools.proxyUrl}/${path}?${params}` let headers = new Headers(objHeaders) @@ -53,9 +53,9 @@ async function proxyserver_get_anyargs(chatid, toolcallid, toolname, objSearchPa } return resp.text() }).then(data => { - gMe.workers_postmessage_for_main(gMe.workers.js, chatid, toolcallid, toolname, data); + gMe.toolsMgr.workers_postmessage_for_main(gMe.toolsMgr.workers.js, chatid, toolcallid, toolname, data); }).catch((err)=>{ - gMe.workers_postmessage_for_main(gMe.workers.js, chatid, toolcallid, toolname, `Error:${err}`); + gMe.toolsMgr.workers_postmessage_for_main(gMe.toolsMgr.workers.js, chatid, toolcallid, toolname, `Error:${err}`); }) } } From a659344388b38a674282da18f2b78d16895036d5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 03:28:33 +0530 Subject: [PATCH 239/266] SimpleChatTC:ToolsManager: Cleanup inc delayed direct posting Me.tools.toolNames is now directly updated by init of ToolsManager The two then in the old tools.init was also unneeded then also as both could have been merged into a single then, even then. However with the new flow, the 1st then is no longer required. Also now the direct calling of onmessage handler on the main thread side wrt immidiate result from tool call is delayed for a cycling through the events loop, by using a setTimeout. No longer expose the tools module throught documents, given that the tools module mainly contains ToolsManager, whose only instance is available through the global gMe. Move the devel related exposing throught document object into a function of its own. --- tools/server/public_simplechat/main.js | 26 ++++++++++++-------- tools/server/public_simplechat/readme.md | 14 ++++++++--- tools/server/public_simplechat/tools.mjs | 30 +++++++++++++++++------- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/tools/server/public_simplechat/main.js b/tools/server/public_simplechat/main.js index 0da0ea508a463..c26dab47c845b 100644 --- a/tools/server/public_simplechat/main.js +++ b/tools/server/public_simplechat/main.js @@ -1,28 +1,34 @@ // @ts-check -// A simple completions and chat/completions test related web front end logic +// A simple implementation of GenAi/LLM chat web client ui / front end logic. +// It handshake with ai server's completions and chat/completions endpoints +// and helps with basic usage and testing. // by Humans for All import * as mChatMagic from './simplechat.js' -import * as tools from "./tools.mjs" import * as du from "./datautils.mjs"; - /** @type {mChatMagic.Me} */ let gMe; -function startme() { - console.log("INFO:SimpleChat:StartMe:Starting..."); - gMe = new mChatMagic.Me(); - gMe.debug_disable(); + +function devel_expose() { // @ts-ignore document["gMe"] = gMe; // @ts-ignore document["du"] = du; - // @ts-ignore - document["tools"] = tools; - tools.init(gMe).then((toolNames)=>gMe.tools.toolNames=toolNames).then(()=>gMe.multiChat.chat_show(gMe.multiChat.curChatId)) +} + + +function startme() { + console.log("INFO:SimpleChat:StartMe:Starting..."); + gMe = new mChatMagic.Me(); + gMe.debug_disable(); + devel_expose() + gMe.toolsMgr.init(gMe).then(()=>{ + gMe.multiChat.chat_show(gMe.multiChat.curChatId); + }) for (let cid of gMe.defaultChatIds) { gMe.multiChat.new_chat_session(cid); } diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 65f77897ec8b4..784c5c6ef6bd9 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -632,9 +632,17 @@ sliding window based drop off or even before they kick in, this can help in many * sys_date_time tool call has been added. -* SimpleChat - Move the main chat related classes into its own js module file, independent of the -main runtime entry point. This allows these classes to be referenced from other modules like tools -related modules with full access to their details for developers and static check tools. +* Refactor code and flow a bit wrt the client web ui + * Move the main chat related classes into its own js module file, independent of the main + runtime entry point (rather move out the runtime entry point into its own file). This allows + these classes to be referenced from other modules like tools related modules with full access + to these classes's details for developers and static check tools. + * building on same make the Tools management code into a ToolsManager class which is inturn + instantiated and the handle stored in top level Me class. This class also maintains and + manages the web workers as well as routing of the tool calling among others. + * add a common helper for posting results directly to the main thread side web worker callback + handlers. Inturn run the calling through a setTimeout0, so that delayed/missing response + situation rescuer timeout logic etal flow doesnt get messed for now. #### ToDo diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index da22de2ed0d45..b3aa3d843fd17 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -43,25 +43,24 @@ export class ToolsManager { /** * @type {string[]} */ - let toolNames = [] + me.tools.toolNames = [] await tjs.init(me).then(()=>{ for (const key in tjs.tc_switch) { this.tc_switch[key] = tjs.tc_switch[key] - toolNames.push(key) + me.tools.toolNames.push(key) } }) await tdb.init(me).then(()=>{ for (const key in tdb.tc_switch) { this.tc_switch[key] = tdb.tc_switch[key] - toolNames.push(key) + me.tools.toolNames.push(key) } }) let tNs = await tweb.init(me) for (const key in tNs) { this.tc_switch[key] = tNs[key] - toolNames.push(key) + me.tools.toolNames.push(key) } - return toolNames } /** @@ -115,7 +114,18 @@ export class ToolsManager { } /** - * Send a message to specified tools web worker's monitor in main thread directly + * Send message to specified Tools-WebWorker's monitor/onmessage handler of main thread + * by calling it directly. + * + * The specified web worker's main thread monitor/callback logic is triggerd in a delayed + * manner by cycling the call through the events loop by using a setTimeout 0, so that the + * callback gets executed only after the caller's code following the call to this helper + * is done. + * + * NOTE: This is needed to ensure that any tool call handler that returns the tool call + * result immidiately without using any asynhronous mechanism, doesnt get-messed-by / + * mess-with the delayed response identifier and rescuer timeout logic. + * * @param {Worker} worker * @param {string} chatid * @param {string} toolcallid @@ -124,9 +134,11 @@ export class ToolsManager { */ workers_postmessage_for_main(worker, chatid, toolcallid, toolname, data) { let mev = new MessageEvent('message', {data: {cid: chatid, tcid: toolcallid, name: toolname, data: data}}); - if (worker.onmessage != null) { - worker.onmessage(mev) - } + setTimeout(function() { + if (worker.onmessage != null) { + worker.onmessage(mev) + } + }, 0); } } From 3894f4eec17a8fcd1eae00c034a7a6a497345232 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 18:51:42 +0530 Subject: [PATCH 240/266] SimpleChatTC:TrackToolCalls:helps ignor delayed response, if reqd Add a pending object which maintains the pending toolcallid wrt each chat session, when ever a tool call is made. In turn when ever a tool call response is got cross check if its toolcallid matches that in the pending list. If so accept the tool call response and remove from pending list. If not just ignore the response. NOTE: The current implementation supports only 1 pending tool call at any time. NOTE: Had to change from a anonymous to arrow function so as to be able to get access to the ToolsManager instance (this) from within the function. ie make use of lexical binding semantic of arrow functions. --- tools/server/public_simplechat/tools.mjs | 54 +++++++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index b3aa3d843fd17..9c955415a673b 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -26,6 +26,12 @@ export class ToolsManager { db: /** @type {Worker} */(/** @type {unknown} */(undefined)), } + /** + * Maintain the latest pending tool call id for each unique chat session id + * @type {Object} + */ + this.pending = {} + } setup_workers() { @@ -74,6 +80,43 @@ export class ToolsManager { return tools } + /** + * Add specified toolcallid to pending list for specified chat session id. + * @param {string} chatid + * @param {string} toolcallid + */ + toolcallpending_add(chatid, toolcallid) { + console.debug(`DBUG:ToolsManager:ToolCallPendingAdd:${chatid}:${toolcallid}`) + this.pending[chatid] = toolcallid; + } + + /** + * Clear pending list for specified chat session id. + * @param {string} chatid + * @param {string} tag + */ + toolcallpending_clear(chatid, tag) { + let curtcid = this.pending[chatid]; + console.debug(`DBUG:ToolsManager:ToolCallPendingClear:${tag}:${chatid}:${curtcid}`) + delete(this.pending[chatid]); + } + + /** + * Check if there is a pending tool call awaiting tool call result for given chat session id. + * Clears from pending list, if found. + * @param {string} chatid + * @param {string} toolcallid + * @param {string} tag + */ + toolcallpending_found_cleared(chatid, toolcallid, tag) { + if (this.pending[chatid] !== toolcallid) { + console.log(`WARN:ToolsManager:ToolCallPendingFoundCleared:${tag}:${chatid}:${toolcallid} not found, skipping...`) + return false + } + this.toolcallpending_clear(chatid, tag) + return true + } + /** * Try call the specified tool/function call. * Returns undefined, if the call was placed successfully @@ -87,6 +130,7 @@ export class ToolsManager { for (const fn in this.tc_switch) { if (fn == toolname) { try { + this.toolcallpending_add(chatid, toolcallid); this.tc_switch[fn]["handler"](chatid, toolcallid, fn, JSON.parse(toolargs)) return undefined } catch (/** @type {any} */error) { @@ -103,10 +147,16 @@ export class ToolsManager { * @param {(chatId: string, toolCallId: string, name: string, data: string) => void} cb */ workers_cb(cb) { - this.workers.js.onmessage = function (ev) { + this.workers.js.onmessage = (ev) => { + if (!this.toolcallpending_found_cleared(ev.data.cid, ev.data.tcid, 'js')) { + return + } cb(ev.data.cid, ev.data.tcid, ev.data.name, ev.data.data) } - this.workers.db.onmessage = function (ev) { + this.workers.db.onmessage = (ev) => { + if (!this.toolcallpending_found_cleared(ev.data.cid, ev.data.tcid, 'db')) { + return + } cb(ev.data.cid, ev.data.tcid, ev.data.name, JSON.stringify(ev.data.data, (k,v)=>{ return (v === undefined) ? '__UNDEFINED__' : v; })); From cc12dd90bb57fe593c0afb4f36db7757046476db Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 19:52:56 +0530 Subject: [PATCH 241/266] SimpleChatTC:TCPending: Clear pending in unhappy paths ie if exception raised during tool call execution and or time out occurs --- tools/server/public_simplechat/readme.md | 6 ++++++ tools/server/public_simplechat/simplechat.js | 2 ++ tools/server/public_simplechat/tools.mjs | 1 + 3 files changed, 9 insertions(+) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 784c5c6ef6bd9..a1764b740a54b 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -644,6 +644,12 @@ sliding window based drop off or even before they kick in, this can help in many handlers. Inturn run the calling through a setTimeout0, so that delayed/missing response situation rescuer timeout logic etal flow doesnt get messed for now. +* track tool calling and inturn maintain pending tool calls so that only still valid tool call responses + will be accepted when the asynchronous tool call response is recieved. Also take care of clearing + pending tool call tracking in unhappy paths like when exception noticied as part of tool call execution, + or if there is no response within the configured timeout period. + NOTE: Currently the logic supports only 1 pending tool call per chat session. + #### ToDo diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index d5e69698050af..6a583aab1ccc9 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -753,6 +753,7 @@ class SimpleChat { try { return await this.me.toolsMgr.tool_call(this.chatId, toolcallid, toolname, toolargs) } catch (/** @type {any} */error) { + this.me.toolsMgr.toolcallpending_found_cleared(this.chatId, toolcallid, 'SC:HandleToolCall:Exc') return `Tool/Function call raised an exception:${error.name}:${error.message}` } } @@ -1213,6 +1214,7 @@ class MultiChatUI { this.ui_reset_userinput(false) } else { this.timers.toolcallResponseTimeout = setTimeout(() => { + this.me.toolsMgr.toolcallpending_found_cleared(chat.chatId, toolCallId, 'MCUI:HandleToolRun:TimeOut') 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) diff --git a/tools/server/public_simplechat/tools.mjs b/tools/server/public_simplechat/tools.mjs index 9c955415a673b..76f95e094724d 100644 --- a/tools/server/public_simplechat/tools.mjs +++ b/tools/server/public_simplechat/tools.mjs @@ -134,6 +134,7 @@ export class ToolsManager { this.tc_switch[fn]["handler"](chatid, toolcallid, fn, JSON.parse(toolargs)) return undefined } catch (/** @type {any} */error) { + this.toolcallpending_found_cleared(chatid, toolcallid, 'ToolsManager:ToolCall:Exc') return `Tool/Function call raised an exception:${error.name}:${error.message}` } } From 32429f47c8e66bc9d16497265001302189651fde Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 23:44:11 +0530 Subject: [PATCH 242/266] SimpleChatTC:IndexHTML:Fix a oversight with new module added Add forgotten to add , after simplechat entry. Currently I am not strictly using the importmap feature, so the error didnt create any problem, but the error was there which has been fixed. --- tools/server/public_simplechat/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/server/public_simplechat/index.html b/tools/server/public_simplechat/index.html index d34a80dfb8e43..67a3834eb103b 100644 --- a/tools/server/public_simplechat/index.html +++ b/tools/server/public_simplechat/index.html @@ -11,7 +11,7 @@