beast-agent 1.8.0 → 2.0.0

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.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +130 -128
  3. package/bin/beast-agent.js +137 -137
  4. package/package.json +1 -1
  5. package/scripts/fix-electron.js +138 -138
  6. package/scripts/release.js +137 -131
  7. package/scripts/swap-electron.js +40 -40
  8. package/src/agent/agentdefs.js +122 -122
  9. package/src/agent/bots.js +580 -580
  10. package/src/agent/bus.js +389 -389
  11. package/src/agent/computeruse.js +200 -200
  12. package/src/agent/config.js +284 -284
  13. package/src/agent/discord.js +332 -332
  14. package/src/agent/engine.js +85 -11
  15. package/src/agent/kb.js +123 -123
  16. package/src/agent/llm.js +430 -430
  17. package/src/agent/logger.js +90 -90
  18. package/src/agent/mcp.js +427 -0
  19. package/src/agent/mem0.js +605 -605
  20. package/src/agent/memory.js +427 -427
  21. package/src/agent/mqueue.js +124 -124
  22. package/src/agent/pdf.js +20 -20
  23. package/src/agent/research.js +133 -133
  24. package/src/agent/scripts/news.py +113 -113
  25. package/src/agent/scripts/stealthsearch.py +30 -30
  26. package/src/agent/scripts/websearch.py +225 -225
  27. package/src/agent/searxng.js +325 -325
  28. package/src/agent/seeds/brainstorming/SKILL.md +90 -90
  29. package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -120
  30. package/src/agent/seeds/executing-plans/SKILL.md +60 -60
  31. package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -167
  32. package/src/agent/seeds/systematic-debugging/SKILL.md +131 -131
  33. package/src/agent/seeds/test-driven-development/SKILL.md +152 -152
  34. package/src/agent/seeds/verification-before-completion/SKILL.md +63 -63
  35. package/src/agent/seeds/writing-plans/SKILL.md +162 -162
  36. package/src/agent/seeds/writing-skills/SKILL.md +229 -229
  37. package/src/agent/skills.js +652 -651
  38. package/src/agent/store.js +378 -378
  39. package/src/agent/telegram.js +155 -155
  40. package/src/agent/tokens.js +39 -39
  41. package/src/agent/usage.js +125 -125
  42. package/src/agent/watchers.js +312 -312
  43. package/src/agent/watext.js +80 -80
  44. package/src/agent/whatsapp.js +555 -555
  45. package/src/cron.js +255 -255
  46. package/src/main.js +385 -6
  47. package/src/preload.js +13 -0
  48. package/src/renderer/browserPreload.js +73 -73
  49. package/src/renderer/i18n.js +38 -0
  50. package/src/renderer/index.html +448 -407
  51. package/src/renderer/renderer.js +897 -42
  52. package/src/renderer/style.css +275 -11
@@ -1,284 +1,284 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
-
7
- /* Minimal indentation-based YAML parser, sufficient for Beast's config.yaml
8
- (nested maps, lists of scalars/maps, quoted/plain scalars, inline {} / []). */
9
-
10
- function beastDir() {
11
- if (process.env.BEAST_DATA) return process.env.BEAST_DATA;
12
- return process.env.APPDATA
13
- ? path.join(process.env.APPDATA, 'beast')
14
- : path.join(os.homedir(), 'AppData', 'Roaming', 'beast');
15
- }
16
-
17
- function parseScalar(raw) {
18
- let s = String(raw).trim();
19
- if (s === '{}' || s === '[]') return s === '{}' ? {} : [];
20
- if (
21
- (s.startsWith('"') && s.endsWith('"') && s.length > 1) ||
22
- (s.startsWith("'") && s.endsWith("'") && s.length > 1)
23
- ) {
24
- return s.slice(1, -1);
25
- }
26
- return s;
27
- }
28
-
29
- function stripComment(line) {
30
- let out = '';
31
- let inS = false;
32
- let inD = false;
33
- for (let i = 0; i < line.length; i++) {
34
- const c = line[i];
35
- if (c === "'" && !inD) inS = !inS;
36
- else if (c === '"' && !inS) inD = !inD;
37
- else if (c === '#' && !inS && !inD && (i === 0 || /\s/.test(line[i - 1]))) break;
38
- out += c;
39
- }
40
- return out.replace(/\s+$/, '');
41
- }
42
-
43
- function tokenize(text) {
44
- const recs = [];
45
- for (const rawLine of text.split(/\r?\n/)) {
46
- if (!rawLine.trim() || /^\s*#/.test(rawLine)) continue;
47
- const line = stripComment(rawLine);
48
- if (!line.trim()) continue;
49
- const indent = line.match(/^ */)[0].length;
50
- const content = line.trim();
51
- if (content.startsWith('- ') || content === '-') {
52
- recs.push({ indent, listItem: true, value: content === '-' ? '' : content.slice(2).trim() });
53
- } else {
54
- const m = content.match(/^([^:]+):\s*(.*)$/);
55
- if (!m) continue;
56
- recs.push({
57
- indent,
58
- listItem: false,
59
- key: m[1].trim().replace(/^["']|["']$/g, ''),
60
- value: m[2],
61
- });
62
- }
63
- }
64
- return recs;
65
- }
66
-
67
- function build(recs, startIdx, indent) {
68
- // Decide container kind from first record
69
- const first = recs[startIdx];
70
- if (!first) return [{}, startIdx];
71
- if (first.listItem) {
72
- const arr = [];
73
- let i = startIdx;
74
- while (i < recs.length && recs[i].indent >= indent && recs[i].listItem) {
75
- const r = recs[i];
76
- if (r.value === '') {
77
- // list of maps: subsequent deeper records belong to this item
78
- const deeper = [];
79
- let j = i + 1;
80
- while (j < recs.length && recs[j].indent > r.indent) {
81
- deeper.push(recs[j]);
82
- j++;
83
- }
84
- const [obj] = build(deeper, 0, deeper.length ? deeper[0].indent : 0);
85
- arr.push(obj && typeof obj === 'object' && !Array.isArray(obj) ? obj : {});
86
- i = j;
87
- } else if (/^([^:]+):\s*(.*)$/.test(r.value) && !/^https?:/.test(r.value)) {
88
- // inline "key: value" inside list item -> single-key map
89
- const m = r.value.match(/^([^:]+):\s*(.*)$/);
90
- const obj = { [m[1].trim().replace(/^["']|["']$/g, '')]: parseScalar(m[2]) };
91
- // absorb following deeper keys into same item
92
- let j = i + 1;
93
- while (j < recs.length && recs[j].indent > r.indent && !recs[j].listItem) {
94
- const rr = recs[j];
95
- obj[rr.key] = rr.value === '' ? null : parseScalar(rr.value);
96
- j++;
97
- }
98
- arr.push(obj);
99
- i = Math.max(i + 1, j);
100
- } else {
101
- arr.push(parseScalar(r.value));
102
- i++;
103
- }
104
- }
105
- return [arr, i];
106
- }
107
-
108
- const map = {};
109
- let i = startIdx;
110
- while (i < recs.length && recs[i].indent >= indent && !recs[i].listItem) {
111
- const r = recs[i];
112
- if (r.indent > indent) {
113
- i++; // stray deeper record, skip defensively
114
- continue;
115
- }
116
- if (r.value !== undefined && r.value.trim() !== '') {
117
- map[r.key] = parseScalar(r.value);
118
- i++;
119
- } else {
120
- // nested block: collect all deeper records
121
- const deeper = [];
122
- let j = i + 1;
123
- while (j < recs.length && recs[j].indent > r.indent) {
124
- deeper.push(recs[j]);
125
- j++;
126
- }
127
- if (deeper.length) {
128
- const [child, consumed] = build(deeper, 0, deeper[0].indent);
129
- map[r.key] = child;
130
- i = i + 1 + consumed;
131
- } else {
132
- map[r.key] = {};
133
- i++;
134
- }
135
- }
136
- }
137
- return [map, i - startIdx];
138
- }
139
-
140
- function parseYaml(text) {
141
- try {
142
- const recs = tokenize(text);
143
- const [out] = build(recs, 0, recs.length ? recs[0].indent : 0);
144
- return out || {};
145
- } catch {
146
- return {};
147
- }
148
- }
149
-
150
- function parseEnvFile(file) {
151
- const env = {};
152
- try {
153
- const text = fs.readFileSync(file, 'utf8');
154
- for (const line of text.split(/\r?\n/)) {
155
- const t = line.trim();
156
- if (!t || t.startsWith('#')) continue;
157
- const m = t.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
158
- if (!m) continue;
159
- let val = m[2].trim();
160
- if (
161
- (val.startsWith('"') && val.endsWith('"')) ||
162
- (val.startsWith("'") && val.endsWith("'"))
163
- ) {
164
- val = val.slice(1, -1);
165
- }
166
- env[m[1]] = val;
167
- }
168
- } catch {}
169
- return env;
170
- }
171
-
172
- function chatCompletionsUrl(base) {
173
- let b = String(base).trim().replace(/\/+$/, '');
174
- if (/\/v\d+$/.test(b)) return b + '/chat/completions';
175
- return b + '/v1/chat/completions';
176
- }
177
-
178
- function resolveProviders(cfg, env) {
179
- const candidates = [];
180
- for (const [id, p] of Object.entries(cfg.providers || {})) {
181
- if (!p || typeof p !== 'object' || Array.isArray(p) || !p.base_url) continue;
182
- let key = p.key_env ? env[p.key_env] : null;
183
- if (!key) key = env[`${id.toUpperCase().replace(/-/g, '_')}_API_KEY`];
184
- if (!key) continue;
185
- const models = new Set();
186
- if (p.model) models.add(p.model);
187
- for (const k of Object.keys(p.models || {})) models.add(k);
188
- if (models.size === 0 && cfg.model && cfg.model.default) models.add(cfg.model.default);
189
- candidates.push({
190
- id,
191
- name: p.name || id,
192
- baseUrl: p.base_url,
193
- url: chatCompletionsUrl(p.base_url),
194
- key,
195
- models: [...models],
196
- /* opsiyonel 1M-token fiyatları: maliyet sayacı için */
197
- costIn: Number(p.price_in) || null,
198
- costOut: Number(p.price_out) || null,
199
- });
200
- }
201
- return candidates;
202
- }
203
-
204
- const CFG_TEMPLATE = `# Beast Agent sağlayıcı yapılandırması
205
- # Anahtarlar .env dosyasında (key_env ile eşleşir). Örnek:
206
- #
207
- # providers:
208
- # zhipu:
209
- # name: Zhipu AI
210
- # base_url: https://api.z.ai/api/paas/v4
211
- # key_env: ZHIPU_API_KEY
212
- # models:
213
- # glm-4.6: {}
214
- #
215
- # model:
216
- # provider: zhipu # varsayılan provider id
217
- # default: glm-4.6 # varsayılan model
218
-
219
- providers: {}
220
- `;
221
-
222
- const ENV_TEMPLATE = `# Beast Agent API anahtarları (Bu dosyayı kimseyle paylaşma!)
223
- # Provider'ın key_env alanıyla eşleşmeli; yoksa <PROVIDER>_API_KEY denenir.
224
- # Örnek:
225
- # ZHIPU_API_KEY=xxxxx
226
- # OPENAI_API_KEY=sk-xxx
227
- `;
228
-
229
- function loadBeastConfig() {
230
- const dataDir = beastDir();
231
- try {
232
- fs.mkdirSync(dataDir, { recursive: true });
233
- } catch {}
234
- const cfgPath = path.join(dataDir, 'config.yaml');
235
- const envPath = path.join(dataDir, '.env');
236
-
237
- try {
238
- if (!fs.existsSync(cfgPath)) fs.writeFileSync(cfgPath, CFG_TEMPLATE);
239
- if (!fs.existsSync(envPath)) fs.writeFileSync(envPath, ENV_TEMPLATE);
240
- } catch {}
241
-
242
- let cfg = {};
243
- try {
244
- cfg = parseYaml(fs.readFileSync(cfgPath, 'utf8'));
245
- } catch {}
246
-
247
- const env = { ...parseEnvFile(envPath) };
248
- for (const [k, v] of Object.entries(process.env)) {
249
- if (!(k in env) && typeof v === 'string') env[k] = v;
250
- }
251
-
252
- const providers = resolveProviders(cfg, env);
253
- const m = cfg.model || {};
254
-
255
- let active = m.provider ? providers.find((p) => p.id === m.provider) : null;
256
- if (!active && providers.length) active = providers[0];
257
-
258
- let activeSelection = null;
259
- if (active) {
260
- const keyOverride = m.key_env ? env[m.key_env] : null;
261
- activeSelection = {
262
- providerId: active.id,
263
- providerName: active.name,
264
- model: m.default || active.models[0],
265
- url: m.base_url ? chatCompletionsUrl(m.base_url) : active.url,
266
- key: keyOverride || active.key,
267
- costIn: active.costIn,
268
- costOut: active.costOut,
269
- };
270
- }
271
-
272
- const chain = [];
273
- if (activeSelection) chain.push(activeSelection);
274
- for (const p of providers) {
275
- if (active && p.id === active.id) continue;
276
- for (const model of p.models.slice(0, 3)) {
277
- chain.push({ providerId: p.id, providerName: p.name, model, url: p.url, key: p.key, costIn: p.costIn, costOut: p.costOut });
278
- }
279
- }
280
-
281
- return { dataDir, configPath: cfgPath, envPath, providers, chain, defaultSelection: activeSelection || chain[0] || null };
282
- }
283
-
284
- module.exports = { parseYaml, parseEnvFile, loadBeastConfig, beastDir, chatCompletionsUrl };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ /* Minimal indentation-based YAML parser, sufficient for Beast's config.yaml
8
+ (nested maps, lists of scalars/maps, quoted/plain scalars, inline {} / []). */
9
+
10
+ function beastDir() {
11
+ if (process.env.BEAST_DATA) return process.env.BEAST_DATA;
12
+ return process.env.APPDATA
13
+ ? path.join(process.env.APPDATA, 'beast')
14
+ : path.join(os.homedir(), 'AppData', 'Roaming', 'beast');
15
+ }
16
+
17
+ function parseScalar(raw) {
18
+ let s = String(raw).trim();
19
+ if (s === '{}' || s === '[]') return s === '{}' ? {} : [];
20
+ if (
21
+ (s.startsWith('"') && s.endsWith('"') && s.length > 1) ||
22
+ (s.startsWith("'") && s.endsWith("'") && s.length > 1)
23
+ ) {
24
+ return s.slice(1, -1);
25
+ }
26
+ return s;
27
+ }
28
+
29
+ function stripComment(line) {
30
+ let out = '';
31
+ let inS = false;
32
+ let inD = false;
33
+ for (let i = 0; i < line.length; i++) {
34
+ const c = line[i];
35
+ if (c === "'" && !inD) inS = !inS;
36
+ else if (c === '"' && !inS) inD = !inD;
37
+ else if (c === '#' && !inS && !inD && (i === 0 || /\s/.test(line[i - 1]))) break;
38
+ out += c;
39
+ }
40
+ return out.replace(/\s+$/, '');
41
+ }
42
+
43
+ function tokenize(text) {
44
+ const recs = [];
45
+ for (const rawLine of text.split(/\r?\n/)) {
46
+ if (!rawLine.trim() || /^\s*#/.test(rawLine)) continue;
47
+ const line = stripComment(rawLine);
48
+ if (!line.trim()) continue;
49
+ const indent = line.match(/^ */)[0].length;
50
+ const content = line.trim();
51
+ if (content.startsWith('- ') || content === '-') {
52
+ recs.push({ indent, listItem: true, value: content === '-' ? '' : content.slice(2).trim() });
53
+ } else {
54
+ const m = content.match(/^([^:]+):\s*(.*)$/);
55
+ if (!m) continue;
56
+ recs.push({
57
+ indent,
58
+ listItem: false,
59
+ key: m[1].trim().replace(/^["']|["']$/g, ''),
60
+ value: m[2],
61
+ });
62
+ }
63
+ }
64
+ return recs;
65
+ }
66
+
67
+ function build(recs, startIdx, indent) {
68
+ // Decide container kind from first record
69
+ const first = recs[startIdx];
70
+ if (!first) return [{}, startIdx];
71
+ if (first.listItem) {
72
+ const arr = [];
73
+ let i = startIdx;
74
+ while (i < recs.length && recs[i].indent >= indent && recs[i].listItem) {
75
+ const r = recs[i];
76
+ if (r.value === '') {
77
+ // list of maps: subsequent deeper records belong to this item
78
+ const deeper = [];
79
+ let j = i + 1;
80
+ while (j < recs.length && recs[j].indent > r.indent) {
81
+ deeper.push(recs[j]);
82
+ j++;
83
+ }
84
+ const [obj] = build(deeper, 0, deeper.length ? deeper[0].indent : 0);
85
+ arr.push(obj && typeof obj === 'object' && !Array.isArray(obj) ? obj : {});
86
+ i = j;
87
+ } else if (/^([^:]+):\s*(.*)$/.test(r.value) && !/^https?:/.test(r.value)) {
88
+ // inline "key: value" inside list item -> single-key map
89
+ const m = r.value.match(/^([^:]+):\s*(.*)$/);
90
+ const obj = { [m[1].trim().replace(/^["']|["']$/g, '')]: parseScalar(m[2]) };
91
+ // absorb following deeper keys into same item
92
+ let j = i + 1;
93
+ while (j < recs.length && recs[j].indent > r.indent && !recs[j].listItem) {
94
+ const rr = recs[j];
95
+ obj[rr.key] = rr.value === '' ? null : parseScalar(rr.value);
96
+ j++;
97
+ }
98
+ arr.push(obj);
99
+ i = Math.max(i + 1, j);
100
+ } else {
101
+ arr.push(parseScalar(r.value));
102
+ i++;
103
+ }
104
+ }
105
+ return [arr, i];
106
+ }
107
+
108
+ const map = {};
109
+ let i = startIdx;
110
+ while (i < recs.length && recs[i].indent >= indent && !recs[i].listItem) {
111
+ const r = recs[i];
112
+ if (r.indent > indent) {
113
+ i++; // stray deeper record, skip defensively
114
+ continue;
115
+ }
116
+ if (r.value !== undefined && r.value.trim() !== '') {
117
+ map[r.key] = parseScalar(r.value);
118
+ i++;
119
+ } else {
120
+ // nested block: collect all deeper records
121
+ const deeper = [];
122
+ let j = i + 1;
123
+ while (j < recs.length && recs[j].indent > r.indent) {
124
+ deeper.push(recs[j]);
125
+ j++;
126
+ }
127
+ if (deeper.length) {
128
+ const [child, consumed] = build(deeper, 0, deeper[0].indent);
129
+ map[r.key] = child;
130
+ i = i + 1 + consumed;
131
+ } else {
132
+ map[r.key] = {};
133
+ i++;
134
+ }
135
+ }
136
+ }
137
+ return [map, i - startIdx];
138
+ }
139
+
140
+ function parseYaml(text) {
141
+ try {
142
+ const recs = tokenize(text);
143
+ const [out] = build(recs, 0, recs.length ? recs[0].indent : 0);
144
+ return out || {};
145
+ } catch {
146
+ return {};
147
+ }
148
+ }
149
+
150
+ function parseEnvFile(file) {
151
+ const env = {};
152
+ try {
153
+ const text = fs.readFileSync(file, 'utf8');
154
+ for (const line of text.split(/\r?\n/)) {
155
+ const t = line.trim();
156
+ if (!t || t.startsWith('#')) continue;
157
+ const m = t.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
158
+ if (!m) continue;
159
+ let val = m[2].trim();
160
+ if (
161
+ (val.startsWith('"') && val.endsWith('"')) ||
162
+ (val.startsWith("'") && val.endsWith("'"))
163
+ ) {
164
+ val = val.slice(1, -1);
165
+ }
166
+ env[m[1]] = val;
167
+ }
168
+ } catch {}
169
+ return env;
170
+ }
171
+
172
+ function chatCompletionsUrl(base) {
173
+ let b = String(base).trim().replace(/\/+$/, '');
174
+ if (/\/v\d+$/.test(b)) return b + '/chat/completions';
175
+ return b + '/v1/chat/completions';
176
+ }
177
+
178
+ function resolveProviders(cfg, env) {
179
+ const candidates = [];
180
+ for (const [id, p] of Object.entries(cfg.providers || {})) {
181
+ if (!p || typeof p !== 'object' || Array.isArray(p) || !p.base_url) continue;
182
+ let key = p.key_env ? env[p.key_env] : null;
183
+ if (!key) key = env[`${id.toUpperCase().replace(/-/g, '_')}_API_KEY`];
184
+ if (!key) continue;
185
+ const models = new Set();
186
+ if (p.model) models.add(p.model);
187
+ for (const k of Object.keys(p.models || {})) models.add(k);
188
+ if (models.size === 0 && cfg.model && cfg.model.default) models.add(cfg.model.default);
189
+ candidates.push({
190
+ id,
191
+ name: p.name || id,
192
+ baseUrl: p.base_url,
193
+ url: chatCompletionsUrl(p.base_url),
194
+ key,
195
+ models: [...models],
196
+ /* opsiyonel 1M-token fiyatları: maliyet sayacı için */
197
+ costIn: Number(p.price_in) || null,
198
+ costOut: Number(p.price_out) || null,
199
+ });
200
+ }
201
+ return candidates;
202
+ }
203
+
204
+ const CFG_TEMPLATE = `# Beast Agent sağlayıcı yapılandırması
205
+ # Anahtarlar .env dosyasında (key_env ile eşleşir). Örnek:
206
+ #
207
+ # providers:
208
+ # zhipu:
209
+ # name: Zhipu AI
210
+ # base_url: https://api.z.ai/api/paas/v4
211
+ # key_env: ZHIPU_API_KEY
212
+ # models:
213
+ # glm-4.6: {}
214
+ #
215
+ # model:
216
+ # provider: zhipu # varsayılan provider id
217
+ # default: glm-4.6 # varsayılan model
218
+
219
+ providers: {}
220
+ `;
221
+
222
+ const ENV_TEMPLATE = `# Beast Agent API anahtarları (Bu dosyayı kimseyle paylaşma!)
223
+ # Provider'ın key_env alanıyla eşleşmeli; yoksa <PROVIDER>_API_KEY denenir.
224
+ # Örnek:
225
+ # ZHIPU_API_KEY=xxxxx
226
+ # OPENAI_API_KEY=sk-xxx
227
+ `;
228
+
229
+ function loadBeastConfig() {
230
+ const dataDir = beastDir();
231
+ try {
232
+ fs.mkdirSync(dataDir, { recursive: true });
233
+ } catch {}
234
+ const cfgPath = path.join(dataDir, 'config.yaml');
235
+ const envPath = path.join(dataDir, '.env');
236
+
237
+ try {
238
+ if (!fs.existsSync(cfgPath)) fs.writeFileSync(cfgPath, CFG_TEMPLATE);
239
+ if (!fs.existsSync(envPath)) fs.writeFileSync(envPath, ENV_TEMPLATE);
240
+ } catch {}
241
+
242
+ let cfg = {};
243
+ try {
244
+ cfg = parseYaml(fs.readFileSync(cfgPath, 'utf8'));
245
+ } catch {}
246
+
247
+ const env = { ...parseEnvFile(envPath) };
248
+ for (const [k, v] of Object.entries(process.env)) {
249
+ if (!(k in env) && typeof v === 'string') env[k] = v;
250
+ }
251
+
252
+ const providers = resolveProviders(cfg, env);
253
+ const m = cfg.model || {};
254
+
255
+ let active = m.provider ? providers.find((p) => p.id === m.provider) : null;
256
+ if (!active && providers.length) active = providers[0];
257
+
258
+ let activeSelection = null;
259
+ if (active) {
260
+ const keyOverride = m.key_env ? env[m.key_env] : null;
261
+ activeSelection = {
262
+ providerId: active.id,
263
+ providerName: active.name,
264
+ model: m.default || active.models[0],
265
+ url: m.base_url ? chatCompletionsUrl(m.base_url) : active.url,
266
+ key: keyOverride || active.key,
267
+ costIn: active.costIn,
268
+ costOut: active.costOut,
269
+ };
270
+ }
271
+
272
+ const chain = [];
273
+ if (activeSelection) chain.push(activeSelection);
274
+ for (const p of providers) {
275
+ if (active && p.id === active.id) continue;
276
+ for (const model of p.models.slice(0, 3)) {
277
+ chain.push({ providerId: p.id, providerName: p.name, model, url: p.url, key: p.key, costIn: p.costIn, costOut: p.costOut });
278
+ }
279
+ }
280
+
281
+ return { dataDir, configPath: cfgPath, envPath, providers, chain, defaultSelection: activeSelection || chain[0] || null };
282
+ }
283
+
284
+ module.exports = { parseYaml, parseEnvFile, loadBeastConfig, beastDir, chatCompletionsUrl };