@vierratale/ai 0.1.0-beta.15 → 0.1.0-beta.16
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 +24 -145
- package/bin/vierrataleai.js +1 -76
- package/build.sh +17 -0
- package/package.json +16 -22
- package/scripts/postinstall.js +2 -2
- package/src/catalog.js +48 -116
- package/src/cli.js +122 -1008
- package/src/config.js +10 -26
- package/src/index.js +1 -2
- package/src/installer.js +105 -0
- package/src/prompts/system.json +2 -2
- package/src/prompts/system.md +0 -42
- package/src/providers/anthropic.js +34 -65
- package/src/providers/base.js +0 -10
- package/src/providers/cortex.js +40 -223
- package/src/providers/gemini.js +31 -62
- package/src/providers/openai.js +33 -64
- package/src/session.js +15 -195
- package/src/ui/branding.js +1 -1
- package/src/utils/intents.js +1 -61
- package/src/utils/webfetch.js +24 -576
- package/src/utils/websearch.js +44 -213
- package/LICENSE +0 -21
- package/src/cmd/agent.js +0 -1825
- package/src/cmd/executor.js +0 -271
- package/src/cmd/todos.js +0 -64
- package/src/cmd/tools.js +0 -204
- package/src/engine.js +0 -150
- package/src/ui/chatbox.js +0 -280
- package/src/ui/input.js +0 -501
- package/src/utils/downloader.js +0 -364
- package/src/utils/filetree.js +0 -44
- package/src/utils/filewriter.js +0 -97
- package/src/utils/logger.js +0 -67
- package/src/utils/math.js +0 -142
package/src/config.js
CHANGED
|
@@ -1,34 +1,24 @@
|
|
|
1
1
|
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
-
import { Catalog } from './catalog.js';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
? join(process.env.VIERRATALE_CONFIG_DIR)
|
|
9
|
-
: join(homedir(), '.config', 'vierrataleai');
|
|
10
|
-
return { dir, file: join(dir, 'config.json') };
|
|
11
|
-
}
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.config', 'vierrataleai');
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
12
7
|
|
|
13
8
|
const DEFAULTS = {
|
|
14
9
|
provider: 'auto',
|
|
15
|
-
model: '
|
|
10
|
+
model: 'vierratale-fast',
|
|
16
11
|
engineHost: 'http://127.0.0.1:11434',
|
|
17
12
|
numCtx: 2048,
|
|
18
13
|
temperature: 0.7,
|
|
19
14
|
maxTokens: 4096,
|
|
20
15
|
keepAlive: '5m',
|
|
21
|
-
numThreads: 8,
|
|
22
|
-
commandTimeoutMs: 60000,
|
|
23
|
-
planTimeoutMs: 45000,
|
|
24
|
-
downloadDir: join(homedir(), 'Downloads'),
|
|
25
16
|
};
|
|
26
17
|
|
|
27
18
|
function loadConfigFile() {
|
|
28
19
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const raw = readFileSync(file, 'utf-8');
|
|
20
|
+
if (existsSync(CONFIG_FILE)) {
|
|
21
|
+
const raw = readFileSync(CONFIG_FILE, 'utf-8');
|
|
32
22
|
return JSON.parse(raw);
|
|
33
23
|
}
|
|
34
24
|
} catch {}
|
|
@@ -54,15 +44,11 @@ export const Config = {
|
|
|
54
44
|
temperature: parseFloat(getEnv('VIERRATALE_TEMPERATURE', String(file.temperature ?? DEFAULTS.temperature))),
|
|
55
45
|
maxTokens: parseInt(getEnv('VIERRATALE_MAX_TOKENS', String(file.maxTokens ?? DEFAULTS.maxTokens))),
|
|
56
46
|
keepAlive: getEnv('VIERRATALE_KEEP_ALIVE', file.keepAlive ?? DEFAULTS.keepAlive),
|
|
57
|
-
numThreads: parseInt(getEnv('VIERRATALE_NUM_THREADS', String(file.numThreads ?? DEFAULTS.numThreads))),
|
|
58
47
|
openaiApiKey: getEnv('OPENAI_API_KEY', file.openaiApiKey || ''),
|
|
59
48
|
anthropicApiKey: getEnv('ANTHROPIC_API_KEY', file.anthropicApiKey || ''),
|
|
60
49
|
geminiApiKey: getEnv('GEMINI_API_KEY', file.geminiApiKey || ''),
|
|
61
50
|
systemPrompt: file.systemPrompt || '',
|
|
62
51
|
providerModels: file.providerModels || {},
|
|
63
|
-
downloadDir: getEnv('VIERRATALE_DOWNLOAD_DIR', file.downloadDir ?? DEFAULTS.downloadDir),
|
|
64
|
-
commandTimeoutMs: parseInt(getEnv('VIERRATALE_COMMAND_TIMEOUT_MS', String(file.commandTimeoutMs ?? DEFAULTS.commandTimeoutMs))),
|
|
65
|
-
planTimeoutMs: parseInt(getEnv('VIERRATALE_PLAN_TIMEOUT_MS', String(file.planTimeoutMs ?? DEFAULTS.planTimeoutMs))),
|
|
66
52
|
};
|
|
67
53
|
return this._config;
|
|
68
54
|
},
|
|
@@ -90,22 +76,20 @@ export const Config = {
|
|
|
90
76
|
},
|
|
91
77
|
|
|
92
78
|
getEffectiveModel(provider) {
|
|
93
|
-
|
|
94
|
-
return Catalog.normalize(raw);
|
|
79
|
+
return this.getProviderModel(provider) || this.get('model');
|
|
95
80
|
},
|
|
96
81
|
|
|
97
82
|
save(overrides = {}) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
mkdirSync(dir, { recursive: true });
|
|
83
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
84
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
101
85
|
}
|
|
102
86
|
const current = this._config || DEFAULTS;
|
|
103
87
|
const merged = { ...current, ...overrides };
|
|
104
|
-
writeFileSync(
|
|
88
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
|
|
105
89
|
this._config = merged;
|
|
106
90
|
},
|
|
107
91
|
|
|
108
92
|
getConfigDir() {
|
|
109
|
-
return
|
|
93
|
+
return CONFIG_DIR;
|
|
110
94
|
},
|
|
111
95
|
};
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export { Catalog } from './catalog.js';
|
|
2
2
|
export { Config } from './config.js';
|
|
3
|
-
export {
|
|
3
|
+
export { Installer } from './installer.js';
|
|
4
4
|
export { CortexProvider } from './providers/cortex.js';
|
|
5
5
|
export { OpenAIProvider } from './providers/openai.js';
|
|
6
6
|
export { Branding } from './ui/branding.js';
|
|
7
|
-
export { FileWriter } from './utils/filewriter.js';
|
package/src/installer.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { execSync, exec } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { Catalog } from './catalog.js';
|
|
4
|
+
import { Config } from './config.js';
|
|
5
|
+
|
|
6
|
+
function runSilent(cmd) {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
exec(cmd, { stdio: 'ignore' }, (err) => resolve(!err));
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isEngineRunning(host) {
|
|
13
|
+
return fetch(`${host}/api/tags`, { signal: AbortSignal.timeout(3000) })
|
|
14
|
+
.then((r) => r.ok)
|
|
15
|
+
.catch(() => false);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isEngineInstalled() {
|
|
19
|
+
try {
|
|
20
|
+
execSync('which ollama 2>/dev/null', { stdio: 'ignore' });
|
|
21
|
+
return true;
|
|
22
|
+
} catch {}
|
|
23
|
+
const paths = ['/usr/local/bin/ollama', '/usr/bin/ollama'];
|
|
24
|
+
return paths.some((p) => existsSync(p));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function installEngine() {
|
|
28
|
+
await runSilent('curl -fsSL https://ollama.com/install.sh | sh');
|
|
29
|
+
return isEngineInstalled();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function pullModel(host, model) {
|
|
33
|
+
try {
|
|
34
|
+
const resp = await fetch(`${host}/api/pull`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37
|
+
body: JSON.stringify({ name: model, stream: false }),
|
|
38
|
+
signal: AbortSignal.timeout(600000),
|
|
39
|
+
});
|
|
40
|
+
return resp.ok;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function getInstalledModels(host) {
|
|
47
|
+
try {
|
|
48
|
+
const resp = await fetch(`${host}/api/tags`);
|
|
49
|
+
if (!resp.ok) return [];
|
|
50
|
+
const data = await resp.json();
|
|
51
|
+
return (data.models || []).map((m) => m.name);
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const Installer = {
|
|
58
|
+
async pullModel(host, model) {
|
|
59
|
+
return pullModel(host, model);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async getInstalledModels(host) {
|
|
63
|
+
return getInstalledModels(host);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async ensure() {
|
|
67
|
+
const host = Config.get('engineHost');
|
|
68
|
+
|
|
69
|
+
if (!isEngineInstalled()) {
|
|
70
|
+
await installEngine();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const running = await isEngineRunning(host);
|
|
74
|
+
if (!running) {
|
|
75
|
+
try {
|
|
76
|
+
// No systemd available here, so launch the daemon directly, detached,
|
|
77
|
+
// and poll until it responds instead of relying on a service manager.
|
|
78
|
+
const child = exec('ollama serve', {
|
|
79
|
+
stdio: 'ignore',
|
|
80
|
+
detached: true,
|
|
81
|
+
});
|
|
82
|
+
if (child.unref) child.unref();
|
|
83
|
+
for (let i = 0; i < 30; i++) {
|
|
84
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
85
|
+
if (await isEngineRunning(host)) break;
|
|
86
|
+
}
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const realModel = Catalog.getRealModel(Config.get('model'));
|
|
91
|
+
const installed = await getInstalledModels(host);
|
|
92
|
+
const needsPull = !installed.includes(realModel);
|
|
93
|
+
|
|
94
|
+
if (needsPull) {
|
|
95
|
+
await pullModel(host, realModel);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return true;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async isReady() {
|
|
102
|
+
const host = Config.get('engineHost');
|
|
103
|
+
return isEngineRunning(host);
|
|
104
|
+
},
|
|
105
|
+
};
|
package/src/prompts/system.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
{
|
|
2
|
-
"system": "You are VierrataleAI, an intelligent AI assistant created by Vierratale. You are helpful, concise, and accurate. You provide clear answers and help users with their questions and tasks. When asked about your identity, always respond that you are VierrataleAI, an AI assistant created by Vierratale.
|
|
3
|
-
}
|
|
2
|
+
"system": "You are VierrataleAI, an intelligent AI assistant created by Vierratale. You are helpful, concise, and accurate. You provide clear answers and help users with their questions and tasks. When asked about your identity, always respond that you are VierrataleAI, an AI assistant created by Vierratale."
|
|
3
|
+
}
|
package/src/prompts/system.md
CHANGED
|
@@ -13,45 +13,3 @@ You are **VierrataleAI**, an intelligent AI assistant created by Vierratale.
|
|
|
13
13
|
- Help users with tasks including coding, writing, analysis, and general knowledge.
|
|
14
14
|
- If you don't know something, say so honestly.
|
|
15
15
|
- Use markdown formatting when appropriate for readability.
|
|
16
|
-
|
|
17
|
-
## Writing Files and Folders
|
|
18
|
-
|
|
19
|
-
You CAN write files and folders directly to the user's disk. When the user asks
|
|
20
|
-
you to create, save, or generate code, configs, scripts, projects, or any other
|
|
21
|
-
files, DO NOT refuse — answer with the format below. Use this exact format for
|
|
22
|
-
EACH file:
|
|
23
|
-
|
|
24
|
-
FILE: relative/path/to/file.js
|
|
25
|
-
```js
|
|
26
|
-
// file content here
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
This works for any file type, including JSON and HTML. For example, write a
|
|
30
|
-
config file exactly like this:
|
|
31
|
-
|
|
32
|
-
FILE: config.json
|
|
33
|
-
```json
|
|
34
|
-
{
|
|
35
|
-
"name": "my-app",
|
|
36
|
-
"port": 3000
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
And a web page like this:
|
|
41
|
-
|
|
42
|
-
FILE: index.html
|
|
43
|
-
```html
|
|
44
|
-
<!doctype html>
|
|
45
|
-
<html>
|
|
46
|
-
<head><title>My Page</title></head>
|
|
47
|
-
<body><h1>Hello</h1></body>
|
|
48
|
-
</html>
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Rules:
|
|
52
|
-
- Put `FILE: <path>` on its own line, immediately followed by a markdown code fence containing the file content. No text between the header and the fence.
|
|
53
|
-
- Use paths relative to the user's working directory (e.g. `src/app.js`, `config.json`).
|
|
54
|
-
- Emit one `FILE:` block per file; nested folders are created automatically.
|
|
55
|
-
- If you need an empty folder, emit `FOLDER: <path>` on its own line.
|
|
56
|
-
- When asked for a story, folk tale, legend, or myth, give the real, well-known version — use the web search results you were given when available — and also save the full story to a .txt file using `FILE: <title>.txt` followed by a ```text fence.
|
|
57
|
-
- Keep any explanation short and outside of the `FILE:`/`FOLDER:` blocks.
|
|
@@ -39,7 +39,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
39
39
|
|
|
40
40
|
async *stream(messages, options = {}) {
|
|
41
41
|
const key = Config.get('anthropicApiKey');
|
|
42
|
-
if (!key) throw new Error('
|
|
42
|
+
if (!key) throw new Error('Anthropic API key not configured');
|
|
43
43
|
|
|
44
44
|
const model = this._resolveModel(options);
|
|
45
45
|
const systemPrompt = options.systemPrompt || '';
|
|
@@ -51,80 +51,49 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
51
51
|
content: m.content,
|
|
52
52
|
}));
|
|
53
53
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
max_tokens: options.maxTokens || Config.get('maxTokens'),
|
|
71
|
-
stream: true,
|
|
72
|
-
temperature: options.temperature || Config.get('temperature'),
|
|
73
|
-
}),
|
|
74
|
-
signal: controller.signal,
|
|
75
|
-
});
|
|
76
|
-
} catch (err) {
|
|
77
|
-
if (err.name === 'AbortError') {
|
|
78
|
-
throw new Error('[ERR-0001] Could not reach the API (no response within 60s). Check your network connection.');
|
|
79
|
-
}
|
|
80
|
-
throw new Error('[ERR-0001] Could not reach the API (no response). Check your network connection.');
|
|
81
|
-
} finally {
|
|
82
|
-
clearTimeout(connectTimer);
|
|
83
|
-
}
|
|
54
|
+
const resp = await fetch(ANTHROPIC_URL, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
'x-api-key': key,
|
|
59
|
+
'anthropic-version': '2023-06-01',
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({
|
|
62
|
+
model,
|
|
63
|
+
system: system.length ? system : undefined,
|
|
64
|
+
messages: apiMessages,
|
|
65
|
+
max_tokens: options.maxTokens || Config.get('maxTokens'),
|
|
66
|
+
stream: true,
|
|
67
|
+
temperature: options.temperature || Config.get('temperature'),
|
|
68
|
+
}),
|
|
69
|
+
});
|
|
84
70
|
|
|
85
71
|
if (!resp.ok) {
|
|
86
|
-
throw new Error('
|
|
72
|
+
throw new Error('Failed to connect to cloud engine');
|
|
87
73
|
}
|
|
88
74
|
|
|
89
75
|
const reader = resp.body.getReader();
|
|
90
76
|
const decoder = new TextDecoder();
|
|
91
77
|
let buffer = '';
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
78
|
+
|
|
79
|
+
while (true) {
|
|
80
|
+
const { done, value } = await reader.read();
|
|
81
|
+
if (done) break;
|
|
82
|
+
|
|
83
|
+
buffer += decoder.decode(value, { stream: true });
|
|
84
|
+
const lines = buffer.split('\n');
|
|
85
|
+
buffer = lines.pop() || '';
|
|
86
|
+
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
if (!line.startsWith('data: ')) continue;
|
|
89
|
+
const data = line.slice(6);
|
|
101
90
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
throw new Error('[ERR-0001] Lost the connection to the cloud API (no data for 60s).');
|
|
91
|
+
const json = JSON.parse(data);
|
|
92
|
+
if (json.type === 'content_block_delta' && json.delta?.text) {
|
|
93
|
+
yield json.delta.text;
|
|
106
94
|
}
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
if (done) break;
|
|
110
|
-
settle();
|
|
111
|
-
buffer += decoder.decode(value, { stream: true });
|
|
112
|
-
const lines = buffer.split('\n');
|
|
113
|
-
buffer = lines.pop() || '';
|
|
114
|
-
|
|
115
|
-
for (const line of lines) {
|
|
116
|
-
if (!line.startsWith('data: ')) continue;
|
|
117
|
-
const data = line.slice(6);
|
|
118
|
-
try {
|
|
119
|
-
const json = JSON.parse(data);
|
|
120
|
-
if (json.type === 'content_block_delta' && json.delta?.text) {
|
|
121
|
-
yield json.delta.text;
|
|
122
|
-
}
|
|
123
|
-
} catch {}
|
|
124
|
-
}
|
|
95
|
+
} catch {}
|
|
125
96
|
}
|
|
126
|
-
} finally {
|
|
127
|
-
if (idle) clearTimeout(idle);
|
|
128
97
|
}
|
|
129
98
|
}
|
|
130
99
|
}
|
package/src/providers/base.js
CHANGED
|
@@ -15,16 +15,6 @@ export class BaseProvider {
|
|
|
15
15
|
throw new Error('stream() must be implemented');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// Non-streaming convenience: collects the stream. Providers may override
|
|
19
|
-
// with a proper single-shot request (see CortexProvider).
|
|
20
|
-
async complete(messages, options = {}) {
|
|
21
|
-
let out = '';
|
|
22
|
-
for await (const chunk of this.stream(messages, options)) {
|
|
23
|
-
out += chunk;
|
|
24
|
-
}
|
|
25
|
-
return out;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
18
|
async listModels() {
|
|
29
19
|
throw new Error('listModels() must be implemented');
|
|
30
20
|
}
|