From 4898c01e273d5a00ad96659e41762f23f387f592 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 18:40:05 +0530 Subject: [PATCH 001/365] 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 c713cb07329fe5beee440f42256a50828531e3d8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 20:18:16 +0530 Subject: [PATCH 002/365] 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 2e438bb31ddc47d13b63009b50a1cd0e2f6d16c9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 23:02:30 +0530 Subject: [PATCH 003/365] 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 08a607ae217e541b63735866dea2ac55f597be59 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 10 Oct 2025 23:31:36 +0530 Subject: [PATCH 004/365] 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 a98b13c2df345ac22eb5c485d3aa495a76b78658 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 00:02:32 +0530 Subject: [PATCH 005/365] 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 2bc00d93757d7cae54c2d524d5200c4abb0660e9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 00:18:37 +0530 Subject: [PATCH 006/365] 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 95b99ce7c819eb7e3cc6bc014e458c06063dbbed Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 01:06:23 +0530 Subject: [PATCH 007/365] 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 788a7a0829dc384d40b56d2e26715fc36caefba2 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 01:17:19 +0530 Subject: [PATCH 008/365] 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 9250453fed6a113f59827d8217c713f63e58b927 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 01:46:23 +0530 Subject: [PATCH 009/365] 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 aff0b343a71f48b327efbb321b11da2831c9b52e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 02:35:01 +0530 Subject: [PATCH 010/365] 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 c33b1ef77846d720f319f9d8f6aeabc2d3d8fed5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 02:58:03 +0530 Subject: [PATCH 011/365] 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 206c1dc98da6fdc65cceb6372b0abb4bacdb8d22 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 03:14:27 +0530 Subject: [PATCH 012/365] 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 d6504dffd53ab7198fbb29e5e55f5ca813b582c3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 03:31:18 +0530 Subject: [PATCH 013/365] 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 dbaeb6ae6cf5d9e7f3b5b99a1c78dd9f93047d63 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 04:03:53 +0530 Subject: [PATCH 014/365] 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 efb4a8b8be9f8ea2cb0f9d84cedcd71bb5324ac3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 04:22:15 +0530 Subject: [PATCH 015/365] 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 561925c5c8e7794addfb494c7fda52c89dbbc1cf Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 11 Oct 2025 23:27:08 +0530 Subject: [PATCH 016/365] 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 a519e753e41d7ffd0cddc2dfaeed5644325a4607 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 01:20:53 +0530 Subject: [PATCH 017/365] 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 7db0d9f9620702858f644a4129d68983f828ed97 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 01:36:43 +0530 Subject: [PATCH 018/365] 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 fc36e2e1199b9a1dac4d5adb5bb9a4a442c9fd8d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 02:53:08 +0530 Subject: [PATCH 019/365] 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 eba9876c5f2eaf7303cc58e14025d6cceaae7841 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 12:58:37 +0530 Subject: [PATCH 020/365] 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 65f932eb6f070cc1acf03ea42a004401089b895c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 13:01:59 +0530 Subject: [PATCH 021/365] 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 4faf33dc2f2010c4799adae50bddf24ba73eaf03 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 16:08:11 +0530 Subject: [PATCH 022/365] 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 bfdf5f684de545a00db52946801be704abed1eb2 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 17:19:51 +0530 Subject: [PATCH 023/365] 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 149aff2ad1e642b8ff6ddb5e031a648bae5efa5b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 21:21:00 +0530 Subject: [PATCH 024/365] 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 9fcb278523968631bf0d660feffad3d7439a772b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 22:16:34 +0530 Subject: [PATCH 025/365] 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 13eb19629833c80bade5355e390f23abb259dfde Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 12 Oct 2025 23:50:35 +0530 Subject: [PATCH 026/365] 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 72153c964bccd59ffc431d427b253465c49127df Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 00:41:19 +0530 Subject: [PATCH 027/365] 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 de496864663f54f80ab9d8b14cdcef7defd819f1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 01:19:37 +0530 Subject: [PATCH 028/365] 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 55ea19032be63abdc94e5208104d0078b73bbfd8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 02:57:17 +0530 Subject: [PATCH 029/365] 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 165cfff8852b9fecad758c348c2eb14a0ec3eeaa Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 03:18:09 +0530 Subject: [PATCH 030/365] 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 823229abcc2dd5d24dd636f72431857b3e632488 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 03:32:58 +0530 Subject: [PATCH 031/365] 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 b83e539d8667b8e1ec13b606a01517879526e524 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 22:11:25 +0530 Subject: [PATCH 032/365] 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 127dbc8835d147aa607d1286b2dffb3ea883c581 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 22:31:31 +0530 Subject: [PATCH 033/365] 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 32e9bac9de19f4157887d1393875e20dd479dbdc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 13 Oct 2025 23:07:44 +0530 Subject: [PATCH 034/365] 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 e2060bbcac6ea57fd42d4c39871b2e0a236b542d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 00:54:21 +0530 Subject: [PATCH 035/365] 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 36d981296b97ef3831f3600f9e85ca6b8306afab Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 00:58:23 +0530 Subject: [PATCH 036/365] 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 36363557d5965639f72bcebece0d68682163e62e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 01:06:00 +0530 Subject: [PATCH 037/365] 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 6421a645f31721b4bc8f171ba5f70838b470956c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 01:34:22 +0530 Subject: [PATCH 038/365] 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 d219d8ee21365e8e82dd911174b60f4bacd4f332 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 01:56:34 +0530 Subject: [PATCH 039/365] 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 3b7980d04d511670057dad998da3b9d0894cac21 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 02:28:42 +0530 Subject: [PATCH 040/365] 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 a47ef5bea5a46a7f17b2f38c5479a079e5ec5c44 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 02:35:24 +0530 Subject: [PATCH 041/365] 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 117ccd438f440c9c4e68bb10a7ebfb960725bc8e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 16:48:40 +0530 Subject: [PATCH 042/365] 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 24e3c0a6eb9e811188445796fe84826bb199de53 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 17:59:06 +0530 Subject: [PATCH 043/365] 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 e4267b1bd5e94672958ae7ed1dc5b51ed9d05e2b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 18:28:49 +0530 Subject: [PATCH 044/365] 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 4c4b1a56d2f2fd00ab0692af4a50bc4b0be86f1c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 18:36:25 +0530 Subject: [PATCH 045/365] 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 9d5fef646037fce9c3325def7f30ac342e6d2a01 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 18:52:18 +0530 Subject: [PATCH 046/365] 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 e1cfd8d846cc8a97eeda4e8dab54d1cfb57a3db8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 19:03:48 +0530 Subject: [PATCH 047/365] 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 020ae40c0485a457d4fa2057fa940c78569f2145 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 19:33:24 +0530 Subject: [PATCH 048/365] 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 863818ad64fa1ad8015c1868bbf3d9a73b939639 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 21:02:22 +0530 Subject: [PATCH 049/365] 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 cda39b7bb1d764815261116eeadca1831576fcd4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 21:36:11 +0530 Subject: [PATCH 050/365] 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 229bc0f2daab9ee8f14ef567580b651c5d6205e0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 22:26:48 +0530 Subject: [PATCH 051/365] 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 83e322d400295c3683babbdb8409286d55ed5091 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 14 Oct 2025 23:27:03 +0530 Subject: [PATCH 052/365] 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 68ec6ba370b1204d05b1bd1bf28429c298eecc3d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 00:04:29 +0530 Subject: [PATCH 053/365] 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 0b16a3a4e4fb0162894b66965004baa84ff642d0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 01:20:27 +0530 Subject: [PATCH 054/365] 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 e20a34c01d979407ab906e24e1739302cc731828 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 03:28:48 +0530 Subject: [PATCH 055/365] 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 95f2fc88a4a49b3dbb4ebd5bc4c536b8b8aab3ab Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 03:35:55 +0530 Subject: [PATCH 056/365] 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 19dd653189845755e2f81bf15313b3a2325bd2ca Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 04:13:34 +0530 Subject: [PATCH 057/365] 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 6b5c37df22f981ed9ccae2aed58da618f54d1e02 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 05:31:08 +0530 Subject: [PATCH 058/365] 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 aa794ddf8c130c65975489eefd89df560f38f835 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 18:20:20 +0530 Subject: [PATCH 059/365] 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 39f2e199877fcd1de6a0242f2eb4537f040fd1c0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 21:48:18 +0530 Subject: [PATCH 060/365] 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 1b6c77b180221b9d84ca70e89eb412b5f89d3f1a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 22:10:27 +0530 Subject: [PATCH 061/365] 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 d3c9c62e51fdb8556602b09aa9049155572fc067 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 22:52:07 +0530 Subject: [PATCH 062/365] 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 7a2da6028b81141832a2535d809add854fa91065 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 23:13:02 +0530 Subject: [PATCH 063/365] 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 236290d5068ab2a4758e994728174138cd2dab8c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 15 Oct 2025 23:31:07 +0530 Subject: [PATCH 064/365] 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 faf8a9595d4aa990be58a60b92cc3f57556c0b0d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 11:52:21 +0530 Subject: [PATCH 065/365] 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 72f48629307b9669e5545cdc4f8b94efcb572024 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 17:45:33 +0530 Subject: [PATCH 066/365] 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 8a1161f8a61c41393423e94a85d158f300507731 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:06:56 +0530 Subject: [PATCH 067/365] 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 28f421d46747d8ce995c962bebc7316054513107 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:18:45 +0530 Subject: [PATCH 068/365] 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 4011bbf9fb643a65f1a04443ee2568ee20ba6bec Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:39:02 +0530 Subject: [PATCH 069/365] 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 240778b74f6cee071d56308ad4d37d4642e69b78 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:54:20 +0530 Subject: [PATCH 070/365] 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 ef9026c19b9e9e5c4483a620ea9764dc3dcf402b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 18:58:06 +0530 Subject: [PATCH 071/365] 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 86266275ff3e1615909baeaed0d08fddea265537 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 19:02:55 +0530 Subject: [PATCH 072/365] 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 7064467df804e89a005c91f1502195f283367707 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 16 Oct 2025 22:47:21 +0530 Subject: [PATCH 073/365] 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 27d6c9c33677c0ef1df947e14004f07e9029f863 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 00:11:01 +0530 Subject: [PATCH 074/365] 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 74935ce711d57b58b739d2228908b66b48c9ad85 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 00:42:04 +0530 Subject: [PATCH 075/365] 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 de4d60771a6fe38f4d04218963386316f4afaf6c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 01:30:46 +0530 Subject: [PATCH 076/365] 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 78cde98b430e8db083c9a9d1a2a19fea73c45f2c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 01:39:35 +0530 Subject: [PATCH 077/365] 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 862dc1dfc67a3605e06f71dd763c90ad9b8395f0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 01:50:26 +0530 Subject: [PATCH 078/365] 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 5664ad719b96bb814a992f4bf3df72258e39c465 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 02:08:38 +0530 Subject: [PATCH 079/365] 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 bb5dd0db0427f36700f376de9f05b563319267b9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 02:52:37 +0530 Subject: [PATCH 080/365] 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 070588935efa784fd12b978052f90da60a056cb8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 03:02:53 +0530 Subject: [PATCH 081/365] 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 b06cdd2384a4450538c8b44f11e4712a384eb37e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 03:56:35 +0530 Subject: [PATCH 082/365] 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 73502ab36bf38d9d794743d15a1a5b7a0eb20474 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 04:13:46 +0530 Subject: [PATCH 083/365] 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 682ac72245afb1f83ecf8d364eea6685c6c6a831 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 05:22:13 +0530 Subject: [PATCH 084/365] 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 f0be2033e702e3c3828fa57c4048c5b5b375a8fb Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 05:36:43 +0530 Subject: [PATCH 085/365] 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 3c399b5cb8fafa5da5c306000686b97f4f30dcda Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 18:01:25 +0530 Subject: [PATCH 086/365] 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 97fac0484671c83f8d7a4ef0ffcc19e9d1c587a9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 18:25:50 +0530 Subject: [PATCH 087/365] 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 73cd2b2d1898d0442376f84758a8b5607d60954f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 22:16:30 +0530 Subject: [PATCH 088/365] 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 7a47460a0aca7435913cacdbd026b992eb5810a6 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 17 Oct 2025 22:46:08 +0530 Subject: [PATCH 089/365] 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 5cc42d9e19247a6cff12ac25af6adcb81e1a74a0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 18 Oct 2025 01:27:53 +0530 Subject: [PATCH 090/365] 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 4b92e6c5c93b864893fe1c19cd6d41ee9e426749 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 18 Oct 2025 18:40:09 +0530 Subject: [PATCH 091/365] 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 87a422baf3edc8e8b6e7f51405de5ae241027c39 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 00:41:14 +0530 Subject: [PATCH 092/365] 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 993794bcc7227180683ddb29a77e5d094297f026 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 13:58:27 +0530 Subject: [PATCH 093/365] 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 79c8ecbf1737ecf271ee6cc237f6df35c47fbbc7 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 14:55:13 +0530 Subject: [PATCH 094/365] 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 44f98ca1903aa22a3aa5799b6fc4011dda070239 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 15:05:55 +0530 Subject: [PATCH 095/365] 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 01e20519fd24274e01c934be18213864bdd6f1f2 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 15:35:34 +0530 Subject: [PATCH 096/365] 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 9953d3d93eb19555d28d38492b85e1edc77d177b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 15:48:58 +0530 Subject: [PATCH 097/365] 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 bb6b3664ab3280c4eff5c68e607ab4696c4b56bb Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 16:08:19 +0530 Subject: [PATCH 098/365] 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 8f4603f4d1318638be0a6c76dea14100484d8d5e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 16:59:32 +0530 Subject: [PATCH 099/365] 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 25b0ebc78158089bad50b40791096ed11ba0d558 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 17:28:46 +0530 Subject: [PATCH 100/365] 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 8378609c039e0f5bd4140f6b5da07fa09b6b4bb6 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 18:59:18 +0530 Subject: [PATCH 101/365] 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 d0156f3f7c1728a06ae8c3ea72ba9700a697edcd Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 19:45:12 +0530 Subject: [PATCH 102/365] 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 946af44ea2284228d6efa94b82874da19767be57 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 19:55:12 +0530 Subject: [PATCH 103/365] 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 0a4d68fa728203a6dee9a6de7fa465ef95a70487 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 20 Oct 2025 21:01:23 +0530 Subject: [PATCH 104/365] 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 2c69c4f5b2516141a20353f55b7bef45dff740c9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 01:57:57 +0530 Subject: [PATCH 105/365] 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 1df428bc65137ad3fa79f68dd60769363347be70 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 02:14:17 +0530 Subject: [PATCH 106/365] 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 4276fa8478aad1c86f5cd8adea2ff9418ebfd4d8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 18:53:49 +0530 Subject: [PATCH 107/365] 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 dfc31046f92aa46707836973f9e9984a8d0491d4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 19:37:18 +0530 Subject: [PATCH 108/365] 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 374af2ca743d1f7aa7c07cafff20df969d17215b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 19:57:53 +0530 Subject: [PATCH 109/365] 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 a4721c02316bc4054e6c2cb3128ae95409a6b736 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 21:44:30 +0530 Subject: [PATCH 110/365] 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 1e43c3872f713f8c6496fbb0b166ff324e76b552 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 21:55:23 +0530 Subject: [PATCH 111/365] 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 b3187897bb10b55ab99c72a16422197a42de3f82 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 22:00:12 +0530 Subject: [PATCH 112/365] 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 73fea5ea8512cef3b9da4b6b4d7e019008e299bd Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 22:15:58 +0530 Subject: [PATCH 113/365] 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 eaff59c26490daaa8c6a75b9f9710db005a2e5cb Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 23:06:44 +0530 Subject: [PATCH 114/365] 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 0273e92ceccb12cb2ec30abdf8b9f619fb733f12 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 23:37:33 +0530 Subject: [PATCH 115/365] 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 b97b32e34e13333735a482fcdbc835a658bccb83 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 21 Oct 2025 23:44:39 +0530 Subject: [PATCH 116/365] 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 2894e4dc0d7fafdd442d7513e1052d2741fe0e93 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 00:07:22 +0530 Subject: [PATCH 117/365] 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 e29db58b753eb6b0bb9d65a47dc21b661569fc4b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 00:11:14 +0530 Subject: [PATCH 118/365] 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 f52b95ed624aa863171d693f7c0b676a89ea08c3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 00:20:41 +0530 Subject: [PATCH 119/365] 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 94a81571ad749a2c6da2e37b6423b81b884344ac Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 08:54:15 +0530 Subject: [PATCH 120/365] 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 ee4b4232e8a558bea26211f132c55263333ffe71 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 22 Oct 2025 09:43:34 +0530 Subject: [PATCH 121/365] 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 7b9fdf7ac40ab83b50bcc516a1fb9f1d59a9c53f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 17:33:54 +0530 Subject: [PATCH 122/365] 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 d4350d16ac7e8df78c1698a22342d887b65dcc51 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 18:02:53 +0530 Subject: [PATCH 123/365] 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 d486e2c8e7e0be1c0dc16681a695fe4830d4a5f3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 18:30:42 +0530 Subject: [PATCH 124/365] 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 004b31d39f6c8e7583464828306e083fec0cc1d1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 19:01:50 +0530 Subject: [PATCH 125/365] 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 83cfcb9b90fe27c7adb32f01d613fa7f0a7b7659 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 19:30:48 +0530 Subject: [PATCH 126/365] 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 a43668ce81a68dd4170349567bbb98a0dfdde2b2 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 20:57:22 +0530 Subject: [PATCH 127/365] 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 4814217e7bc9e31fb7407850a59c9172ad7cd079 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 22:39:05 +0530 Subject: [PATCH 128/365] 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 a40a845dded37ec449f599cbede74adfbb1fb4d5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 22:49:07 +0530 Subject: [PATCH 129/365] 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 8772646a2c807b1b986827e4b6f6aebfbd70d44f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 23 Oct 2025 23:18:06 +0530 Subject: [PATCH 130/365] 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 af0b33329d56e18d4d9a29a39a060e04b43dfec8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 00:36:54 +0530 Subject: [PATCH 131/365] 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 1fa3cb0383ca9801d786142e40a42451396305a7 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 01:11:22 +0530 Subject: [PATCH 132/365] 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 ddc2e9dc87107b120ebca8890e8ca2668eea562f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 01:31:30 +0530 Subject: [PATCH 133/365] 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 1ce5e32995d22b1483a1fe30e4030c4ccee63d66 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 03:00:09 +0530 Subject: [PATCH 134/365] 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 69c6a7d496e742cbcda92183edc360171c0d0bcb Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 03:40:55 +0530 Subject: [PATCH 135/365] 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 22ccae324fde9285cf86aa598dea789f75a546e9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 04:18:36 +0530 Subject: [PATCH 136/365] 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 1e580d7c901e7d3dd3017e5991267f29e4f47225 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 05:05:24 +0530 Subject: [PATCH 137/365] 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 d865b04a4366b14cdedb18003ee21f0b876435e2 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 19:04:33 +0530 Subject: [PATCH 138/365] 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 bbf6e4c6bba64de3a8794bbf75ccf778c2e25fe0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 20:51:10 +0530 Subject: [PATCH 139/365] 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 f9c6273658ae9463b50ce7c0a3a5eca26825066c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Fri, 24 Oct 2025 20:55:44 +0530 Subject: [PATCH 140/365] 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 95f75e315a4327586923d30d737aa5f7a72e9eed Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 12:01:56 +0530 Subject: [PATCH 141/365] 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 af38fa499a94f3119a5d26e57c36baf7544e9a5b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 15:51:48 +0530 Subject: [PATCH 142/365] 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 64ccfd0a1fd60d69873cba2b2b3daffb694fa08e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 15:54:47 +0530 Subject: [PATCH 143/365] 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 5354af3594ce6f86956a0ff8b274f4a3c29c596b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 19:13:30 +0530 Subject: [PATCH 144/365] 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 6a44daf3cbf07a913d65b24df1e878139e8d59fd Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 21:12:48 +0530 Subject: [PATCH 145/365] 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 d97f2c0d6e15f91759d5594c731614694f40b063 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 23:12:06 +0530 Subject: [PATCH 146/365] 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 e15f517eae01a2c4bab7c5d7f3155c8e768027c1 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 26 Oct 2025 23:25:05 +0530 Subject: [PATCH 147/365] 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 d9b3585158051f947b43feacfbdf50f70df6a254 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 09:09:05 +0530 Subject: [PATCH 148/365] 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 ce71a2bbe028600f9be3e2c21a8c92990a54dd48 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 09:43:49 +0530 Subject: [PATCH 149/365] 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 c0b4214aca1a4b1a4815e5030c095bbff652c7e8 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 10:32:19 +0530 Subject: [PATCH 150/365] 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 00c3dc2fb17b4714dbac55b834c2ca6f8f525f7d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 11:00:16 +0530 Subject: [PATCH 151/365] 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 3cbc99b4412dc421f4fb541e6dd1ce0e3b663b99 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 11:10:08 +0530 Subject: [PATCH 152/365] 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 1452c9692457cafea3c4c624cdb90d61bb4d7887 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 11:29:13 +0530 Subject: [PATCH 153/365] 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 f77b2e466b526673da6754218a024f16d95e7586 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 27 Oct 2025 15:44:17 +0530 Subject: [PATCH 154/365] 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 4db7b46cc2bb6c651e127565c97f9e7a97568c01 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 00:23:04 +0530 Subject: [PATCH 155/365] 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 1c5fd6e6bfabcc1d68963c69f01f3f7c3c33c444 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 01:43:52 +0530 Subject: [PATCH 156/365] 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 c0d7d17cedaea1e1e1a2da050cd61af2acdfc77f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 02:17:15 +0530 Subject: [PATCH 157/365] 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 f20ac869171148335d3faf34921085ff9ea52788 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 02:37:34 +0530 Subject: [PATCH 158/365] 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 800d3ae3f86f9b3db0662960fbae102cefaabac3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 02:50:28 +0530 Subject: [PATCH 159/365] 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 48c824f8bd7436c0692645e5c13d3a952d4502a6 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 03:34:02 +0530 Subject: [PATCH 160/365] 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 4ed20ce1d05bb48c2c26e0700955b375141a200f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 15:16:14 +0530 Subject: [PATCH 161/365] 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 b4a72f0f27757cfc84565bde68f9c22e42c4c4a0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 16:14:00 +0530 Subject: [PATCH 162/365] 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 95787f9a78206bfb2823bf0308079677e65d4870 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 17:05:58 +0530 Subject: [PATCH 163/365] 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 4d9b9f2b3c8aa3a90a2d9609e330eee2ef386698 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 17:55:45 +0530 Subject: [PATCH 164/365] 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 b6660edc881abcf09d2254c64198f81765d5fa2d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 18:14:26 +0530 Subject: [PATCH 165/365] 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 a9a3ffcaf13fdf3e19682032487e5dde2151555d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 28 Oct 2025 18:56:24 +0530 Subject: [PATCH 166/365] 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 2e5d7d0430d6a8dc9971bb5e16c4810b007f2cb5 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 03:38:34 +0530 Subject: [PATCH 167/365] 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/365] 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 af004245710ac7296fb0945c50a84301733345dd Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 15:39:32 +0530 Subject: [PATCH 169/365] 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 e173a6db4287f4c5d0287eed6f415027effcc8b0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 19:32:44 +0530 Subject: [PATCH 170/365] 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 7e9df269338b25863f0c3134b9435ad359dfa8af Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 20:11:14 +0530 Subject: [PATCH 171/365] 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 efb6324104304ce23ab817f89869950e5d176a7a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 21:14:21 +0530 Subject: [PATCH 172/365] 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 fe89cb5a48d132f5b8e4951ad49db935409f58ad Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 29 Oct 2025 22:06:57 +0530 Subject: [PATCH 173/365] 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 e71675afb2c8eb6d80cb8dfd27f71b3e40b8b870 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 01:35:09 +0530 Subject: [PATCH 174/365] 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 52f55f48977a3621927f93ea278762a03490c763 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 01:13:12 +0530 Subject: [PATCH 175/365] 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 921f58f6b43de568a41d90bc8410f3195f8aa0c9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 01:53:28 +0530 Subject: [PATCH 176/365] 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 20a389356e1506c83be8b10c14b9b6d9a636e84d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 02:29:46 +0530 Subject: [PATCH 177/365] 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 27a9f57cd53ea0bd6c718aadb3ff27c7cafb2be7 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 03:13:32 +0530 Subject: [PATCH 178/365] 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 edaeb758fd41ad311154710bf7456ee25e7ef260 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 03:28:07 +0530 Subject: [PATCH 179/365] 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 5fb94781bc1508deec03f05d6deb3d10e15c96b9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 04:24:10 +0530 Subject: [PATCH 180/365] 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 0f2e0a263d0d3c346bfc9fe837e80714ec3524c0 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 04:36:47 +0530 Subject: [PATCH 181/365] 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 308eefc4ea0f9aa16bb20f0b81f791d1a3897fcc Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 05:51:38 +0530 Subject: [PATCH 182/365] 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 6d26b39afe21fcf096f71c1e51be7e17dd960aa7 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 05:59:45 +0530 Subject: [PATCH 183/365] 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 32b3c3dff6202ac6710487bd10d63f291766083c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 06:14:27 +0530 Subject: [PATCH 184/365] 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 66804eac9b02f150311f720cb5f56afaca9003fb Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 06:24:01 +0530 Subject: [PATCH 185/365] 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 4292eab4a3a642d4e28adb0bc063ed361f00ae7b Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 06:28:27 +0530 Subject: [PATCH 186/365] 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 5a60bddfa5df1c1b9dd4a4e2493ffab2d58b10db Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 30 Oct 2025 07:11:50 +0530 Subject: [PATCH 187/365] 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 bb4f049eaf5e42b6db3ccb0f745bed7a5c21d1c9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 15:01:52 +0530 Subject: [PATCH 188/365] 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 d22819573626217379acd64a039f179fd82f65de Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 15:04:10 +0530 Subject: [PATCH 189/365] 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 ce0e77a0f2a685af0948e54cbfd93b3cfd911d55 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 15:22:29 +0530 Subject: [PATCH 190/365] 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 f1fdc1c788f1684d74e06ead11c24697d9247d10 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 15:48:02 +0530 Subject: [PATCH 191/365] 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 a3248439cbf8693d45c79ffc9bb8733810828387 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 20:53:01 +0530 Subject: [PATCH 192/365] 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 10c726393372fbc5d128601b4af7902b87a020ee Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 21:31:27 +0530 Subject: [PATCH 193/365] 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 16499dbdde62311993a6b6a141e6594dec7c6e91 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 22:06:43 +0530 Subject: [PATCH 194/365] 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 94f37c203f41659159259923b7b757fe558ea345 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 22:16:02 +0530 Subject: [PATCH 195/365] 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 64439c88969eaa35a66616c00c39d77520dc354a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 22:24:13 +0530 Subject: [PATCH 196/365] 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 4a071d7ff60f8ff63d23687a37771395bbb11917 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 22:32:57 +0530 Subject: [PATCH 197/365] 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 cfee209fa4e483522e28cbfe8737d6e3e073512c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 23:12:27 +0530 Subject: [PATCH 198/365] 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 361d6e1e2ffa3887ed8dd9bcb3fe0dea49b5e71a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sat, 1 Nov 2025 23:52:42 +0530 Subject: [PATCH 199/365] 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 7c4d488752842f83cd71a90bf66ca80195c36b5d Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 02:01:31 +0530 Subject: [PATCH 200/365] 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 40829c13f497b7e646430477389b2ef0e920a626 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 02:29:00 +0530 Subject: [PATCH 201/365] 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 dfb154e7a7ea8f5cb130587fe85caf403d0d724e Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 02:47:46 +0530 Subject: [PATCH 202/365] 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 9305799234231745cd0745d1bb11a5ddd0e1e3a2 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 03:05:30 +0530 Subject: [PATCH 203/365] 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 136dd5872d472c65ab4b794c1522874b4cb798a9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 03:38:43 +0530 Subject: [PATCH 204/365] 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 bd85df9c22c9a603a19cd2537bc5b7ff1173e799 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 04:23:47 +0530 Subject: [PATCH 205/365] 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 8650a9630c4e51abf28d34de0cc9e96c09c46414 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 13:06:40 +0530 Subject: [PATCH 206/365] 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 6f52e8989a06a4469bc4a8159bc3ea08802d23eb Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 13:07:41 +0530 Subject: [PATCH 207/365] 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 21845ebce2c4a3defb7c08e7d9a5cfd939ca64d9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 13:39:37 +0530 Subject: [PATCH 208/365] 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 6c50b58fac0281f6549bcce6cb79e939f625edde Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 14:15:07 +0530 Subject: [PATCH 209/365] 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 ac716801b8ff3c08169be6f932e1918d8cd32a52 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 16:24:36 +0530 Subject: [PATCH 210/365] 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 6e1d080d7f3bcc334cedaae570bd25b9f23d275f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 16:36:41 +0530 Subject: [PATCH 211/365] 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 0d06d2238bd301038d94c9b4531684367243cf3c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 16:44:40 +0530 Subject: [PATCH 212/365] 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 772d26772420bc9b097e2d09fae5a9ee34773fc7 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 17:24:36 +0530 Subject: [PATCH 213/365] 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 50fa20b5c211ec90ed0d2da1db7f23615dfb4fb4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 18:30:11 +0530 Subject: [PATCH 214/365] 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 ed391ffefb443cf35e0d69fe871df8d15a6f4fce Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 20:13:10 +0530 Subject: [PATCH 215/365] 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 9a8ff55e1be5ab1aafe76f046f0211112f373726 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 22:11:07 +0530 Subject: [PATCH 216/365] 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 bf89061230df293b3b4da5ce386047061eb853f2 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Sun, 2 Nov 2025 22:33:57 +0530 Subject: [PATCH 217/365] 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 0ec8fbb8021273e3ecaa43aaf7e2eaba97dc38da Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 01:11:35 +0530 Subject: [PATCH 218/365] 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 fc8f1bb44e0846d988e3503426d7b2e44a59eac6 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 01:45:38 +0530 Subject: [PATCH 219/365] 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 714c13a71433257d91d55f3723a0fb3c8e71d424 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 03:24:07 +0530 Subject: [PATCH 220/365] 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 5ea269e3e51a095484c30b969c12b3d8489ff915 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 12:37:59 +0530 Subject: [PATCH 221/365] 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 1cb0dabd4515a7710cbf94b396e0d14760b365fb Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 12:12:41 +0530 Subject: [PATCH 222/365] 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 228091c3024c1cdee764a02e8a721ebc066b59ee Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 13:02:56 +0530 Subject: [PATCH 223/365] 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 2097c0986d3ee302a756fd23d44f2f22e4eabbfe Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 14:35:23 +0530 Subject: [PATCH 224/365] 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 45a8c359215e6b96f3c1c34b5f693e248216170c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 18:57:29 +0530 Subject: [PATCH 225/365] 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 c30a2876527c4328c1e2631c0d86d54bd6518b76 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 20:02:56 +0530 Subject: [PATCH 226/365] 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 14aa437a807e1e7bb25f9051de655e41d622b5ff Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 20:59:18 +0530 Subject: [PATCH 227/365] 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 eee883040c6d238946fc12e3b35ba0640b85094f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 21:42:48 +0530 Subject: [PATCH 228/365] 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 619a997d751f57d3fd154c831866dc957ea2c07a Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 3 Nov 2025 23:13:12 +0530 Subject: [PATCH 229/365] 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 1efffa0424f660545d9a32526c9c849c1c26e598 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 4 Nov 2025 22:25:39 +0530 Subject: [PATCH 230/365] 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 84a83727b8efd3ded00863883ed59f2aee0469e7 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Tue, 4 Nov 2025 23:55:51 +0530 Subject: [PATCH 231/365] 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 ba68df900d4a0ddd32bc7ce6c67e547bb8696a62 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 5 Nov 2025 19:29:45 +0530 Subject: [PATCH 232/365] 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 44dfe0c043134de1668027f51539c56b766e08b4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 5 Nov 2025 21:47:18 +0530 Subject: [PATCH 233/365] 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 37ddd3b0d37799dc939760e9571f54d53552876f Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 5 Nov 2025 14:42:39 +0530 Subject: [PATCH 234/365] 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 389abf9992f09d5191454f7c7bd67035baa43868 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Wed, 5 Nov 2025 17:32:34 +0530 Subject: [PATCH 235/365] 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 295dfce3ca1b264b418783db55169cc74546ee65 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 01:12:30 +0530 Subject: [PATCH 236/365] 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 9d7042f6ef64caa963b37982c1483c4ab0ffe4e3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 02:23:37 +0530 Subject: [PATCH 237/365] 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 11109f5348cd97b7dd278e69dcbb5f12bb0b37c4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 02:38:09 +0530 Subject: [PATCH 238/365] 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 24ea71b9465f2ff7b17e9b5f5a0a1dcd78f3ade3 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 03:28:33 +0530 Subject: [PATCH 239/365] 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 f64d31aaf8c2d4a37f2922ebe7f3cf6e6fee9537 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 18:51:42 +0530 Subject: [PATCH 240/365] 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 33370e682be7cd9edb7cf9505d413d80fdced9ad Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 19:52:56 +0530 Subject: [PATCH 241/365] 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 76fdefc4fc8e192ef4a98598c19c9a373e2dde9c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Thu, 6 Nov 2025 23:44:11 +0530 Subject: [PATCH 242/365] 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 @@ diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 6958948edf68f..a889645f6f9c6 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -1,5 +1,5 @@ -# SimpleChat +# SimpleChat / AnveshikaSallap by Humans for All. diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index a348a67115423..273f6d3e28319 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -1,6 +1,9 @@ // @ts-check -// 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. +// 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. +// Supports tool calling (including a bunch of builtin ones), reasoning and vision related handshakes, +// if supported by the ai model, that one is interacting with. +// // by Humans for All import * as du from "./datautils.mjs"; @@ -18,6 +21,7 @@ export const AI_TC_SESSIONNAME = `TCExternalAI` const ROLES_TEMP_ENDSWITH = TEMP_MARKER + export class Roles { static System = "system"; static User = "user"; @@ -451,21 +455,26 @@ function usage_note(sRecentUserMsgCnt) {
    Usage Note
      -
    • New button creates new chat session, with its own system prompt.
    • +
    • New btn creates new chat session, with its own system prompt & settings.
    • Prompt button toggles system prompt entry.
    • -
    • System prompt above, helps control ai response characteristics.
      • -
      • Completion mode - no system prompt normally.
      • +
      • System prompt, helps control ai response characteristics.
      • +
      • No system prompt normally if using Completion mode
    • Use shift+enter for inserting enter/newline.
    • -
    • Enter your query/response to ai assistant in text area provided below.
    • -
    • Use image button for vision models, submitting or switching session clears same
    • +
    • Enter your query/response to ai assistant in user input area provided below.
    • +
        +
      • image btn for vision models, submitting / switching session clears same
      • +
    • Settings button allows current chat session's configuration to be updated.
    • -
    • Remember that each chat session has its own setting.
    • -
    • settings-tools-enabled should be true to enable tool calling.
      • -
      • If ai assistant requests a tool call, verify same before triggering.
      • +
      • Remember that each chat session has its own setting.
      • +
      +
    • settings-tools-enabled should be true for tool calling.
    • +
        +
      • if ai assistant requests a tool call, verify same before triggering.
      • submit tool response placed into user query/response text area
      • +
      • for web access inc search/pdf tool calls, run included simpleproxy.py
    • ContextWindow = [System, ${sRecentUserMsgCnt} User Query/Resp, Cur Query].
      • @@ -2157,7 +2166,7 @@ export class Config { export class Me { constructor() { - this.defaultChatIds = [ "Default", "Other", AI_TC_SESSIONNAME ]; + this.defaultChatIds = [ "Chat1", "Chat2", AI_TC_SESSIONNAME ]; this.defaultCfg = new Config() this.multiChat = new MultiChatUI(this); this.toolsMgr = new mTools.ToolsManager() From 32232e7c5bba36c44f981f2a5d13ff77d1145e9c Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 24 Nov 2025 14:43:50 +0530 Subject: [PATCH 357/365] SimpleChatTCRV:UIRefresh cleanup: Show only msgs in sliding window Ensure we are working with the Chat Messages in Chat session, which are in the currently active sliding window. Remove any chat message blocks in the chat session ui, which are no longer in the sliding window of context. This brings uirefresh semantic to be in sync with chat_show logic --- tools/server/public_simplechat/readme.md | 11 +++------ tools/server/public_simplechat/simplechat.js | 25 +++++++++++++++++--- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index a889645f6f9c6..14969f5508612 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -906,6 +906,9 @@ Chat Session specific settings tool call proxy server. * however any resultant changes to the available tool calls list wont get reflected, till one reloads the program. +* uirefresh helper ensures client side sliding window is always satisfied. + * now it remove messages no longer in the sliding window, so user only sees what is sent to the ai server, + in the chat session messages ui. #### ToDo @@ -933,14 +936,6 @@ session to the end user, but inturn not to be sent to the ai server. Ex current Updating system prompt, will reset user input area fully now, which seems a good enough behaviour, while keeping the code flow also simple and straight, do I need to change it, I dont think so as of now. -Should I have a toggle to control whether show only the messages sent to ai server based on sliding window -or show all the messages (ie even beyond the sliding window)? -* rather previously with chat_show only whats in current sliding window was being shown, but now with - the uirefresh based logic, all messages from last chat_show will be shown irrespective of whether still - in ai server handshake related sliding window or not. -* Update UIRefresh helper to optionally remove messages no longer in the sliding window, so user only sees - what is sent to the ai server in the chat session messages ui. - For now amn't bringing in mozilla/github/standard-entities pdf, md, mathslatex etal javascript libraries for their respective functionalities. diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 273f6d3e28319..6f7706ff85867 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -481,6 +481,9 @@ function usage_note(sRecentUserMsgCnt) {
      • ChatHistInCtxt, MaxTokens, ModelCtxt window to expand
    • ${AI_TC_SESSIONNAME} session keeps tool calls disabled, to avoid recursive...
    • +
        +
      • Used by external_ai tool call, which allows ai calling ai, as needed.
      • +
    `; return sUsageNote; @@ -1421,6 +1424,7 @@ class MultiChatUI { // Create main section let secMain = document.createElement('section') secMain.id = `cmuid${msg.uniqId}` + secMain.dataset.cmuid = String(msg.uniqId) secMain.classList.add(`role-${msg.ns.role}`) secMain.classList.add('chat-message') secMain.addEventListener('mouseenter', (ev)=>{ @@ -1510,7 +1514,7 @@ class MultiChatUI { } /** - * Refresh UI wrt given chatId, provided it matches the currently selected chatId + * Refresh UI (optionally bruteforce) wrt given chatId, provided it matches the currently selected chatId * * Show the chat contents in elDivChat. * Also update @@ -1522,6 +1526,8 @@ class MultiChatUI { * * option to load prev saved chat if any * * as well as settings/info. * + * Ensures only messages with in the currently set sliding window are shown + * * @param {string} chatId * @param {boolean} bClear * @param {boolean} bShowInfoAll @@ -1585,6 +1591,8 @@ class MultiChatUI { * If the houseKeeping.clear flag is set then fallback to * the brute force full on chat_show. * + * Also ensures only messages with in the currently set sliding window are shown + * * @param {string} chatId * @param {number} lastN */ @@ -1599,9 +1607,20 @@ class MultiChatUI { } this.ui_userinput_reset(false) this.ui_toolcallvalidated_as_needed(new ChatMessageEx()); + let chatToShow = chat.recent_chat(chat.cfg.chatProps.iRecentUserMsgCnt); + // Remove messages outside sliding window + /** @type {NodeListOf} */ + let elList = this.elDivChat.querySelectorAll('[id*="cmuid"]') + for (const el of elList) { + if (Number(el.dataset.cmuid) >= chatToShow[0].uniqId) { + break + } + el.remove() + } + // Refresh last few messages in the chat history, as requested by user for(let i=lastN; i > 0; i-=1) { - let msg = chat.xchat[chat.xchat.length-i] - let nextMsg = chat.xchat[chat.xchat.length-(i-1)] + let msg = chatToShow[chatToShow.length-i] + let nextMsg = chatToShow[chatToShow.length-(i-1)] if (msg) { this.chatmsg_ui_remove(msg.uniqId) this.show_message(this.elDivChat, chat.chatId, msg, (i-1), nextMsg) From d44e0022cd5f5d8843f18271c489d69daff53155 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 24 Nov 2025 15:54:18 +0530 Subject: [PATCH 358/365] SimpleChatTCRV:Cleanup Make a note on how user can always view the full chat history if they want to. --- tools/server/public_simplechat/readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 14969f5508612..3b6ac35ef43b9 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -909,6 +909,10 @@ Chat Session specific settings * uirefresh helper ensures client side sliding window is always satisfied. * now it remove messages no longer in the sliding window, so user only sees what is sent to the ai server, in the chat session messages ui. + * avoids adding additional control specifically for ui, and instead stick to the ai nw handshake related + chat sliding window size (which takes care of try avoid overloading the ai model context size) selected + by user already. User can always change the sliding window size to view past messages beyond the currently + active sliding window size and then switch back again, if they want to. #### ToDo From 489d0c29470c11df53cb31b2fba15d3d1ace0be9 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 24 Nov 2025 16:23:27 +0530 Subject: [PATCH 359/365] SimpleChatTCRV:Misc cleanup continues... --- tools/server/public_simplechat/readme.md | 12 ++++++++---- tools/server/public_simplechat/simplechat.js | 10 ++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 3b6ac35ef43b9..8c475c1599b40 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -455,7 +455,7 @@ always run this from a discardable vm, just in case if one wants to be extra cau The following tools/functions are currently provided by default -##### directly in browser +##### directly in and using browser capabilities * sys_date_time - provides the current date and time @@ -485,6 +485,8 @@ The following tools/functions are currently provided by default * or so and so and so ... * given the fuzzy nature of the generative ai, sometimes the model may even use this tool call to get answer to questions like what is your name ;> + * end user can use this mechanism to try and bring in an instance of ai running on a more powerful + machine, but then to be used only if needed or so Most of the above (except for external ai call) 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 @@ -935,7 +937,9 @@ if the models support same. It should also take care of some aspects of the tool potentially. MAYBE add a special ClientSideOnly role for use wrt Chat history to maintain things to be shown in a chat -session to the end user, but inturn not to be sent to the ai server. Ex current settings or so ... +session to the end user, but inturn not to be sent to the ai server. Ex current settings, any edits to toolcall, +any tool call or server handshake errors seen (which user might have worked around as needed and continued the +conversation) or so ... Updating system prompt, will reset user input area fully now, which seems a good enough behaviour, while keeping the code flow also simple and straight, do I need to change it, I dont think so as of now. @@ -963,9 +967,9 @@ in devel console of the browser * remove the assistant response from end of chat session, if any, using * document['gMe'].multiChat.simpleChats['SessionId'].xchat.pop() * [202511] One can even use the del button in the popover menu wrt each chat message to delete - * reset role of Tool response chat message to TOOL.TEMP from tool + * 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" + * document['gMe'].multiChat.simpleChats['SessionId'].xchat[toolMessageIndex].role = "TOOL-TEMP" * if you dont mind running the tool call again, just deleting the tool response message will also do * 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 diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 6f7706ff85867..3abc6bd27da69 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -33,9 +33,15 @@ export class Roles { /** Used to identify tool call response, which has not yet been accepted and submitted by users */ static ToolTemp = `TOOL${ROLES_TEMP_ENDSWITH}`; + + /** Could be used to maintain modified tool calls related info or errors or ... */ + static ExtraTemp = `EXTRA${ROLES_TEMP_ENDSWITH}`; + /** - * Used to maintain errors that wont be normally communicated back to ai server - * like error got during handshake with ai server or so. + * One could either use ExtraTemp Role to maintain the errors seen or maybe one could + * use this to maintain errors that wont be normally communicated back to ai server + * like error got during handshake with ai server or so, which in turn the user might + * work around by restarting the server with different / updated configuration or ... */ static ErrorTemp = `ERROR${ROLES_TEMP_ENDSWITH}`; } From 6af6b889adacf917a963e13acaf37f3d8e572b76 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 24 Nov 2025 19:23:44 +0530 Subject: [PATCH 360/365] SimpleChatTCRV: Documentation cleanup Move existing readme.md into docs/details.md Similarly move the screenshot image into docs/ --- .../{readme.md => docs/details.md} | 0 .../{ => docs}/simplechat_screens.webp | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename tools/server/public_simplechat/{readme.md => docs/details.md} (100%) rename tools/server/public_simplechat/{ => docs}/simplechat_screens.webp (100%) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/docs/details.md similarity index 100% rename from tools/server/public_simplechat/readme.md rename to tools/server/public_simplechat/docs/details.md diff --git a/tools/server/public_simplechat/simplechat_screens.webp b/tools/server/public_simplechat/docs/simplechat_screens.webp similarity index 100% rename from tools/server/public_simplechat/simplechat_screens.webp rename to tools/server/public_simplechat/docs/simplechat_screens.webp From 468b62fa62ac8afbc15a919cfac67f3a256534e4 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 24 Nov 2025 19:57:10 +0530 Subject: [PATCH 361/365] SimpleChatTCRV: split changelog from details into separate file --- .../public_simplechat/docs/changelog.md | 330 ++++++++++++++++++ .../server/public_simplechat/docs/details.md | 329 ----------------- 2 files changed, 330 insertions(+), 329 deletions(-) create mode 100644 tools/server/public_simplechat/docs/changelog.md diff --git a/tools/server/public_simplechat/docs/changelog.md b/tools/server/public_simplechat/docs/changelog.md new file mode 100644 index 0000000000000..16d0a9dbc287c --- /dev/null +++ b/tools/server/public_simplechat/docs/changelog.md @@ -0,0 +1,330 @@ +# Progress + +Look into source files and git logs for the details, this is a partial changelog of stuff already done +and some of the things that one may look at in the future. + +## 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. + +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. + +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. + +Trap http response errors and inform user the specific error returned by ai server. + +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. + +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 +* 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 + +* 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. + +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. + +sys_date_time tool call has been added. + +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. + +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. + +Add support for fetch_xml_as_text tool call, fix importmaps in index.html + +Renamed and updated logic wrt xml fetching to be fetch_xml_filtered. allow one to use re to identify +the tags to be filtered in a fine grained manner including filtering based on tag heirarchy +* avoid showing empty skipped tag blocks + +Logic which shows the generated tool call has been updated to trap errors when parsing the function call +arguments generated by the ai. This ensures that the chat ui itself doesnt get stuck in it. Instead now +the tool call response can inform the ai model that its function call had issues. + +Renamed fetch_web_url_text to fetch_html_text, so that gen ai model wont try to use this to fetch xml or +rss files, because it will return empty content, because there wont be any html content to strip the tags +and unwanted blocks before returning. + +Capture the body of ai server not ok responses, to help debug as well as to show same to user. + +Extract and include the outline of titles (along with calculated numbering) in the text output of pdftext +* ensure that one doesnt recurse beyond a predefined limit. + +Convert NSChatMessage from typedef to Class and update ChatMessageEx, SimpleChat, MultiChatUI classes to +make use of the same. +* helpers consolidated + * helpers to check if given instance contains reasoning or content or toolcall or tool response related + fields/info in them. + * helpers to get the corresponding field values + * some of these helpers where in ChatMessageEx and beyond before +* now the network handshaked fields are declared as undefined by default (instead of empty values). + this ensures that json stringify will automatically discard fields whose values are still undefined. +* add fields wrt tool response and update full flow to directly work with these fields instead of the + xml based serialisation which was previously used for maintaining the different tool response fields + within the content field (and inturn extract from there when sending to server). + * now a dataset based attribute is used to identify when input element contains user input and when + it contains tool call result/response. +* this simplifies the flow wrt showing chat message (also make it appear more cleanly) as well as + identifying not yet accepted tool result and showing in user query input field and related things. +* ALERT: ON-DISK-STORAGE structure of chat sessions have changed wrt tool responses. So old saves will + no longer work wrt tool responses + +UI updates +* update logic to allow empty tool results to be sent to ai engine server +* css - when user input textarea is in tool result mode (ie wrt TOOL.TEMP role), change the background + color to match the tool role chat message block color, so that user can easily know that the input + area is being used for submitting tool response or user response, at any given moment in time. + +Vision +* Add image_url field. Allow user to load image, which is inturn stored as a dataURL in image_url. +* when user presses submit with a message, if there is some content (image for now) in dataURL, + then initialise image_url field with same. +* when generating chat messages for ai server network handshake, create the mixed content type of + content field which includes both the text (from content field) and image (from image_url field) + ie if a image_url is found wrt a image. + * follow the openai format/template wrt these mixed content messages. +* Usage: specify a mmproj file directly or through -hf, additionally had to set --batch-size to 8k + and ubatch-size to 2k wrt gemma3-4b-it +* when showing chat instantiate img elements to show image_urls. + * limit horizontally to max width and vertically to 20% of the height +* show any image loaded by the user, in the corresponding image button +* consolidate dataurl handling into a bunch of helper functions. +* trap quota errors wrt localStorage etal +* dont forget to reset the file type input's value, so that reselecting the same image still + triggers the input's change event. + +SimpleChat class now allows extra fields to be specified while adding, in a generic way using a +object/literal object or equivalent. + +UI Cleanup - msgs spaced out, toolcall edit hr not always, scroll ui only when required, +hide settings/info till user requests, heading gradient + +iDB module +* add open, transact, put and get. Use for chat session save and load +* getKeys used to show Restore/Load button wrt chat sessions. + +ChatMessage +* assign a globally unique (ie across sessions) id to each chat message instance. +* add support for deleting chat message based on its uniquie id in SimpleChat. + * try ensure that adjacent messages remain on screen, after a message is deleted from session. +* add a popover div block in html, which acts as a popup menu containing buttons to work with + individual chat messages. + * experiment and finalise on anchor based relative positioning of the popover menu. + * have a del button, which allows one to delete the currently in focus chat message. + * have a copy button, which allows one to copy the textual content into system clipboard. + +MultiChatUI +* chat_show takes care of showing or clearing tool call edit / trigger as well as tool response + edit / submit. Also show the currently active tool call and its response before it is submitted + was previously only shown in the edit / trigger and edit / submit ui elements, now instead it + also shows as part of the chat session message blocks, so that user can delete or copy these + if needed using the same mechanism as other messages in the chat session. +* use a delete msg helper, which takes care of deleting the msg from chat session as well as + efficiently update ui to any extent by removing the corresponding element directly from existing + chat session ui without recreating the full chat session ui. +* a helper to add a message into specified chat session, as well as show/update in the chat session + ui by appending the chat message, instead of recreating the full chat session ui. +... + +MultiChatUI+ +* both chat_show and chat_uirefresh (if lastN >= 2) both take care of updating tool call edit/trigger + as well as the tool call response edit/submit related ui elements suitably. + * chat_show recreates currently active sliding window of chat session (which could even be full) + * while chat_uirefresh recreates/updates ui only for the lastN messages (prefer in general, as optimal) +* normal user response / query submit as well as tool call response or error submit have been updated + to use the optimal uirefresh logic now. + +Cleanup in general +* Inform end user when loading from a saved session. +* Update starting entry point flow to avoid calling chat_show twice indirectly, inturn leading to + two restore previously saved session blocks. Rather when adding tool calls support, and inturn + had to account for delayed collating of available simpleproxy based tool calls, I forgot to clean + this flow up. +* Make the sys_date_time template description bit more verbose, just in case. +* ui_userinput_reset now also resets associated Role always, inturn + * full on version from chat_show, inturn when session switching. + So user switchs session will reset all user input area and related data, while + also ensuring user input area has the right needed associated role setup. + * partial version from uirefresh, inturn adding user or tool call response messages. +* ui cleanup + * more rounded buttons, chat messages and input area elements. + * make the body very very lightly gray in color, while the user input area is made whiter. + * gradients wrt heading, role-specific individual chat message blocks. + * avoid borders and instead give a box effect through light shadows. + * also avoid allround border around chat message role block and instead have to only one side. + * timeout close popover menu. + * usage notes + * update wrt vision and toggling of sessions and system prompt through main title area. + * fix issue with sliding window size not reflecting properly in context window entry. + * make restore block into details based block, and anchor its position independent of db check. + * avoid unneeded outer overall scrollbar by adjusting fullbody height in screen mode. + * user css variable to define the overall background color and inturn use same to merge gradients + to the background, as well as to help switch the same seemlessly between screen and print modes. + * make the scrollbars more subtle and in the background. + * allow user input textarea to grow vertically to some extent. + * make things rounded across board by default. add some padding to toolcall details block, ... + * use icons without text wrt chat sessions++, new chat, clear chat and settings top level buttons. + * use title property/attribute to give a hint to the user about the button functionality. + * add role specific background gradients wrt the tool call trigger and user input block as well as + fix wrt the tool temp message block. also wrt system input block at top. + * also rename the TEMP role tags to use -TEMP instead of .TEMP, so that CSS rule selectors will + treat such tags like role-TOOL-TEMP as say a proper css class name rather than messing up with + something like role-TOOL.TEMP which will get split to role-TOOL and TEMP and inturn corresponding + css rule doesnt/wont get applied. + * given that now there is a proper visual cue based seperation of the tool call trigger block from + surrounding content, using proper seperate tool call specific coloring, so remove the
    horiz + line seperation wrt tool call trigger block. + * however retain the horizontal line seperation between the tool trigger block and user input block, + given that some users and some ai dont seem to identify the difference very easily. + * work around firefox currently not yet supporting anchor based relative positioning of popover. + * ensure the new uirefresh flow handles the following situations in a clean way like + * a new chat session clearing out usagenote+restore+currentconfig, as user starts chatting + * the settings ui getting cleared out as user starts/continues chatting directly into user input + without using chat session button to switch back to the chat. +* Auto ObjPropsEdit UI + * allow it to be themed by assigning id to top level block. + * fix a oversight (forgotten $) with use of templated literals and having variables in them. + * ensure full props hierarchy is accounted for when setting the id of elements. +* Chat button to toggle sessions buttons and system prompt. +* Use unix date format markers wrt sys_date_time toolcall, also add w (day of week). +* Free up the useful vertical space by merging chat sessions buttons/tabs into heading +* Allow user to load multiple images and submit to ai as part of a single user message. +* Use popover ui to allow user to view larger versions of loaded images as well as remove before submitting + to ai, if and when needed. +* Add external_ai toolcall with no access to internet or tool calls (helps avoid recursive ai tool calling). + User can see response generated by the external ai tool call, as and when it is recieved. +* Maintain chat session specific DivStream elements, and show live ai responses (through corresponding + DivStream) wrt the current chat session as well as any from the external ai tool call session. + In future, if the logic is updated to allow switching chat session ui in the middle of a pending tool call + or pending ai server response, things wont mess up ui, as they will be updating their respective DivStream. + Also switching sessions takes care of showing the right DivStream ie of currently switched to chat, so that + end user can see the streamed response from that chat session as it is occuring. +* Cleanup the tool call descriptions and verbose messages returned a bit. + +Chat Session specific settings +* Needed so that one could + * setup a different ai model / engine as the external ai backend. + * interact with different independent ai models / engines / parallel instances in general +* Move needed configs from Me into a seperate Config class. + * also move ShowSettings, ShowInfo etal into Config class +* SimpleChat maintains an instance of Config class instead of Me. +* ToolsManager and the different tool call modules have been updated to + * have seperate init and setup calls. + * init is called at the begining + * setup will be called when ever a chat session is being created + and or in future when ever any config of interest changes. + * pick needed config etal from the specified chatId's config and not any global config. +* Starting flow updated to chain the different logical blocks of code + * first allow tools manager to be initd + * next create the needed default set of sessions, while parallely calling tool manager setup as needed. + * ensures that the available list of tool calls match the config of the chat session involved. + Needed as user could change tools related proxy server url. + * next setup the main ui as needed. +* Hide user-input area and tool call validate/trigger area when switching into settings and ensure they + get unhidden when returning back, as needed. +* Save and restore ChatSession config entries, as needed, in localStorage. + * load previously saved config if any, when creating ChatSession + * when ever switching, including into a, ChatSession, Configs of all chat sessions are saved. +* ALERT: If a chat session's tools proxyUrl is changed + * the same will be picked up immidiately wrt all subsequent tool calls which depend on the + tool call proxy server. + * however any resultant changes to the available tool calls list wont get reflected, + till one reloads the program. +* uirefresh helper ensures client side sliding window is always satisfied. + * now it remove messages no longer in the sliding window, so user only sees what is sent to the ai server, + in the chat session messages ui. + * avoids adding additional control specifically for ui, and instead stick to the ai nw handshake related + chat sliding window size (which takes care of try avoid overloading the ai model context size) selected + by user already. User can always change the sliding window size to view past messages beyond the currently + active sliding window size and then switch back again, if they want to. + + +## ToDo + +Is the tool call promise land trap deep enough, need to think through and explore around this once later. + +Add fetch_rss and may be different document formats processing related tool calling, in turn through +the simpleproxy.py if and where needed. + +* Using xmlfiltered and tagDropREs of + * ["^rss:channel:item:(?!title).+$"] one can fetch and extract out all the titles. + * ["^rss:channel:item:(?!title|link|description).+$"] one can fetch and extract out all the + titles along with corresponding links and descriptions + * rather with some minimal proding and guidance gpt-oss generated this to use xmlfiltered to read rss + +Add a option/button to reset the chat session config, to defaults. + +Have a seperate helper to show the user input area, based on set state. And have support for multiple images +if the models support same. It should also take care of some aspects of the tool call response edit / submit, +potentially. + +MAYBE add a special ClientSideOnly role for use wrt Chat history to maintain things to be shown in a chat +session to the end user, but inturn not to be sent to the ai server. Ex current settings, any edits to toolcall, +any tool call or server handshake errors seen (which user might have worked around as needed and continued the +conversation) or so ... + +Updating system prompt, will reset user input area fully now, which seems a good enough behaviour, while +keeping the code flow also simple and straight, do I need to change it, I dont think so as of now. + +For now amn't bringing in mozilla/github/standard-entities pdf, md, mathslatex etal javascript libraries for +their respective functionalities. + +Add support for base64 encoded pdf passing to ai models, when the models and llama engine gain that capability +in turn using openai file - file-data type sub block within content array or so ... diff --git a/tools/server/public_simplechat/docs/details.md b/tools/server/public_simplechat/docs/details.md index 8c475c1599b40..421264b75ac80 100644 --- a/tools/server/public_simplechat/docs/details.md +++ b/tools/server/public_simplechat/docs/details.md @@ -622,335 +622,6 @@ 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. -### 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. - -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. - -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. - -Trap http response errors and inform user the specific error returned by ai server. - -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. - -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 -* 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 - -* 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. - -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. - -sys_date_time tool call has been added. - -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. - -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. - -Add support for fetch_xml_as_text tool call, fix importmaps in index.html - -Renamed and updated logic wrt xml fetching to be fetch_xml_filtered. allow one to use re to identify -the tags to be filtered in a fine grained manner including filtering based on tag heirarchy -* avoid showing empty skipped tag blocks - -Logic which shows the generated tool call has been updated to trap errors when parsing the function call -arguments generated by the ai. This ensures that the chat ui itself doesnt get stuck in it. Instead now -the tool call response can inform the ai model that its function call had issues. - -Renamed fetch_web_url_text to fetch_html_text, so that gen ai model wont try to use this to fetch xml or -rss files, because it will return empty content, because there wont be any html content to strip the tags -and unwanted blocks before returning. - -Capture the body of ai server not ok responses, to help debug as well as to show same to user. - -Extract and include the outline of titles (along with calculated numbering) in the text output of pdftext -* ensure that one doesnt recurse beyond a predefined limit. - -Convert NSChatMessage from typedef to Class and update ChatMessageEx, SimpleChat, MultiChatUI classes to -make use of the same. -* helpers consolidated - * helpers to check if given instance contains reasoning or content or toolcall or tool response related - fields/info in them. - * helpers to get the corresponding field values - * some of these helpers where in ChatMessageEx and beyond before -* now the network handshaked fields are declared as undefined by default (instead of empty values). - this ensures that json stringify will automatically discard fields whose values are still undefined. -* add fields wrt tool response and update full flow to directly work with these fields instead of the - xml based serialisation which was previously used for maintaining the different tool response fields - within the content field (and inturn extract from there when sending to server). - * now a dataset based attribute is used to identify when input element contains user input and when - it contains tool call result/response. -* this simplifies the flow wrt showing chat message (also make it appear more cleanly) as well as - identifying not yet accepted tool result and showing in user query input field and related things. -* ALERT: ON-DISK-STORAGE structure of chat sessions have changed wrt tool responses. So old saves will - no longer work wrt tool responses - -UI updates -* update logic to allow empty tool results to be sent to ai engine server -* css - when user input textarea is in tool result mode (ie wrt TOOL.TEMP role), change the background - color to match the tool role chat message block color, so that user can easily know that the input - area is being used for submitting tool response or user response, at any given moment in time. - -Vision -* Add image_url field. Allow user to load image, which is inturn stored as a dataURL in image_url. -* when user presses submit with a message, if there is some content (image for now) in dataURL, - then initialise image_url field with same. -* when generating chat messages for ai server network handshake, create the mixed content type of - content field which includes both the text (from content field) and image (from image_url field) - ie if a image_url is found wrt a image. - * follow the openai format/template wrt these mixed content messages. -* Usage: specify a mmproj file directly or through -hf, additionally had to set --batch-size to 8k - and ubatch-size to 2k wrt gemma3-4b-it -* when showing chat instantiate img elements to show image_urls. - * limit horizontally to max width and vertically to 20% of the height -* show any image loaded by the user, in the corresponding image button -* consolidate dataurl handling into a bunch of helper functions. -* trap quota errors wrt localStorage etal -* dont forget to reset the file type input's value, so that reselecting the same image still - triggers the input's change event. - -SimpleChat class now allows extra fields to be specified while adding, in a generic way using a -object/literal object or equivalent. - -UI Cleanup - msgs spaced out, toolcall edit hr not always, scroll ui only when required, -hide settings/info till user requests, heading gradient - -iDB module -* add open, transact, put and get. Use for chat session save and load -* getKeys used to show Restore/Load button wrt chat sessions. - -ChatMessage -* assign a globally unique (ie across sessions) id to each chat message instance. -* add support for deleting chat message based on its uniquie id in SimpleChat. - * try ensure that adjacent messages remain on screen, after a message is deleted from session. -* add a popover div block in html, which acts as a popup menu containing buttons to work with - individual chat messages. - * experiment and finalise on anchor based relative positioning of the popover menu. - * have a del button, which allows one to delete the currently in focus chat message. - * have a copy button, which allows one to copy the textual content into system clipboard. - -MultiChatUI -* chat_show takes care of showing or clearing tool call edit / trigger as well as tool response - edit / submit. Also show the currently active tool call and its response before it is submitted - was previously only shown in the edit / trigger and edit / submit ui elements, now instead it - also shows as part of the chat session message blocks, so that user can delete or copy these - if needed using the same mechanism as other messages in the chat session. -* use a delete msg helper, which takes care of deleting the msg from chat session as well as - efficiently update ui to any extent by removing the corresponding element directly from existing - chat session ui without recreating the full chat session ui. -* a helper to add a message into specified chat session, as well as show/update in the chat session - ui by appending the chat message, instead of recreating the full chat session ui. -... - -MultiChatUI+ -* both chat_show and chat_uirefresh (if lastN >= 2) both take care of updating tool call edit/trigger - as well as the tool call response edit/submit related ui elements suitably. - * chat_show recreates currently active sliding window of chat session (which could even be full) - * while chat_uirefresh recreates/updates ui only for the lastN messages (prefer in general, as optimal) -* normal user response / query submit as well as tool call response or error submit have been updated - to use the optimal uirefresh logic now. - -Cleanup in general -* Inform end user when loading from a saved session. -* Update starting entry point flow to avoid calling chat_show twice indirectly, inturn leading to - two restore previously saved session blocks. Rather when adding tool calls support, and inturn - had to account for delayed collating of available simpleproxy based tool calls, I forgot to clean - this flow up. -* Make the sys_date_time template description bit more verbose, just in case. -* ui_userinput_reset now also resets associated Role always, inturn - * full on version from chat_show, inturn when session switching. - So user switchs session will reset all user input area and related data, while - also ensuring user input area has the right needed associated role setup. - * partial version from uirefresh, inturn adding user or tool call response messages. -* ui cleanup - * more rounded buttons, chat messages and input area elements. - * make the body very very lightly gray in color, while the user input area is made whiter. - * gradients wrt heading, role-specific individual chat message blocks. - * avoid borders and instead give a box effect through light shadows. - * also avoid allround border around chat message role block and instead have to only one side. - * timeout close popover menu. - * usage notes - * update wrt vision and toggling of sessions and system prompt through main title area. - * fix issue with sliding window size not reflecting properly in context window entry. - * make restore block into details based block, and anchor its position independent of db check. - * avoid unneeded outer overall scrollbar by adjusting fullbody height in screen mode. - * user css variable to define the overall background color and inturn use same to merge gradients - to the background, as well as to help switch the same seemlessly between screen and print modes. - * make the scrollbars more subtle and in the background. - * allow user input textarea to grow vertically to some extent. - * make things rounded across board by default. add some padding to toolcall details block, ... - * use icons without text wrt chat sessions++, new chat, clear chat and settings top level buttons. - * use title property/attribute to give a hint to the user about the button functionality. - * add role specific background gradients wrt the tool call trigger and user input block as well as - fix wrt the tool temp message block. also wrt system input block at top. - * also rename the TEMP role tags to use -TEMP instead of .TEMP, so that CSS rule selectors will - treat such tags like role-TOOL-TEMP as say a proper css class name rather than messing up with - something like role-TOOL.TEMP which will get split to role-TOOL and TEMP and inturn corresponding - css rule doesnt/wont get applied. - * given that now there is a proper visual cue based seperation of the tool call trigger block from - surrounding content, using proper seperate tool call specific coloring, so remove the
    horiz - line seperation wrt tool call trigger block. - * however retain the horizontal line seperation between the tool trigger block and user input block, - given that some users and some ai dont seem to identify the difference very easily. - * work around firefox currently not yet supporting anchor based relative positioning of popover. - * ensure the new uirefresh flow handles the following situations in a clean way like - * a new chat session clearing out usagenote+restore+currentconfig, as user starts chatting - * the settings ui getting cleared out as user starts/continues chatting directly into user input - without using chat session button to switch back to the chat. -* Auto ObjPropsEdit UI - * allow it to be themed by assigning id to top level block. - * fix a oversight (forgotten $) with use of templated literals and having variables in them. - * ensure full props hierarchy is accounted for when setting the id of elements. -* Chat button to toggle sessions buttons and system prompt. -* Use unix date format markers wrt sys_date_time toolcall, also add w (day of week). -* Free up the useful vertical space by merging chat sessions buttons/tabs into heading -* Allow user to load multiple images and submit to ai as part of a single user message. -* Use popover ui to allow user to view larger versions of loaded images as well as remove before submitting - to ai, if and when needed. -* Add external_ai toolcall with no access to internet or tool calls (helps avoid recursive ai tool calling). - User can see response generated by the external ai tool call, as and when it is recieved. -* Maintain chat session specific DivStream elements, and show live ai responses (through corresponding - DivStream) wrt the current chat session as well as any from the external ai tool call session. - In future, if the logic is updated to allow switching chat session ui in the middle of a pending tool call - or pending ai server response, things wont mess up ui, as they will be updating their respective DivStream. - Also switching sessions takes care of showing the right DivStream ie of currently switched to chat, so that - end user can see the streamed response from that chat session as it is occuring. -* Cleanup the tool call descriptions and verbose messages returned a bit. - -Chat Session specific settings -* Needed so that one could - * setup a different ai model / engine as the external ai backend. - * interact with different independent ai models / engines / parallel instances in general -* Move needed configs from Me into a seperate Config class. - * also move ShowSettings, ShowInfo etal into Config class -* SimpleChat maintains an instance of Config class instead of Me. -* ToolsManager and the different tool call modules have been updated to - * have seperate init and setup calls. - * init is called at the begining - * setup will be called when ever a chat session is being created - and or in future when ever any config of interest changes. - * pick needed config etal from the specified chatId's config and not any global config. -* Starting flow updated to chain the different logical blocks of code - * first allow tools manager to be initd - * next create the needed default set of sessions, while parallely calling tool manager setup as needed. - * ensures that the available list of tool calls match the config of the chat session involved. - Needed as user could change tools related proxy server url. - * next setup the main ui as needed. -* Hide user-input area and tool call validate/trigger area when switching into settings and ensure they - get unhidden when returning back, as needed. -* Save and restore ChatSession config entries, as needed, in localStorage. - * load previously saved config if any, when creating ChatSession - * when ever switching, including into a, ChatSession, Configs of all chat sessions are saved. -* ALERT: If a chat session's tools proxyUrl is changed - * the same will be picked up immidiately wrt all subsequent tool calls which depend on the - tool call proxy server. - * however any resultant changes to the available tool calls list wont get reflected, - till one reloads the program. -* uirefresh helper ensures client side sliding window is always satisfied. - * now it remove messages no longer in the sliding window, so user only sees what is sent to the ai server, - in the chat session messages ui. - * avoids adding additional control specifically for ui, and instead stick to the ai nw handshake related - chat sliding window size (which takes care of try avoid overloading the ai model context size) selected - by user already. User can always change the sliding window size to view past messages beyond the currently - active sliding window size and then switch back again, if they want to. - - -#### ToDo - -Is the tool call promise land trap deep enough, need to think through and explore around this once later. - -Add fetch_rss and may be different document formats processing related tool calling, in turn through -the simpleproxy.py if and where needed. - -* Using xmlfiltered and tagDropREs of - * ["^rss:channel:item:(?!title).+$"] one can fetch and extract out all the titles. - * ["^rss:channel:item:(?!title|link|description).+$"] one can fetch and extract out all the - titles along with corresponding links and descriptions - * rather with some minimal proding and guidance gpt-oss generated this to use xmlfiltered to read rss - -Add a option/button to reset the chat session config, to defaults. - -Have a seperate helper to show the user input area, based on set state. And have support for multiple images -if the models support same. It should also take care of some aspects of the tool call response edit / submit, -potentially. - -MAYBE add a special ClientSideOnly role for use wrt Chat history to maintain things to be shown in a chat -session to the end user, but inturn not to be sent to the ai server. Ex current settings, any edits to toolcall, -any tool call or server handshake errors seen (which user might have worked around as needed and continued the -conversation) or so ... - -Updating system prompt, will reset user input area fully now, which seems a good enough behaviour, while -keeping the code flow also simple and straight, do I need to change it, I dont think so as of now. - -For now amn't bringing in mozilla/github/standard-entities pdf, md, mathslatex etal javascript libraries for -their respective functionalities. - -Add support for base64 encoded pdf passing to ai models, when the models and llama engine gain that capability -in turn using openai file - file-data type sub block within content array or so ... - - ### Debuging the handshake and beyond When working with llama.cpp server based GenAi/LLM running locally, to look at the handshake directly From 893406d023ac70b82ec9d7e3b4957cbb1e552555 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 24 Nov 2025 19:48:53 +0530 Subject: [PATCH 362/365] SimpleChatTCRV: Add simple readme in place of detailed one Gives quick overview of the features, given that the original readme (now docs/details.md++) got created over a period of disjoined time as features got added. --- .../public_simplechat/docs/changelog.md | 2 + tools/server/public_simplechat/readme.md | 180 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 tools/server/public_simplechat/readme.md diff --git a/tools/server/public_simplechat/docs/changelog.md b/tools/server/public_simplechat/docs/changelog.md index 16d0a9dbc287c..4456698c97121 100644 --- a/tools/server/public_simplechat/docs/changelog.md +++ b/tools/server/public_simplechat/docs/changelog.md @@ -1,5 +1,7 @@ # Progress +by Humans for All. + Look into source files and git logs for the details, this is a partial changelog of stuff already done and some of the things that one may look at in the future. diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md new file mode 100644 index 0000000000000..9e17fbb43f918 --- /dev/null +++ b/tools/server/public_simplechat/readme.md @@ -0,0 +1,180 @@ +# SimpleChat / AnveshikaSallap + +by Humans for All. + +## Quickstart + +### Server + +From the root directory of llama.cpp source code repo containing build / tools / ... sub directories + +Start ai engine / server using + +```bash +build/bin/llama-server -m \ + --path tools/server/public_simplechat --jinja +``` + +- `--jinja` enables tool‑calling support +- `--mmproj ` enables vision support +- `--port ` use if a custom port is needed + +If one needs web related access / tool calls dont forget to run + +```bash +cd tools/server/public_simplechat/local.tools; python3 ./simpleproxy.py --config simpleproxy.json +``` + +- `--debug True` enables debug mode which captures internet handshake data. + +### Client + +1. Open `http://127.0.0.1:PORT/index.html` in a browser + +2. Select / Create a chat session + - set a suitable system prompt, if needed + - modify **settings**, if needed + - **Restore** loads last autosaved session with same name + +3. Enter the query, press **Enter** + - use **Shift‑Enter** for newline + - include images if required (ai vision models) + +4. View any streamed ai response (if enabled and supported) + +5. If a tool call is requested + - verify / edit the tool call details before triggering the same + - one can even ask ai to rethink on the tool call requested, + by sending a appropriate user response instead of a tool call response. + - tool call is executed using browser's web worker or simpleproxy + - tool call response is placed in user input area (with color coding) + - verify / edit the tool call response, before submit same back to ai + - tool response initially assigned `TOOL-TEMP` role, promoted to `TOOL` upon submit + - based on got response, if needed one can rerun tool call with modified arguments + - *at any time there can be one pending tool call wrt a chat session* + +6. **Delete / Copy** available via popover menu for each message + +7. **Clear** / **+ New** chat with provided buttons, as needed + + +## Overview + +A lightweight ai chat client with a web front-end that supports multiple chat sessions, vision, reasoning and tool calling. + +- Supports multiple independent chat sessions with + - One‑shot or streamed (default) responses + - Custom settings and system prompts per session + - Automatic local autosave (restorable on next load) + - can handshake with `/completions` or `/chat/completions` (default) endpoints + +- Supports peeking at model's reasoning live + - if model streams the same and + - streaming mode is enabled in settings (default) + +- Supports vision / image / multimodal ai models + - attach image files as part of user chat messages + - handshaked as `image_url`s in chat message content array along with text + - supports multi-image uploads per message + - images displayed inline in the chat history + - specify `mmproj` file via `-mmproj` or using `-hf` + - specify `-batch-size` and `-ubatch-size` if needed + +- Built-in support for GenAI models that expose tool calling + + - includes a bunch of useful builtin tool calls, without needing any additional setup + + - direct browser based tool calls include + - `sys_date_time`, `simple_calculator`, `run_javascript_function_code`, `data_store_*`, `external_ai` + - except for external_ai, these are run from within a web worker context to isolate main context from them + - data_store brings in browser IndexedDB based persistant key/value storage across sessions + + - along with included python based simpleproxy.py + - `search_web_text`, `fetch_web_url_raw`, `fetch_html_text`, `fetch_pdf_as_text`, `fetch_xml_filtered` + - these built‑in tool calls (via SimpleProxy) help fetch PDFs, HTML, XML or perform web search + - PDF tool also returns an outline with numbering + - result is truncated to `iResultMaxDataLength` (default 128 kB) + - helps isolate these functionality into a separate vm running locally or otherwise, if needed + - supports whitelisting of `allowed.schemes` and `allowed.domains` through `simpleproxy.json` + - supports a bearer token shared between server and client for auth + - needs https support, for better security wrt this flow, avoided now given mostly local use + + - follows a safety first design and lets the user + - verify and optionally edit the tool call requests, before executing the same + - verify and optionally edit the tool call response, before submitting the same + - user can update the settings for auto executing these actions, if needed + + - external_ai allows invoking a separate fresh ai instance + - ai could run self modified targeted versions of itself/... with custom system prompts and user messages as needed + - user can bring in an ai instance with additional compute access, which should be used only if needed + - tool calling is currently kept disabled in such a instance + +- Client side Sliding window Context control, using `iRecentUserMsgCnt`, helps limit context sent to ai model + +- Optional auto trimming of trailing garbage from model outputs + +- Follows responsive design to try adapt to any screen size + +- built using plain html + css + javascript and python + - no additional dependencies that one needs to keep track of + - except for pypdf, if pdf support needed. automaticaly drops pdf tool call if pypdf missing + - fits within ~260KB even in uncompressed source form (including simpleproxy.py) + - easily extend with additional tool calls using either javascript or python, for additional functionality + +Start exploring / experimenting with your favorite ai models and thier capabilities. + + +## Configuration / Settings + +One can modify the session configuration using Settings UI. All the settings and more are also exposed in the browser console via `document['gMe']`. + +### Settings Groups + +| Group | Purpose | +|---------|---------| +| `chatProps` | ApiEndpoint, streaming, sliding window, ... | +| `tools` | `enabled`, `proxyUrl`, `proxyAuthInsecure`, search URL/template & drop rules, max data length, timeouts | +| `apiRequestOptions` | `temperature`, `max_tokens`, `frequency_penalty`, `presence_penalty`, `cache_prompt`, ... | +| `headers` | `Content-Type`, `Authorization`, ... | + +### Some specific settings + +- **Ai Server** (`baseURL`) + - ai server (llama-server) address + - default is `http://127.0.0.1:PORT` +- **Stream** (`stream`) + - `true` for live streaming, `false` for oneshot +- **Client side Sliding Window** (`iRecentUserMsgCnt`) + - `-1` : send full history + - `0` : only system prompt + - `>0` : last N user messages after the most recent system prompt +- **Cache Prompt** (`cache_prompt`) + - enables server‑side caching of system prompt and history +- **Tool Call Timeout** (`toolCallResponseTimeoutMS`) + - 200s by default +- **Tool call Auto** (`autoSecs`) + - seconds to wait before auto-triggering tool calls and auto-submitting tool responses + - default is 0 ie manual +- **Trim Garbage** (`bTrimGarbage`) + - Removes repeated trailing text + + +## Debugging Tips + +- **Local TCPdump** + - `sudo tcpdump -i lo -s 0 -vvv -A host 127.0.0.1 and port 8080` +- **Browser DevTools** + - inspect `document['gMe']` for session state +- **Reset Tool Call** + - delete any assistant response after the tool call handshake + - next wrt the last tool message + - set role back to `TOOL-TEMP` + - edit the response as needed + - delete the same + - user will be given option to edit and retrigger the tool call + - submit the new response + + +## At the end + +A thank you to all open source and open model developers, who strive for the common good. From cf085923d1dadde61a5021ae47df682e8502b219 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 24 Nov 2025 21:09:02 +0530 Subject: [PATCH 363/365] SimpleChatTCRV: Update/Cleanup the new readme --- tools/server/public_simplechat/readme.md | 59 ++++++++++++++---------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index 9e17fbb43f918..b789ab396f9b0 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -2,6 +2,9 @@ by Humans for All. +A lightweight simple minded ai chat client with a web front-end that supports multiple chat sessions, vision, reasoning and tool calling. + + ## Quickstart ### Server @@ -18,6 +21,7 @@ build/bin/llama-server -m \ - `--jinja` enables tool‑calling support - `--mmproj ` enables vision support - `--port ` use if a custom port is needed + - default is 8080 wrt llama-server If one needs web related access / tool calls dont forget to run @@ -25,18 +29,20 @@ If one needs web related access / tool calls dont forget to run cd tools/server/public_simplechat/local.tools; python3 ./simpleproxy.py --config simpleproxy.json ``` -- `--debug True` enables debug mode which captures internet handshake data. +- `--debug True` enables debug mode which captures internet handshake data +- port defaults to 3128, can be changed from simpleproxy.json, if needed ### Client -1. Open `http://127.0.0.1:PORT/index.html` in a browser +1. Open `http://127.0.0.1:8080/index.html` in a browser + - assuming one is running the llama-server locally with its default port 2. Select / Create a chat session - set a suitable system prompt, if needed - modify **settings**, if needed - **Restore** loads last autosaved session with same name -3. Enter the query, press **Enter** +3. Enter query/response into user input area at the bottom, press **Enter** - use **Shift‑Enter** for newline - include images if required (ai vision models) @@ -45,25 +51,26 @@ cd tools/server/public_simplechat/local.tools; python3 ./simpleproxy.py --config 5. If a tool call is requested - verify / edit the tool call details before triggering the same - one can even ask ai to rethink on the tool call requested, - by sending a appropriate user response instead of a tool call response. - - tool call is executed using browser's web worker or simpleproxy - - tool call response is placed in user input area (with color coding) + by sending a appropriate user response instead of a tool call response + - tool call is executed using Browser's web worker or included SimpleProxy.py + - tool call response is placed in user input area + - the user input area is color coded to distinguish between user and tool responses - verify / edit the tool call response, before submit same back to ai - tool response initially assigned `TOOL-TEMP` role, promoted to `TOOL` upon submit - based on got response, if needed one can rerun tool call with modified arguments - - *at any time there can be one pending tool call wrt a chat session* + - at any time there can be one pending tool call wrt a chat session -6. **Delete / Copy** available via popover menu for each message +6. **Delete & Copy** available via popover menu for each message -7. **Clear** / **+ New** chat with provided buttons, as needed +7. **Clear / + New** chat with provided buttons, as needed ## Overview -A lightweight ai chat client with a web front-end that supports multiple chat sessions, vision, reasoning and tool calling. +A lightweight simple minded ai chat client with a web front-end that supports multiple chat sessions, vision, reasoning and tool calling. - Supports multiple independent chat sessions with - - One‑shot or streamed (default) responses + - One‑shot or Streamed (default) responses - Custom settings and system prompts per session - Automatic local autosave (restorable on next load) - can handshake with `/completions` or `/chat/completions` (default) endpoints @@ -75,29 +82,30 @@ A lightweight ai chat client with a web front-end that supports multiple chat se - Supports vision / image / multimodal ai models - attach image files as part of user chat messages - handshaked as `image_url`s in chat message content array along with text - - supports multi-image uploads per message + - supports multiple image uploads per message - images displayed inline in the chat history - specify `mmproj` file via `-mmproj` or using `-hf` - specify `-batch-size` and `-ubatch-size` if needed -- Built-in support for GenAI models that expose tool calling +- Built-in support for GenAI/LLM models that support tool calling - includes a bunch of useful builtin tool calls, without needing any additional setup - - direct browser based tool calls include + - building on modern browsers' flexibility, following tool calls are directly supported by default - `sys_date_time`, `simple_calculator`, `run_javascript_function_code`, `data_store_*`, `external_ai` - except for external_ai, these are run from within a web worker context to isolate main context from them - data_store brings in browser IndexedDB based persistant key/value storage across sessions - - along with included python based simpleproxy.py + - in collaboration with included python based simpleproxy.py, these additional tool calls are supported - `search_web_text`, `fetch_web_url_raw`, `fetch_html_text`, `fetch_pdf_as_text`, `fetch_xml_filtered` - these built‑in tool calls (via SimpleProxy) help fetch PDFs, HTML, XML or perform web search - - PDF tool also returns an outline with numbering + - PDF tool also returns an outline with numbering, if available - result is truncated to `iResultMaxDataLength` (default 128 kB) - - helps isolate these functionality into a separate vm running locally or otherwise, if needed + - helps isolate core of these functionality into a separate vm running locally or otherwise, if needed - supports whitelisting of `allowed.schemes` and `allowed.domains` through `simpleproxy.json` - supports a bearer token shared between server and client for auth - needs https support, for better security wrt this flow, avoided now given mostly local use + and need for user to setup corresponding pki key pairs. - follows a safety first design and lets the user - verify and optionally edit the tool call requests, before executing the same @@ -105,8 +113,8 @@ A lightweight ai chat client with a web front-end that supports multiple chat se - user can update the settings for auto executing these actions, if needed - external_ai allows invoking a separate fresh ai instance - - ai could run self modified targeted versions of itself/... with custom system prompts and user messages as needed - - user can bring in an ai instance with additional compute access, which should be used only if needed + - ai could run self modified targeted versions of itself/... using custom system prompts and user messages as needed + - user can setup an ai instance with additional compute access, which should be used only if needed - tool calling is currently kept disabled in such a instance - Client side Sliding window Context control, using `iRecentUserMsgCnt`, helps limit context sent to ai model @@ -116,8 +124,8 @@ A lightweight ai chat client with a web front-end that supports multiple chat se - Follows responsive design to try adapt to any screen size - built using plain html + css + javascript and python - - no additional dependencies that one needs to keep track of - - except for pypdf, if pdf support needed. automaticaly drops pdf tool call if pypdf missing + - no additional dependencies that one needs to worry about and inturn keep track of + - except for pypdf, if pdf support needed. automaticaly drops pdf tool call support, if pypdf missing - fits within ~260KB even in uncompressed source form (including simpleproxy.py) - easily extend with additional tool calls using either javascript or python, for additional functionality @@ -141,7 +149,10 @@ One can modify the session configuration using Settings UI. All the settings and - **Ai Server** (`baseURL`) - ai server (llama-server) address - - default is `http://127.0.0.1:PORT` + - default is `http://127.0.0.1:8080` +- **SimpleProxy Server** (`proxyUrl`) + - the simpleproxy.py server address + - default is `http://127.0.0.1:3128` - **Stream** (`stream`) - `true` for live streaming, `false` for oneshot - **Client side Sliding Window** (`iRecentUserMsgCnt`) @@ -149,14 +160,14 @@ One can modify the session configuration using Settings UI. All the settings and - `0` : only system prompt - `>0` : last N user messages after the most recent system prompt - **Cache Prompt** (`cache_prompt`) - - enables server‑side caching of system prompt and history + - enables server‑side caching of system prompt and history to an extent - **Tool Call Timeout** (`toolCallResponseTimeoutMS`) - 200s by default - **Tool call Auto** (`autoSecs`) - seconds to wait before auto-triggering tool calls and auto-submitting tool responses - default is 0 ie manual - **Trim Garbage** (`bTrimGarbage`) - - Removes repeated trailing text + - tries to remove repeating trailing text ## Debugging Tips From 5dede41701effb228141d1e61bc6cc873e252b64 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 24 Nov 2025 21:45:42 +0530 Subject: [PATCH 364/365] SimpleChatTCRV:Cleanup importmap Remove the unneeded , wrt last entry Rather the import map isnt used currently, but certain entries kept there for future, more as a reminder. --- 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 702b902fb219f..217b025039af3 100644 --- a/tools/server/public_simplechat/index.html +++ b/tools/server/public_simplechat/index.html @@ -14,7 +14,7 @@ "simplechat": "./simplechat.js", "datautils": "./datautils.mjs", "ui": "./ui.mjs", - "toolsmanager": "./tools.mjs", + "toolsmanager": "./tools.mjs" } } From 09ec47413945fa7e19547a41a41e520101673b88 Mon Sep 17 00:00:00 2001 From: hanishkvc Date: Mon, 24 Nov 2025 22:41:19 +0530 Subject: [PATCH 365/365] SimpleChatTCRV:AiCallingAi ToolCall: flow cleanup and flexibility By default ensure external_ai tool call related special chat session starts with tool calls disabled and client side sliding window of 1. Add a helper in SimpleChat class to set these along with clearing of any chat history. Inturn now give the user flexibility to change this from within the program, if they need to for what ever reason, till the program restarts. --- .../public_simplechat/docs/changelog.md | 3 ++ .../server/public_simplechat/docs/details.md | 28 +++++++++------ tools/server/public_simplechat/main.js | 2 +- tools/server/public_simplechat/readme.md | 11 ++++-- tools/server/public_simplechat/simplechat.js | 35 +++++++++++++++++-- tools/server/public_simplechat/toolai.mjs | 5 +-- 6 files changed, 66 insertions(+), 18 deletions(-) diff --git a/tools/server/public_simplechat/docs/changelog.md b/tools/server/public_simplechat/docs/changelog.md index 4456698c97121..963d65b908f18 100644 --- a/tools/server/public_simplechat/docs/changelog.md +++ b/tools/server/public_simplechat/docs/changelog.md @@ -296,6 +296,9 @@ Chat Session specific settings chat sliding window size (which takes care of try avoid overloading the ai model context size) selected by user already. User can always change the sliding window size to view past messages beyond the currently active sliding window size and then switch back again, if they want to. +* More flexibility to user wrt ExternalAi tool call ie ai calling ai + * the user can change the default behaviour of tools being disabled and sliding window of 1 + * program restart will reset these back to the default ## ToDo diff --git a/tools/server/public_simplechat/docs/details.md b/tools/server/public_simplechat/docs/details.md index 421264b75ac80..5e29e5a0c5102 100644 --- a/tools/server/public_simplechat/docs/details.md +++ b/tools/server/public_simplechat/docs/details.md @@ -254,16 +254,18 @@ It is attached to the document object. Some of these can also be updated using t * 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 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. + * iRecentUserMsgCnt - a simple minded ClientSide SlidingWindow logic 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 user messages (after the latest system prompt) and its responses from the ai model along with any associated tool calls 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. * less than 0 : Send entire chat history to server - * 0 : Send only the system message if any to the server + * 0 : Send only the system message if any to the server. Even the latest user message wont be sent. * greater than 0 : Send the latest chat history from the latest system prompt, limited to specified cnt. + * NOTE: the latest user message (query/response/...) for which we need a ai response, will also be counted as belonging to the iRecentUserMsgCnt. + * 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. @@ -467,16 +469,22 @@ The following tools/functions are currently provided by default * data_store_get/set/delete/list - allows for a basic data store to be used, to maintain data and or context across sessions and so... -* external_ai - allows ai to use an independent session of itself / different instance of ai, +* external_ai - allows ai to use an independent fresh by default session of itself / different ai, with a custom system prompt of ai's choosing and similarly user message of ai's choosing, in order to get any job it deems necessary to be done in a uncluttered indepedent session. - * helps ai to process stuff that it needs, without having to worry about any previous chat history - etal messing with the current data's context and processing. + * in its default configuration, helps ai to process stuff that it needs, without having to worry + about any previous chat history etal messing with the current data's context and processing. * helps ai to process stuff with targeted system prompts of its choosing, for the job at hand. - * tool calling is disabled wrt the external_ai's independent session, for now. - * it was noticed that else even the external_ai may call into more external_ai calls trying to - find answer to the same question. maybe one can enable tool calling, while explicitly disabling - external_ai tool call from within external_ai tool call or so later... + * by default + * tool calling is disabled wrt the external_ai's independent session. + * it was noticed that else even external_ai may call into more external_ai calls trying to + find answers to the same question/situation. + * maybe one can enable tool calling, while explicitly disabling of external_ai tool call + from within external_ai tool call related session or so later... + * client side sliding window size is set to 1 so that only system prompt and ai set user message + gets handshaked with the external_ai instance + * End user can change this behaviour by changing the corresponding settings of the TCExternalAi + special chat session, which is internally used for this tool call. * Could be used by ai for example to * summarise a large text content, where it could use the context of the text to generate a suitable system prompt for summarising things suitably @@ -486,7 +494,7 @@ The following tools/functions are currently provided by default * given the fuzzy nature of the generative ai, sometimes the model may even use this tool call to get answer to questions like what is your name ;> * end user can use this mechanism to try and bring in an instance of ai running on a more powerful - machine, but then to be used only if needed or so + machine with more compute and memory capabiliteis, but then to be used only if needed or so Most of the above (except for external ai call) 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 diff --git a/tools/server/public_simplechat/main.js b/tools/server/public_simplechat/main.js index 6e0efa03ff03d..d60fc910342ef 100644 --- a/tools/server/public_simplechat/main.js +++ b/tools/server/public_simplechat/main.js @@ -32,7 +32,7 @@ function startme() { sL.push(gMe.multiChat.new_chat_session(cid)); } await Promise.allSettled(sL) - gMe.multiChat.simpleChats[mChatMagic.AI_TC_SESSIONNAME].cfg.tools.enabled = false + gMe.multiChat.simpleChats[mChatMagic.AI_TC_SESSIONNAME].default_isolating() gMe.multiChat.setup_ui(gMe.defaultChatIds[0]); gMe.multiChat.show_sessions(); gMe.multiChat.handle_session_switch(gMe.multiChat.curChatId) diff --git a/tools/server/public_simplechat/readme.md b/tools/server/public_simplechat/readme.md index b789ab396f9b0..9763b64f30d22 100644 --- a/tools/server/public_simplechat/readme.md +++ b/tools/server/public_simplechat/readme.md @@ -112,10 +112,17 @@ A lightweight simple minded ai chat client with a web front-end that supports mu - verify and optionally edit the tool call response, before submitting the same - user can update the settings for auto executing these actions, if needed - - external_ai allows invoking a separate fresh ai instance + - external_ai allows invoking a separate optionally fresh by default ai instance - ai could run self modified targeted versions of itself/... using custom system prompts and user messages as needed - user can setup an ai instance with additional compute access, which should be used only if needed - - tool calling is currently kept disabled in such a instance + - by default in such a instance + - tool calling is kept disabled along with + - client side sliding window of 1, + ie only system prompt and latest user message is sent to ai server. + - TCExternalAI is the special chat session used internally for this, + and the default behaviour will get impacted if you modify the settings of this special chat session. + - Restarting this chat client logic will force reset things to the default behaviour, + how ever any other settings wrt TCExternalAi, that where changed, will persist across restarts. - Client side Sliding window Context control, using `iRecentUserMsgCnt`, helps limit context sent to ai model diff --git a/tools/server/public_simplechat/simplechat.js b/tools/server/public_simplechat/simplechat.js index 3abc6bd27da69..0f3df0e742e09 100644 --- a/tools/server/public_simplechat/simplechat.js +++ b/tools/server/public_simplechat/simplechat.js @@ -486,9 +486,10 @@ function usage_note(sRecentUserMsgCnt) {
    • ChatHistInCtxt, MaxTokens, ModelCtxt window to expand
    -
  • ${AI_TC_SESSIONNAME} session keeps tool calls disabled, to avoid recursive...
  • +
  • ${AI_TC_SESSIONNAME} session used for external_ai tool call, ie ai calling ai
    • -
    • Used by external_ai tool call, which allows ai calling ai, as needed.
    • +
    • by default keeps tool calls disabled, client side sliding window of 1
    • +
    • if you change for some reason, you may want to change back to these
    `; @@ -572,6 +573,18 @@ export class SimpleChat { this.latestResponse = new ChatMessageEx(); } + /** + * A relatively isolating default setup + * * clear any chat history + * * disable tool calls + * * set client side sliding window to 1 so that only system prompt is sent along with latest user message + */ + default_isolating() { + this.clear() + this.cfg.tools.enabled = false + this.cfg.chatProps.iRecentUserMsgCnt = 1 + } + setup() { return this.toolsMgr.setup(this.chatId) } @@ -669,6 +682,7 @@ export class SimpleChat { * * 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) { @@ -2074,6 +2088,13 @@ export class Config { this.chatProps = { apiEP: ApiEP.Type.Chat, stream: true, + /** + * How many recent user msgs to consider and include along with their corresponding + * assistant responses and tool calls if any, wrt client side sliding window logic. + * * user specified System prompt is outside this count. + * * the latest user query/response to send to ai server is part of this. + * * only user messages following the latest system prompt is considered. + */ iRecentUserMsgCnt: 5, bCompletionFreshChatAlways: true, bCompletionInsertStandardRolePrefix: false, @@ -2201,7 +2222,15 @@ export class Me { this.dataURLs = [] this.houseKeeping = { clear: true, - } + }; + /** + * Control if externalai toolcall related special chat session starts in a forced isolating state + * * always external_ai tool call is made + * * Or does it start in such a state only at the time of program loading and inturn + * if user has the flexibility to change this characteristic till this program is restarted, + * for what ever reason they may deem fit. + */ + this.tcexternalaiForceIsolatingDefaultsAlways = false; } /** diff --git a/tools/server/public_simplechat/toolai.mjs b/tools/server/public_simplechat/toolai.mjs index 412624757bfc3..02f1bc0e5af11 100644 --- a/tools/server/public_simplechat/toolai.mjs +++ b/tools/server/public_simplechat/toolai.mjs @@ -92,10 +92,11 @@ let externalai_meta = { */ function externalai_run(chatid, toolcallid, toolname, obj) { let sc = gMe.multiChat.simpleChats[mChatMagic.AI_TC_SESSIONNAME]; - sc.clear() + if (gMe.tcexternalaiForceIsolatingDefaultsAlways) { + sc.default_isolating() + } sc.add_system_anytime(obj['system_prompt'], 'TC:ExternalAI') sc.add(new mChatMagic.ChatMessageEx(new mChatMagic.NSChatMessage(mChatMagic.Roles.User, obj['user_message']))) - sc.cfg.tools.enabled = false sc.handle_chat_hs(sc.cfg.baseURL, mChatMagic.ApiEP.Type.Chat, gMe.multiChat.elDivStreams).then((resp)=>{ gMe.toolsMgr.workers_postmessage_for_main(gMe.toolsMgr.workers.js, chatid, toolcallid, toolname, resp.content_equiv()); }).catch((err)=>{