@vierratale/ai 0.1.0-beta.1 → 0.1.0-beta.10
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.
- package/README.md +157 -15
- package/bin/vierrataleai.js +74 -1
- package/package.json +10 -2
- package/scripts/postinstall.js +35 -0
- package/src/catalog.js +94 -45
- package/src/cli.js +1098 -51
- package/src/cmd/agent.js +1711 -0
- package/src/cmd/executor.js +257 -0
- package/src/cmd/tools.js +153 -0
- package/src/config.js +54 -15
- package/src/index.js +2 -1
- package/src/installer.js +159 -15
- package/src/prompts/system.json +2 -2
- package/src/prompts/system.md +42 -0
- package/src/providers/anthropic.js +130 -0
- package/src/providers/base.js +18 -0
- package/src/providers/cortex.js +298 -0
- package/src/providers/gemini.js +128 -0
- package/src/providers/index.js +7 -3
- package/src/providers/openai.js +79 -34
- package/src/session.js +221 -0
- package/src/ui/banner.js +23 -12
- package/src/ui/branding.js +8 -9
- package/src/ui/chatbox.js +280 -0
- package/src/ui/input.js +501 -0
- package/src/ui/terminal.js +84 -8
- package/src/utils/downloader.js +364 -0
- package/src/utils/filewriter.js +97 -0
- package/src/utils/intents.js +102 -0
- package/src/utils/logger.js +67 -0
- package/src/utils/math.js +142 -0
- package/src/utils/webfetch.js +332 -0
- package/src/utils/websearch.js +262 -0
- package/build.sh +0 -17
- package/src/providers/ollama.js +0 -89
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
const GOOGLE_SEARCH = 'https://www.google.com/search';
|
|
2
|
+
const STARTPAGE_SEARCH = 'https://www.startpage.com/sp/search';
|
|
3
|
+
const DDG_SEARCH = 'https://html.duckduckgo.com/html/';
|
|
4
|
+
const WIKI_SEARCH = 'https://en.wikipedia.org/w/api.php';
|
|
5
|
+
|
|
6
|
+
const UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36';
|
|
7
|
+
const GOOGLE_HEADERS = {
|
|
8
|
+
'User-Agent': UA,
|
|
9
|
+
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
|
10
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
11
|
+
'Sec-Fetch-Mode': 'navigate',
|
|
12
|
+
'Sec-Fetch-Site': 'none',
|
|
13
|
+
'Upgrade-Insecure-Requests': '1',
|
|
14
|
+
Cookie: 'CONSENT=YES+cb.20220419-07-p0.en+FX+700; SOCS=CAISEwgDEgk0NzU3NTA3MjQaBwgAARICIAA',
|
|
15
|
+
};
|
|
16
|
+
const STARTPAGE_HEADERS = {
|
|
17
|
+
'User-Agent': UA,
|
|
18
|
+
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
19
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function isBlockedBody(html) {
|
|
23
|
+
const s = String(html || '').slice(0, 5000).toLowerCase();
|
|
24
|
+
return /enablejs|sorry\/index|unusual traffic|recaptcha|captcha|proof.?of.?work|just a moment|browser check/.test(s);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isStartpageChallenge(body) {
|
|
28
|
+
return /challenge|"difficulty"|"issuedAt"/.test(String(body || '').slice(0, 3000));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function stripTags(text) {
|
|
32
|
+
const map = {
|
|
33
|
+
'&': '&', '<': '<', '>': '>', '"': '"',
|
|
34
|
+
''': "'", ''': "'", ''': "'", ' ': ' ',
|
|
35
|
+
''': "'", '…': '...', '–': '-', '’': "'",
|
|
36
|
+
};
|
|
37
|
+
return String(text)
|
|
38
|
+
.replace(/<[^>]*>/g, '')
|
|
39
|
+
.replace(/&[a-zA-Z0-9#]+;/g, (m) => map[m] ?? '')
|
|
40
|
+
.replace(/\s{2,}/g, ' ')
|
|
41
|
+
.trim();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function decodeGoogleUrl(url) {
|
|
45
|
+
if (!url) return url;
|
|
46
|
+
if (url.startsWith('/url?')) {
|
|
47
|
+
const m = url.match(/[?&]q=([^&]+)/);
|
|
48
|
+
return m ? safeDecode(m[1]) : url;
|
|
49
|
+
}
|
|
50
|
+
if (url.startsWith('/') ) return url;
|
|
51
|
+
return safeDecode(url);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function safeDecode(s) {
|
|
55
|
+
try {
|
|
56
|
+
return decodeURIComponent(String(s).replace(/\+/g, ' '));
|
|
57
|
+
} catch {
|
|
58
|
+
return String(s);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class WebSearch {
|
|
63
|
+
static async search(query, maxResults = 5) {
|
|
64
|
+
const results = await this._searchGoogle(query, maxResults);
|
|
65
|
+
if (results.length > 0) return results;
|
|
66
|
+
|
|
67
|
+
const startpage = await this._searchStartpage(query, maxResults);
|
|
68
|
+
if (startpage.length > 0) return startpage.map((r) => ({ ...r, source: 'google' }));
|
|
69
|
+
|
|
70
|
+
const ddg = await this._searchDuckDuckGo(query, maxResults);
|
|
71
|
+
if (ddg.length > 0) return ddg;
|
|
72
|
+
|
|
73
|
+
return this._searchWikipedia(query, maxResults);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static async _searchGoogle(query, maxResults) {
|
|
77
|
+
const url = `${GOOGLE_SEARCH}?q=${encodeURIComponent(query)}&num=${Math.max(maxResults, 5)}&hl=en&gl=us&udm=14`;
|
|
78
|
+
try {
|
|
79
|
+
const resp = await fetch(url, {
|
|
80
|
+
headers: GOOGLE_HEADERS,
|
|
81
|
+
signal: AbortSignal.timeout(12000),
|
|
82
|
+
redirect: 'follow',
|
|
83
|
+
});
|
|
84
|
+
if (!resp.ok) return [];
|
|
85
|
+
const html = await resp.text();
|
|
86
|
+
if (isBlockedBody(html) || isStartpageChallenge(html)) return [];
|
|
87
|
+
return this._parseGoogle(html, maxResults);
|
|
88
|
+
} catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static async _searchStartpage(query, maxResults) {
|
|
94
|
+
try {
|
|
95
|
+
const cookie = await this._startpageCookie();
|
|
96
|
+
const resp = await fetch(STARTPAGE_SEARCH, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: {
|
|
99
|
+
...STARTPAGE_HEADERS,
|
|
100
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
101
|
+
Cookie: cookie,
|
|
102
|
+
},
|
|
103
|
+
body: `query=${encodeURIComponent(query)}&cat=web&language=english&privacy_preference=0`,
|
|
104
|
+
signal: AbortSignal.timeout(12000),
|
|
105
|
+
redirect: 'follow',
|
|
106
|
+
});
|
|
107
|
+
if (!resp.ok) return [];
|
|
108
|
+
const html = await resp.text();
|
|
109
|
+
if (isBlockedBody(html) || isStartpageChallenge(html)) return [];
|
|
110
|
+
return this._parseStartpage(html, maxResults);
|
|
111
|
+
} catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static async _startpageCookie() {
|
|
117
|
+
try {
|
|
118
|
+
const home = await fetch('https://www.startpage.com/', {
|
|
119
|
+
headers: STARTPAGE_HEADERS,
|
|
120
|
+
signal: AbortSignal.timeout(8000),
|
|
121
|
+
});
|
|
122
|
+
const cookies = home.headers.getSetCookie ? home.headers.getSetCookie() : [home.headers.get('set-cookie')].filter(Boolean);
|
|
123
|
+
return cookies.map((c) => c.split(';')[0]).join('; ');
|
|
124
|
+
} catch {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static _parseGoogle(html, maxResults) {
|
|
130
|
+
const out = [];
|
|
131
|
+
const seen = new Set();
|
|
132
|
+
const resultRegex = /<a[^>]+href=["']([^"']+)["'][^>]*>(?:(?!<\/a>).)*?<h3[^>]*>(.*?)<\/h3>(?:(?!<\/a>).)*?<\/a>/gis;
|
|
133
|
+
let m;
|
|
134
|
+
while ((m = resultRegex.exec(html)) !== null && out.length < maxResults) {
|
|
135
|
+
let url = decodeGoogleUrl(m[1]);
|
|
136
|
+
if (!url || !/^https?:/.test(url)) continue;
|
|
137
|
+
const title = stripTags(m[2]);
|
|
138
|
+
if (!title || seen.has(url)) continue;
|
|
139
|
+
const after = html.slice(resultRegex.lastIndex, resultRegex.lastIndex + 2000);
|
|
140
|
+
const snippet = this._snippetAfter(after);
|
|
141
|
+
seen.add(url);
|
|
142
|
+
out.push({ title, url, snippet, source: 'google' });
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
static _snippetAfter(after) {
|
|
148
|
+
const vwi = after.match(/<div[^>]*class=["']VwiC3b[^"']*["'][^>]*>([\s\S]{0,500}?)<\/div>/);
|
|
149
|
+
if (vwi) {
|
|
150
|
+
const text = stripTags(vwi[1]);
|
|
151
|
+
if (text) return text.slice(0, 300);
|
|
152
|
+
}
|
|
153
|
+
const span = after.match(/<span[^>]*>([\s\S]{0,500}?)<\/span>/);
|
|
154
|
+
if (span) {
|
|
155
|
+
const text = stripTags(span[1]);
|
|
156
|
+
if (text) return text.slice(0, 300);
|
|
157
|
+
}
|
|
158
|
+
const byline = after.slice(0, 1200).replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
159
|
+
return byline.slice(0, 300);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static _parseStartpage(html, maxResults) {
|
|
163
|
+
const out = [];
|
|
164
|
+
const seen = new Set();
|
|
165
|
+
const linkRegex = /<a[^>]+href="((?:https?:)?\/\/[^"]+)"[^>]*>(?:(?!<\/a>).)*?<h3[^>]*>(.*?)<\/h3>(?:(?!<\/a>).)*?<\/a>/gis;
|
|
166
|
+
let m;
|
|
167
|
+
while ((m = linkRegex.exec(html)) !== null && out.length < maxResults) {
|
|
168
|
+
let url = m[1];
|
|
169
|
+
if (url.startsWith('//')) url = 'https:' + url;
|
|
170
|
+
if (!url.startsWith('http') || /startpage\.com|google\.com/.test(url)) continue;
|
|
171
|
+
const title = stripTags(m[2]);
|
|
172
|
+
if (!title || seen.has(url)) continue;
|
|
173
|
+
const after = html.slice(linkRegex.lastIndex);
|
|
174
|
+
const snippet = this._snippetAfter(after);
|
|
175
|
+
seen.add(url);
|
|
176
|
+
out.push({ title, url, snippet, source: 'google' });
|
|
177
|
+
}
|
|
178
|
+
if (out.length > 0) return out;
|
|
179
|
+
return this._parseStartpageOlder(html, maxResults);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
static _parseStartpageOlder(html, maxResults) {
|
|
183
|
+
const out = [];
|
|
184
|
+
const blocks = html.split(/<section class="w-gl__result"|class="result"/gi).slice(1);
|
|
185
|
+
for (const block of blocks.slice(0, maxResults)) {
|
|
186
|
+
const a = block.match(/href="((?:https?:)?\/\/[^"]+)"/);
|
|
187
|
+
const t = block.match(/<h3[^>]*>(.*?)<\/h3>/s);
|
|
188
|
+
if (!a || !t) continue;
|
|
189
|
+
let url = a[1];
|
|
190
|
+
if (url.startsWith('//')) url = 'https:' + url;
|
|
191
|
+
const snippet = stripTags(block).slice(0, 300);
|
|
192
|
+
out.push({ title: stripTags(t[1]), url, snippet, source: 'google' });
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
static async _searchDuckDuckGo(query, maxResults) {
|
|
198
|
+
const url = `${DDG_SEARCH}?q=${encodeURIComponent(query)}`;
|
|
199
|
+
try {
|
|
200
|
+
const resp = await fetch(url, {
|
|
201
|
+
headers: { 'User-Agent': UA },
|
|
202
|
+
signal: AbortSignal.timeout(10000),
|
|
203
|
+
});
|
|
204
|
+
if (!resp.ok) return [];
|
|
205
|
+
return this._parseDuckDuckGo(await resp.text(), maxResults);
|
|
206
|
+
} catch {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static _parseDuckDuckGo(html, maxResults) {
|
|
212
|
+
const results = [];
|
|
213
|
+
const resultRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>[\s\S]*?<a[^>]*class="result__snippet"[^>]*>(.*?)<\/a>/gi;
|
|
214
|
+
let match;
|
|
215
|
+
let count = 0;
|
|
216
|
+
while ((match = resultRegex.exec(html)) !== null && count < maxResults) {
|
|
217
|
+
let url = match[1];
|
|
218
|
+
const snippet = stripTags(match[3] || '');
|
|
219
|
+
const title = stripTags(match[2] || '');
|
|
220
|
+
url = this._decodeDdgUrl(url);
|
|
221
|
+
if (url && !url.includes('duckduckgo.com/y.js')) {
|
|
222
|
+
results.push({ title, url, snippet, source: 'duckduckgo' });
|
|
223
|
+
count++;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return results;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
static _decodeDdgUrl(url) {
|
|
230
|
+
const match = url.match(/uddg=([^&]+)/);
|
|
231
|
+
return match ? safeDecode(match[1]) : url;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
static async _searchWikipedia(query, maxResults) {
|
|
235
|
+
const params = new URLSearchParams({
|
|
236
|
+
action: 'query',
|
|
237
|
+
list: 'search',
|
|
238
|
+
srsearch: query,
|
|
239
|
+
srlimit: String(maxResults),
|
|
240
|
+
format: 'json',
|
|
241
|
+
utf8: '1',
|
|
242
|
+
});
|
|
243
|
+
const url = `${WIKI_SEARCH}?${params.toString()}`;
|
|
244
|
+
try {
|
|
245
|
+
const resp = await fetch(url, {
|
|
246
|
+
headers: { 'User-Agent': UA },
|
|
247
|
+
signal: AbortSignal.timeout(10000),
|
|
248
|
+
});
|
|
249
|
+
if (!resp.ok) return [];
|
|
250
|
+
const data = await resp.json();
|
|
251
|
+
const search = data?.query?.search || [];
|
|
252
|
+
return search.map((r) => ({
|
|
253
|
+
title: r.title,
|
|
254
|
+
url: `https://en.wikipedia.org/wiki/${encodeURIComponent(r.title.replace(/ /g, '_'))}`,
|
|
255
|
+
snippet: stripTags(r.snippet || ''),
|
|
256
|
+
source: 'wikipedia',
|
|
257
|
+
}));
|
|
258
|
+
} catch {
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
package/build.sh
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Build VierrataleAI Node.js version for packaging
|
|
3
|
-
set -e
|
|
4
|
-
|
|
5
|
-
cd "$(dirname "$0")"
|
|
6
|
-
|
|
7
|
-
echo "Installing dependencies..."
|
|
8
|
-
npm install
|
|
9
|
-
|
|
10
|
-
echo "Testing..."
|
|
11
|
-
node bin/vierrataleai.js --version
|
|
12
|
-
|
|
13
|
-
echo "Packaging..."
|
|
14
|
-
npm pack
|
|
15
|
-
|
|
16
|
-
echo "Build complete!"
|
|
17
|
-
echo "To publish: npm publish --tag beta"
|
package/src/providers/ollama.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { BaseProvider } from './base.js';
|
|
2
|
-
import { Catalog } from '../catalog.js';
|
|
3
|
-
import { Config } from '../config.js';
|
|
4
|
-
|
|
5
|
-
export class OllamaProvider extends BaseProvider {
|
|
6
|
-
constructor() {
|
|
7
|
-
super('ollama');
|
|
8
|
-
this.host = Config.get('ollamaHost');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async isAvailable() {
|
|
12
|
-
try {
|
|
13
|
-
const resp = await fetch(`${this.host}/api/tags`, { signal: AbortSignal.timeout(3000) });
|
|
14
|
-
return resp.ok;
|
|
15
|
-
} catch {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async listModels() {
|
|
21
|
-
try {
|
|
22
|
-
const resp = await fetch(`${this.host}/api/tags`);
|
|
23
|
-
if (!resp.ok) return [];
|
|
24
|
-
const data = await resp.json();
|
|
25
|
-
return (data.models || []).map((m) => ({
|
|
26
|
-
realName: m.name,
|
|
27
|
-
displayName: Catalog.getDisplayName(m.name),
|
|
28
|
-
size: m.size,
|
|
29
|
-
}));
|
|
30
|
-
} catch {
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async *stream(messages, options = {}) {
|
|
36
|
-
const model = Catalog.getRealModel(options.model || Config.get('model'));
|
|
37
|
-
const systemPrompt = options.systemPrompt || '';
|
|
38
|
-
|
|
39
|
-
const ollamaMessages = [];
|
|
40
|
-
if (systemPrompt) {
|
|
41
|
-
ollamaMessages.push({ role: 'system', content: systemPrompt });
|
|
42
|
-
}
|
|
43
|
-
for (const msg of messages) {
|
|
44
|
-
ollamaMessages.push({ role: msg.role, content: msg.content });
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const resp = await fetch(`${this.host}/api/chat`, {
|
|
48
|
-
method: 'POST',
|
|
49
|
-
headers: { 'Content-Type': 'application/json' },
|
|
50
|
-
body: JSON.stringify({
|
|
51
|
-
model,
|
|
52
|
-
messages: ollamaMessages,
|
|
53
|
-
stream: true,
|
|
54
|
-
options: {
|
|
55
|
-
num_ctx: Config.get('numCtx'),
|
|
56
|
-
temperature: options.temperature || Config.get('temperature'),
|
|
57
|
-
},
|
|
58
|
-
}),
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
if (!resp.ok) {
|
|
62
|
-
throw new Error('Failed to connect to backend engine');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const reader = resp.body.getReader();
|
|
66
|
-
const decoder = new TextDecoder();
|
|
67
|
-
let buffer = '';
|
|
68
|
-
|
|
69
|
-
while (true) {
|
|
70
|
-
const { done, value } = await reader.read();
|
|
71
|
-
if (done) break;
|
|
72
|
-
|
|
73
|
-
buffer += decoder.decode(value, { stream: true });
|
|
74
|
-
const lines = buffer.split('\n');
|
|
75
|
-
buffer = lines.pop() || '';
|
|
76
|
-
|
|
77
|
-
for (const line of lines) {
|
|
78
|
-
if (!line.trim()) continue;
|
|
79
|
-
try {
|
|
80
|
-
const json = JSON.parse(line);
|
|
81
|
-
if (json.message?.content) {
|
|
82
|
-
yield json.message.content;
|
|
83
|
-
}
|
|
84
|
-
if (json.done) return;
|
|
85
|
-
} catch {}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|