Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
fa23e9d
SimpleChatToolCalling: Test/Explore srvr initial hs using cmdline
hanishkvc Oct 10, 2025
75ce9e4
SimpleChatTools: Add boolean to allow user control of tools use
hanishkvc Oct 10, 2025
68fc28f
SimpleChatTC: Update test shell script a bit
hanishkvc Oct 10, 2025
85845a0
SimpleChatTC: Add skeleton for a javascript interpretor tool call
hanishkvc Oct 10, 2025
bbaae70
SimpleChatTC: More generic tooljs, SimpCalc, some main skeleton
hanishkvc Oct 10, 2025
f091568
SimpleChatTC: Bring in the tools meta into the main flow
hanishkvc Oct 10, 2025
9d8be85
SimpleChatTC: use tcpdump to dbg hs; check if ai aware of tools
hanishkvc Oct 10, 2025
2e4693c
SimpleChatTC: Skeleton to handle diff fields when streaming
hanishkvc Oct 10, 2025
27161cb
SimpleChatTC: Extract streamed field - assume only 1f at any time
hanishkvc Oct 10, 2025
788d56a
SimpleChatTC: Avoid null content, Fix oversight wrt finish_reason
hanishkvc Oct 10, 2025
4cbe1d2
SimpleChatTC: Show toolcall being generated by ai - Temp
hanishkvc Oct 10, 2025
174b0b1
SimpleChatTC: AssistantResponse class initial go
hanishkvc Oct 10, 2025
10b1013
SimpleChatTC: AssistantResponse everywhere initial go
hanishkvc Oct 10, 2025
e4e29a2
SimpleChatTC: twins wrt streamed response handling
hanishkvc Oct 10, 2025
d7f612f
SimpleChatTC: Saner/Robust AssistantResponse content_equiv
hanishkvc Oct 10, 2025
2a27697
SimpleChatTC:tooljs: Trap console.log and store in new result key
hanishkvc Oct 11, 2025
92b82ae
SimpleChatTC: Implement a simple toolcall handling flow
hanishkvc Oct 11, 2025
d8b1b36
SimpleChatTC: Cleanup initial/1st go toolcall flow
hanishkvc Oct 11, 2025
7a2bcfb
SimpleChatTC: Trap any exception raised during tool call
hanishkvc Oct 11, 2025
f10ab96
SimpleChatTC: More clearer description of toolcalls execution env
hanishkvc Oct 12, 2025
a1f1776
SimpleChatTC: Clarify some type definitions to avoid warnings
hanishkvc Oct 12, 2025
4ac6f0a
SimpleChatTC: Move tool calling to tools, try trap async failures
hanishkvc Oct 12, 2025
3796306
SimpleChatTC: Pass toolname to the tool handler
hanishkvc Oct 12, 2025
0ed8329
SimpleChatTC: Cleanup the function description a bit
hanishkvc Oct 12, 2025
aa81f51
SimpleChatTC: Update the readme.md wrt tool calling a bit
hanishkvc Oct 12, 2025
5ed2bc3
SimpleChatTC: ToolCall hs info in normal assistant-user chat flow
hanishkvc Oct 12, 2025
619d64d
SimpleChatTC: Add ui elements for tool call verify and trigger
hanishkvc Oct 12, 2025
226aa7d
SimpleChatTC: Let user trigger tool call, instead of automatic
hanishkvc Oct 12, 2025
2aabca2
SimpleChatTC: Update readme with bit more details, Cleaner UI
hanishkvc Oct 12, 2025
90b2491
SimpleChatTC: Tool Calling UI elements use up horizontal space
hanishkvc Oct 12, 2025
a8eadc4
SimpleChatTC: Update readme wrt --jinja argument and bit more
hanishkvc Oct 12, 2025
70bc1b4
SimpleChatTC: Move console.log trapping into its own module
hanishkvc Oct 13, 2025
f8ebe8f
SimpleChatTC:ToolsConsole:Cleanup a bit, add basic set of notes
hanishkvc Oct 13, 2025
cc60600
SimpleChatTC: Initial skeleton of a simple toolsworker
hanishkvc Oct 13, 2025
7ea9bf6
SimpleChatTC: Pass around structured objects wrt tool worker
hanishkvc Oct 13, 2025
4664748
SimpleChatTC: Actual tool call implementations simplified
hanishkvc Oct 13, 2025
5933b28
SimpleChatTC: Get ready for decoupled tool call response
hanishkvc Oct 13, 2025
50be171
SimpleChatTC: Web worker flow initial go cleanup
hanishkvc Oct 13, 2025
44cfebc
SimpleChatTC: Increase the sliding window context to Last4 QA
hanishkvc Oct 13, 2025
6f137f2
SimpleChatTC: Update readme.md wrt latest updates. 2k maxtokens
hanishkvc Oct 13, 2025
dbf050c
SimpleChatTC: update descs to indicate use of web workers
hanishkvc Oct 13, 2025
39c1c01
SimpleChatTC:ChatMessage: AssistantResponse into chat message class
hanishkvc Oct 14, 2025
e9a7871
SimpleChatTC:ChatMessageEx: UpdateStream logic
hanishkvc Oct 14, 2025
340ae0c
SimpleChatTC:ChatMessageEx:cleanup, HasToolCalls, ContentEquiv
hanishkvc Oct 14, 2025
0629f79
SimpleChatTC:ChatMessage: remove ResponseExtractStream
hanishkvc Oct 14, 2025
bb25aa0
SimpleChatTC:ChatMessageEx: add update_oneshot
hanishkvc Oct 14, 2025
ae00cb2
SimpleChatTC:ChatMessageEx: ods load, system prompt related
hanishkvc Oct 14, 2025
e1e1d42
SimpleChatTC:ChatMessageEx: RecentChat, GetSystemLatest
hanishkvc Oct 14, 2025
3b73b00
SimpleChatTC:ChatMessageEx: Upd Add, rm sysPromptAtBeginOnly hlpr
hanishkvc Oct 14, 2025
aa80bf0
SimpleChatTC:ChatMessageEx: Recent chat users upd
hanishkvc Oct 14, 2025
755505e
SimpleChatTC:ChatMessageEx: Cleanup remaining stuff
hanishkvc Oct 14, 2025
7fb5526
SimpleChatTC:Load allows old and new ChatMessage(Ex) formats
hanishkvc Oct 14, 2025
69cbc81
SimpleChatTC:ChatMessageEx: send tool_calls, only if needed
hanishkvc Oct 14, 2025
f379e65
SimpleChatTC:Propogate toolcall id through tool call chain
hanishkvc Oct 14, 2025
4efa232
SimpleChatTC:ChatMessageEx: Build tool role result fully
hanishkvc Oct 14, 2025
a644cb3
SimpleChatTC:ChatMessageEx:While at it also ns_delete
hanishkvc Oct 14, 2025
61c2314
SimpleChatTC:Readme: Updated wrt new relativelyProper toolCallsHS
hanishkvc Oct 14, 2025
75a63e3
SimpleChatTC:ChatMessageEx: Better tool result extractor
hanishkvc Oct 15, 2025
99b04ec
SimpleChatTC:ChatMessageEx: 1st go at trying to track promises
hanishkvc Oct 15, 2025
7dc999f
SimpleChatTC:TrapPromise: log the trapping
hanishkvc Oct 15, 2025
5d76493
SimpleChatTC:Promises: trap normal fetch (dont care await or not)
hanishkvc Oct 15, 2025
788a9e4
SimpleChatTC: Allow await in generated code that will be evald
hanishkvc Oct 15, 2025
c155bd3
SimpleChatTC:Ensure fetch's promise chain is also trapped
hanishkvc Oct 15, 2025
1e1bb9a
SimpleChatTC: update readme wrt promise related trapping
hanishkvc Oct 15, 2025
9b1ed1f
SimpleChatTC:WebFetchThroughProxy:Initial go creating request
hanishkvc Oct 16, 2025
1b361ee
SimpleChatTC:SimpleProxy:Process args --port
hanishkvc Oct 16, 2025
9ec3d45
SimpleChatTC:SimpleProxy: Start server, Show requested path
hanishkvc Oct 16, 2025
620da45
SimpleChatTC:SimpleProxy: Cleanup for basic run
hanishkvc Oct 16, 2025
bdd054b
SimpleChatTC:SimpleProxy: Extract and check path, route to handlers
hanishkvc Oct 16, 2025
5ac60a8
SimpleChatTC:SimpleProxy:implement handle_urlraw
hanishkvc Oct 16, 2025
ac448c3
SimpleChatTC:SimpleProxy:UrlRaw: Fixup basic oversight wrt 1st go
hanishkvc Oct 16, 2025
2ca2b98
SimpleChatTC:WebFetch: Update to use internal SimpleProxy.py
hanishkvc Oct 16, 2025
04a5f95
SimpleChatTC:SimpleProxy: Cleanup few messages
hanishkvc Oct 16, 2025
295893c
SimpleChatTC:SimpleProxy:Common UrlReq helper for UrlRaw & UrlText
hanishkvc Oct 16, 2025
4694537
SimpleChatTC:SimpleProxy: ElementTree, No _UrlopenRet
hanishkvc Oct 16, 2025
696d560
SimpleChatTC:SimpleProxy: Switch to html.parser
hanishkvc Oct 16, 2025
2e6a386
SimpleChatTC:SimpleProxy:UrlText: Capture body except for scripts
hanishkvc Oct 16, 2025
0a3b4c2
SimpleChatTC:SimpleProxy:UrlText: Avoid style blocks also
hanishkvc Oct 16, 2025
61b9371
SimpleChatTC:WebUrl FetchStrip through simple proxy
hanishkvc Oct 16, 2025
378bde5
SimpleChatTC:SimpleProxy:UrlText: Try strip empty lines some what
hanishkvc Oct 16, 2025
e7277cb
SimpleChatTC:SimpleProxy:UrlText: Slightly better trimming
hanishkvc Oct 16, 2025
9db2f27
SimpleChatTC:WebUrlText:Update name and desc to see if prefered
hanishkvc Oct 16, 2025
c5a602f
SimpleChatTC:SimpleProxy:Options just in case
hanishkvc Oct 16, 2025
6efbd12
SimpleChatTC:WebFetch:UrlEnc url2fetch b4Passing toProxy asQuery
hanishkvc Oct 16, 2025
8f0700a
SimpleChatTC: Update readme wrt web fetch and related simple proxy
hanishkvc Oct 17, 2025
514cec0
SimpleChatTC:SimpleProxy:HtmlParser more generic and flexible
hanishkvc Oct 17, 2025
e124317
SimpleChatTC:WebFetch: Cleanup the names and descriptions a bit
hanishkvc Oct 17, 2025
910995e
SimpleChatTC:SimpleProxy: Ensure CORS related headers sent always
hanishkvc Oct 17, 2025
4ffed94
SimpleChatTC:WebFetch:Trap Non Ok status and raise error
hanishkvc Oct 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions tools/server/public_simplechat/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@
<p> You need to have javascript enabled.</p>
</div>

<hr>
<div id="tool-div">
<div>
<div class="sameline">
<textarea id="toolname-in" class="flex-grow" rows="1" placeholder="name of tool to run"></textarea>
<button id="tool-btn">run tool</button>
</div>
</div>
<div class="sameline">
<textarea id="toolargs-in" class="flex-grow" rows="2" placeholder="arguments to pass to the specified tool"></textarea>
</div>
</div>

<hr>
<div class="sameline">
<textarea id="user-in" class="flex-grow" rows="2" placeholder="enter your query to the ai model here" ></textarea>
Expand Down
234 changes: 234 additions & 0 deletions tools/server/public_simplechat/local.tools/simpleproxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# 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
import http.server
import urllib.parse
import urllib.request
from dataclasses import dataclass
import html.parser


gMe = {
'--port': 3128,
'server': None
}


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}")
pr = urllib.parse.urlparse(self.path)
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: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_headers_common()


@dataclass(frozen=True)
class UrlReqResp:
callOk: bool
httpStatus: int
httpStatusMsg: str = ""
contentType: str = ""
contentData: str = ""


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:{tag}:Url:{url}")
url = url[0]
if (not url) or (len(url) == 0):
return UrlReqResp(False, 400, f"WARN:{tag}:MissingUrl")
try:
# Get requested url
with urllib.request.urlopen(url, 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: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(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:UrlFetchFailed:{exc}")


class TextHtmlParser(html.parser.HTMLParser):

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):
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(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'))
except Exception as exc:
ph.send_error(502, f"WARN:UrlFetchFailed:{exc}")


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] = int(args[iArg])
iArg += 1
case _:
gMe['INTERNAL.ProcessArgs.Unknown'].append(cArg)
print(f"WARN:ProcessArgs:{iArg}:IgnoringUnknownCommand:{cArg}")
iArg += 1


def run():
try:
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...")
if (gMe['server']):
gMe['server'].server_close()
sys.exit(0)
except Exception as exc:
print(f"ERRR:Run:Exiting:Exception:{exc}")
if (gMe['server']):
gMe['server'].server_close()
sys.exit(1)


if __name__ == "__main__":
process_args(sys.argv)
run()
Loading