summaryrefslogtreecommitdiff
path: root/llm-tools-web.el
diff options
context:
space:
mode:
Diffstat (limited to 'llm-tools-web.el')
-rw-r--r--llm-tools-web.el118
1 files changed, 118 insertions, 0 deletions
diff --git a/llm-tools-web.el b/llm-tools-web.el
new file mode 100644
index 0000000..7002713
--- /dev/null
+++ b/llm-tools-web.el
@@ -0,0 +1,118 @@
1;;; Web tools, taken from gptel-agent
2(defun gptel-agent--fetch-with-timeout (url url-cb tool-cb failed-msg &rest args)
3 "Fetch URL and call URL-CB in the result buffer.
4
5Call TOOL-CB if there is an error or a timeout. TOOL-CB and ARGS are
6passed to URL-CB. FAILED-MSG is a fragment used for messaging. Handles
7cleanup."
8 (let* ((timeout 30) timer done
9 (inherit-process-coding-system t)
10 (proc-buffer
11 (url-retrieve
12 url (lambda (status)
13 (setq done t)
14 (when timer (cancel-timer timer))
15 (if-let* ((err (plist-get status :error)))
16 (funcall tool-cb
17 (format "Error: %s failed with error: %S" failed-msg err))
18 (apply url-cb tool-cb args))
19 (kill-buffer (current-buffer)))
20 args 'silent)))
21 (setq timer
22 (run-at-time
23 timeout nil
24 (lambda (buf cb)
25 (unless done
26 (setq done t)
27 (let ((kill-buffer-query-functions)) (kill-buffer buf))
28 (funcall
29 cb (format "Error: %s timed out after %d seconds."
30 failed-msg timeout))))
31 proc-buffer tool-cb))
32 proc-buffer))
33
34;;;; Web Searching, taken from gptel-agent
35(defun llm-tools--shr-next-link ()
36 "Jump to the next SHR link in the buffer. Return jump position."
37 (let ((current-prop (get-char-property (point) 'shr-url))
38 (next-pos (point)))
39 (while (and (not (eobp))
40 (setq next-pos
41 (or (next-single-property-change (point) 'shr-url)
42 (point-max)))
43 (let ((next-prop (get-char-property next-pos 'shr-url)))
44 (or (equal next-prop current-prop)
45 (equal next-prop nil))))
46 (goto-char next-pos))
47 (goto-char next-pos)))
48
49(defvar llm-tools--web-search-active nil)
50
51(defun llm-tools--web-search-eww (tool-cb query &optional count)
52 "Search the web using eww's default search engine (usually DuckDuckGo).
53
54Call TOOL-CB with the results as a string. QUERY is the search string.
55COUNT is the number of results to return (default 5)."
56 ;; No more than two active searches at one time
57 (setq llm-tools--web-search-active
58 (cl-delete-if-not
59 (lambda (buf) (and (buffer-live-p buf)
60 (process-live-p (get-buffer-process buf))))
61 llm-tools--web-search-active))
62 (if (>= (length llm-tools--web-search-active) 2)
63 (progn (message "Web search: waiting for turn")
64 (run-at-time 5 nil #'llm-tools--web-search-eww
65 tool-cb query count))
66 (push (llm-tools--fetch-with-timeout
67 (concat eww-search-prefix (url-hexify-string query))
68 #'llm-tools--web-search-eww-callback
69 tool-cb (format "Web search for \"%s\"" query))
70 llm-tools--web-search-active)))
71
72(defun llm-tools--web-search-eww-callback (cb)
73 "Extract website text and run callback CB with it."
74 (let* ((count 5) (results))
75 (goto-char (point-min))
76 (goto-char url-http-end-of-headers)
77 (let* ((dom (libxml-parse-html-region (point) (point-max)))
78 (result-count 0))
79 (eww-score-readability dom)
80 (with-temp-buffer
81 (shr-insert-document (eww-highest-readability dom))
82 (goto-char (point-min))
83 (while (and (not (eobp)) (< result-count count))
84 (let ((pos (point))
85 (url (get-char-property (point) 'shr-url))
86 (next-pos (llm-tools--shr-next-link)))
87 (when-let* (((stringp url))
88 (idx (string-search "http" url))
89 (url-fmt (url-unhex-string (substring url idx))))
90 (cl-incf result-count)
91 (push (concat url-fmt "\n\n"
92 (string-trim
93 (buffer-substring-no-properties pos next-pos))
94 "\n\n----\n")
95 results))))))
96 (funcall cb (apply #'concat (nreverse results)))))
97
98;;;; Read URLs, taken from gptel-agent
99(defun llm-tools--read-url (tool-cb url)
100 "Fetch URL text and call TOOL-CB with it."
101 (llm-tools--fetch-with-timeout
102 url
103 (lambda (cb)
104 (goto-char (point-min)) (forward-paragraph)
105 (condition-case errdata
106 (let ((dom (libxml-parse-html-region (point) (point-max))))
107 (with-temp-buffer
108 (eww-score-readability dom)
109 (shr-insert-document (eww-highest-readability dom))
110 (decode-coding-region (point-min) (point-max) 'utf-8)
111 (funcall
112 cb (buffer-substring-no-properties
113 (point-min) (point-max)))))
114 (error (funcall cb (format "Error: Request failed with error data:\n%S"
115 errdata)))))
116 tool-cb (format "Fetch for \"%s\"" url)))
117
118(provide 'llm-tools-web)