@yeaft/webchat-agent 0.1.442 → 0.1.443

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,131 @@
1
+ /**
2
+ * web-fetch.js — Fetch web page content.
3
+ *
4
+ * Retrieves the content of a URL, converts HTML to readable text,
5
+ * and returns it for the LLM to process.
6
+ */
7
+
8
+ import { defineTool } from './types.js';
9
+
10
+ /** Strip HTML tags and normalize whitespace for readability. */
11
+ function htmlToText(html) {
12
+ return html
13
+ // Remove script/style blocks
14
+ .replace(/<script[\s\S]*?<\/script>/gi, '')
15
+ .replace(/<style[\s\S]*?<\/style>/gi, '')
16
+ // Replace br/p/div/h tags with newlines
17
+ .replace(/<br\s*\/?>/gi, '\n')
18
+ .replace(/<\/(p|div|h[1-6]|li|tr|blockquote)>/gi, '\n')
19
+ .replace(/<(p|div|h[1-6]|li|tr|blockquote)[^>]*>/gi, '\n')
20
+ // Strip remaining tags
21
+ .replace(/<[^>]+>/g, '')
22
+ // Decode common HTML entities
23
+ .replace(/&amp;/g, '&')
24
+ .replace(/&lt;/g, '<')
25
+ .replace(/&gt;/g, '>')
26
+ .replace(/&quot;/g, '"')
27
+ .replace(/&#39;/g, "'")
28
+ .replace(/&nbsp;/g, ' ')
29
+ // Normalize whitespace
30
+ .replace(/[ \t]+/g, ' ')
31
+ .replace(/\n{3,}/g, '\n\n')
32
+ .trim();
33
+ }
34
+
35
+ export default defineTool({
36
+ name: 'WebFetch',
37
+ description: `Fetch and read the content of a web page.
38
+
39
+ Retrieves the URL content, strips HTML tags, and returns readable text.
40
+ Use this to read documentation, articles, or any web page.
41
+
42
+ Guidelines:
43
+ - Provide the full URL including protocol (https://)
44
+ - Large pages will be truncated — use the offset parameter for pagination
45
+ - For APIs, the raw response body is returned as-is
46
+ - Respects the abort signal for cancellation`,
47
+ parameters: {
48
+ type: 'object',
49
+ properties: {
50
+ url: {
51
+ type: 'string',
52
+ description: 'The URL to fetch',
53
+ },
54
+ max_length: {
55
+ type: 'number',
56
+ description: 'Maximum content length in characters (default: 50000)',
57
+ },
58
+ raw: {
59
+ type: 'boolean',
60
+ description: 'If true, return raw response without HTML stripping (for APIs)',
61
+ },
62
+ },
63
+ required: ['url'],
64
+ },
65
+ modes: ['chat', 'work'],
66
+ isConcurrencySafe: () => true,
67
+ isReadOnly: () => true,
68
+ async execute(input, ctx) {
69
+ const { url, max_length = 50000, raw = false } = input;
70
+ if (!url) return JSON.stringify({ error: 'url is required' });
71
+
72
+ try {
73
+ // Validate URL
74
+ let parsedUrl;
75
+ try {
76
+ parsedUrl = new URL(url);
77
+ } catch {
78
+ return JSON.stringify({ error: `Invalid URL: ${url}` });
79
+ }
80
+
81
+ // Only allow http(s)
82
+ if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
83
+ return JSON.stringify({ error: `Unsupported protocol: ${parsedUrl.protocol}` });
84
+ }
85
+
86
+ const response = await fetch(url, {
87
+ signal: ctx?.signal,
88
+ headers: {
89
+ 'User-Agent': 'Yeaft/1.0 (compatible; bot)',
90
+ 'Accept': 'text/html, application/json, text/plain, */*',
91
+ },
92
+ redirect: 'follow',
93
+ });
94
+
95
+ if (!response.ok) {
96
+ return JSON.stringify({
97
+ error: `HTTP ${response.status}: ${response.statusText}`,
98
+ url,
99
+ });
100
+ }
101
+
102
+ const contentType = response.headers.get('content-type') || '';
103
+ const body = await response.text();
104
+
105
+ let content;
106
+ if (raw || contentType.includes('json') || contentType.includes('text/plain')) {
107
+ content = body;
108
+ } else {
109
+ content = htmlToText(body);
110
+ }
111
+
112
+ // Truncate if too long
113
+ const truncated = content.length > max_length;
114
+ if (truncated) {
115
+ content = content.slice(0, max_length);
116
+ }
117
+
118
+ return JSON.stringify({
119
+ url: response.url, // final URL after redirects
120
+ status: response.status,
121
+ contentType,
122
+ contentLength: content.length,
123
+ truncated,
124
+ content,
125
+ });
126
+ } catch (err) {
127
+ if (err.name === 'AbortError') return JSON.stringify({ error: 'Fetch cancelled' });
128
+ return JSON.stringify({ error: `Fetch failed: ${err.message}`, url });
129
+ }
130
+ },
131
+ });
@@ -0,0 +1,80 @@
1
+ /**
2
+ * web-search.js — Web search tool.
3
+ *
4
+ * Delegates to an external search API or LLM-based web search.
5
+ * Supports configurable search providers via Yeaft config.
6
+ */
7
+
8
+ import { defineTool } from './types.js';
9
+
10
+ export default defineTool({
11
+ name: 'WebSearch',
12
+ description: `Search the web for current information.
13
+
14
+ Use this when you need up-to-date information that may not be in your training data.
15
+ Returns search results with titles, URLs, and snippets.
16
+
17
+ Guidelines:
18
+ - Use specific, targeted search queries
19
+ - Include the current year for time-sensitive queries
20
+ - Combine with WebFetch to read full page content from results`,
21
+ parameters: {
22
+ type: 'object',
23
+ properties: {
24
+ query: {
25
+ type: 'string',
26
+ description: 'The search query',
27
+ },
28
+ limit: {
29
+ type: 'number',
30
+ description: 'Maximum number of results (default: 5)',
31
+ },
32
+ },
33
+ required: ['query'],
34
+ },
35
+ modes: ['chat', 'work'],
36
+ isConcurrencySafe: () => true,
37
+ isReadOnly: () => true,
38
+ async execute(input, ctx) {
39
+ const { query, limit = 5 } = input;
40
+ if (!query) return JSON.stringify({ error: 'query is required' });
41
+
42
+ try {
43
+ // Check if adapter supports web search natively (some LLM providers have built-in search)
44
+ const adapter = ctx?.adapter;
45
+ if (adapter && typeof adapter.webSearch === 'function') {
46
+ const results = await adapter.webSearch(query, limit);
47
+ return JSON.stringify(results, null, 2);
48
+ }
49
+
50
+ // Check config for search API endpoint
51
+ const searchUrl = ctx?.config?.searchApiUrl;
52
+ if (searchUrl) {
53
+ const url = new URL(searchUrl);
54
+ url.searchParams.set('q', query);
55
+ url.searchParams.set('limit', String(limit));
56
+
57
+ const response = await fetch(url.toString(), {
58
+ signal: ctx?.signal,
59
+ headers: { 'User-Agent': 'Yeaft/1.0' },
60
+ });
61
+
62
+ if (!response.ok) {
63
+ return JSON.stringify({ error: `Search API returned ${response.status}: ${response.statusText}` });
64
+ }
65
+
66
+ const data = await response.json();
67
+ return JSON.stringify(data, null, 2);
68
+ }
69
+
70
+ // Fallback: no search provider configured
71
+ return JSON.stringify({
72
+ error: 'No web search provider configured.',
73
+ hint: 'Configure searchApiUrl in ~/.yeaft/config.json or use an LLM provider with built-in search.',
74
+ });
75
+ } catch (err) {
76
+ if (err.name === 'AbortError') return JSON.stringify({ error: 'Search cancelled' });
77
+ return JSON.stringify({ error: `Web search failed: ${err.message}` });
78
+ }
79
+ },
80
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * write-stdin.js — Write data to a running process's stdin.
3
+ *
4
+ * Used in conjunction with Bash for processes that need interactive input.
5
+ * Currently returns a guidance message since Bash tool uses 'ignore' for stdin.
6
+ */
7
+
8
+ import { defineTool } from './types.js';
9
+
10
+ export default defineTool({
11
+ name: 'WriteStdin',
12
+ description: `Write data to a running process's standard input.
13
+
14
+ This tool is intended for sending input to interactive processes.
15
+ Since the Bash tool runs commands non-interactively, this is primarily
16
+ useful with the terminal system or long-running processes.
17
+
18
+ Note: For most use cases, pipe input via Bash: echo "input" | command`,
19
+ parameters: {
20
+ type: 'object',
21
+ properties: {
22
+ process_id: {
23
+ type: 'string',
24
+ description: 'Process identifier or terminal ID',
25
+ },
26
+ data: {
27
+ type: 'string',
28
+ description: 'Data to write to stdin',
29
+ },
30
+ newline: {
31
+ type: 'boolean',
32
+ description: 'Append newline after data (default: true)',
33
+ },
34
+ },
35
+ required: ['data'],
36
+ },
37
+ modes: ['work'],
38
+ isConcurrencySafe: () => false,
39
+ isReadOnly: () => false,
40
+ async execute(input, ctx) {
41
+ const { process_id, data, newline = true } = input;
42
+ if (!data && data !== '') return JSON.stringify({ error: 'data is required' });
43
+
44
+ // The Bash tool uses 'ignore' for stdin, so direct stdin writing
45
+ // is only possible through the terminal system.
46
+ // For most interactive needs, recommend using pipe syntax.
47
+ return JSON.stringify({
48
+ hint: 'The Bash tool does not support interactive stdin. Use pipe syntax instead:',
49
+ example: `echo "${data}" | your_command`,
50
+ alternativeBash: `printf '%s\\n' '${data.replace(/'/g, "'\\''")}' | your_command`,
51
+ message: 'For interactive processes, use the terminal system (not the AI tool system).',
52
+ });
53
+ },
54
+ });