@p-sw/brainbox 0.1.2-alpha.1 → 0.1.2-alpha.11
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/dist/index.js +776 -363
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,12 +4,17 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { readFileSync as readFileSync2 } from "fs";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
|
-
import { dirname as
|
|
7
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
8
8
|
|
|
9
9
|
// src/utils/logger.ts
|
|
10
10
|
import chalk from "chalk";
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
existsSync,
|
|
13
|
+
mkdirSync,
|
|
14
|
+
createWriteStream,
|
|
15
|
+
writeFileSync
|
|
16
|
+
} from "fs";
|
|
17
|
+
import { join } from "path";
|
|
13
18
|
var LEVELS = {
|
|
14
19
|
debug: { rank: 0, color: chalk.gray, stderr: false },
|
|
15
20
|
info: { rank: 1, color: chalk.blue, stderr: false },
|
|
@@ -26,48 +31,98 @@ var ICONS = {
|
|
|
26
31
|
error: "✖",
|
|
27
32
|
fatal: "▲"
|
|
28
33
|
};
|
|
34
|
+
function pad2(n) {
|
|
35
|
+
return n < 10 ? `0${n}` : `${n}`;
|
|
36
|
+
}
|
|
37
|
+
function dateKey(d = new Date) {
|
|
38
|
+
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class DailyFileSink {
|
|
42
|
+
dir;
|
|
43
|
+
date;
|
|
44
|
+
stream;
|
|
45
|
+
setDir(dir) {
|
|
46
|
+
if (dir === this.dir)
|
|
47
|
+
return;
|
|
48
|
+
this.stream?.end();
|
|
49
|
+
this.stream = undefined;
|
|
50
|
+
this.date = undefined;
|
|
51
|
+
this.dir = dir;
|
|
52
|
+
if (dir && !existsSync(dir))
|
|
53
|
+
mkdirSync(dir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
write(data) {
|
|
56
|
+
if (!this.dir)
|
|
57
|
+
return;
|
|
58
|
+
const today = dateKey();
|
|
59
|
+
if (!this.stream || this.date !== today) {
|
|
60
|
+
this.stream?.end();
|
|
61
|
+
this.date = today;
|
|
62
|
+
if (!existsSync(this.dir))
|
|
63
|
+
mkdirSync(this.dir, { recursive: true });
|
|
64
|
+
this.stream = createWriteStream(join(this.dir, `${today}.log`), {
|
|
65
|
+
flags: "a"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
this.stream.write(data);
|
|
69
|
+
}
|
|
70
|
+
close() {
|
|
71
|
+
this.stream?.end();
|
|
72
|
+
this.stream = undefined;
|
|
73
|
+
this.date = undefined;
|
|
74
|
+
this.dir = undefined;
|
|
75
|
+
}
|
|
76
|
+
get enabled() {
|
|
77
|
+
return this.dir !== undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
var shared = {
|
|
81
|
+
level: "info",
|
|
82
|
+
timestamps: true,
|
|
83
|
+
colors: chalk.level > 0,
|
|
84
|
+
json: false,
|
|
85
|
+
silent: false
|
|
86
|
+
};
|
|
87
|
+
var mainSink = new DailyFileSink;
|
|
88
|
+
var llmLogDir;
|
|
89
|
+
function applyShared(options) {
|
|
90
|
+
if (options.level !== undefined)
|
|
91
|
+
shared.level = options.level;
|
|
92
|
+
if (options.timestamps !== undefined)
|
|
93
|
+
shared.timestamps = options.timestamps;
|
|
94
|
+
if (options.colors !== undefined)
|
|
95
|
+
shared.colors = options.colors;
|
|
96
|
+
if (options.silent !== undefined)
|
|
97
|
+
shared.silent = options.silent;
|
|
98
|
+
if (options.json !== undefined)
|
|
99
|
+
shared.json = options.json;
|
|
100
|
+
if ("logDir" in options)
|
|
101
|
+
mainSink.setDir(options.logDir);
|
|
102
|
+
}
|
|
29
103
|
|
|
30
104
|
class Logger {
|
|
31
|
-
level;
|
|
32
|
-
timestamps;
|
|
33
|
-
colors;
|
|
34
105
|
tag;
|
|
35
|
-
file;
|
|
36
|
-
json;
|
|
37
|
-
silent;
|
|
38
|
-
fileStream;
|
|
39
106
|
constructor(options = {}) {
|
|
40
|
-
this.level = options.level ?? "info";
|
|
41
|
-
this.timestamps = options.timestamps ?? true;
|
|
42
|
-
this.colors = options.colors ?? chalk.level > 0;
|
|
43
107
|
this.tag = options.tag;
|
|
44
|
-
|
|
45
|
-
this.json = options.json ?? false;
|
|
46
|
-
this.silent = options.silent ?? false;
|
|
47
|
-
if (this.file) {
|
|
48
|
-
const dir = dirname(this.file);
|
|
49
|
-
if (!existsSync(dir))
|
|
50
|
-
mkdirSync(dir, { recursive: true });
|
|
51
|
-
this.fileStream = createWriteStream(this.file, { flags: "a" });
|
|
52
|
-
}
|
|
108
|
+
applyShared(options);
|
|
53
109
|
}
|
|
54
110
|
shouldLog(level) {
|
|
55
|
-
return LEVELS[level].rank >= LEVELS[
|
|
111
|
+
return LEVELS[level].rank >= LEVELS[shared.level].rank;
|
|
56
112
|
}
|
|
57
113
|
formatTimestamp() {
|
|
58
114
|
const now = new Date;
|
|
59
|
-
|
|
60
|
-
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
|
|
115
|
+
return `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())} ${pad2(now.getHours())}:${pad2(now.getMinutes())}:${pad2(now.getSeconds())}`;
|
|
61
116
|
}
|
|
62
117
|
format(level, message) {
|
|
63
|
-
const ts =
|
|
118
|
+
const ts = shared.timestamps ? `[${this.formatTimestamp()}]` : "";
|
|
64
119
|
const tag = this.tag ? `[${this.tag}]` : "";
|
|
65
120
|
const icon = ICONS[level];
|
|
66
121
|
const levelStr = level.toUpperCase();
|
|
67
122
|
const consoleParts = [
|
|
68
123
|
ts,
|
|
69
124
|
tag,
|
|
70
|
-
|
|
125
|
+
shared.colors ? LEVELS[level].color(icon) : icon,
|
|
71
126
|
message
|
|
72
127
|
].filter(Boolean);
|
|
73
128
|
const consoleLine = consoleParts.join(" ");
|
|
@@ -90,13 +145,13 @@ class Logger {
|
|
|
90
145
|
return;
|
|
91
146
|
const { console: consoleLine, file: fileLine } = this.format(level, message);
|
|
92
147
|
const jsonLine = this.formatJson(level, message);
|
|
93
|
-
if (!
|
|
148
|
+
if (!shared.silent) {
|
|
94
149
|
const out = LEVELS[level].stderr ? process.stderr : process.stdout;
|
|
95
150
|
out.write(consoleLine + `
|
|
96
151
|
`);
|
|
97
152
|
}
|
|
98
|
-
if (
|
|
99
|
-
|
|
153
|
+
if (mainSink.enabled) {
|
|
154
|
+
mainSink.write(shared.json ? jsonLine : fileLine);
|
|
100
155
|
}
|
|
101
156
|
}
|
|
102
157
|
debug(message) {
|
|
@@ -119,57 +174,56 @@ class Logger {
|
|
|
119
174
|
}
|
|
120
175
|
child(tag) {
|
|
121
176
|
const combined = this.tag ? `${this.tag}:${tag}` : tag;
|
|
122
|
-
return new Logger({
|
|
123
|
-
level: this.level,
|
|
124
|
-
timestamps: this.timestamps,
|
|
125
|
-
colors: this.colors,
|
|
126
|
-
tag: combined,
|
|
127
|
-
file: this.file,
|
|
128
|
-
json: this.json,
|
|
129
|
-
silent: this.silent
|
|
130
|
-
});
|
|
177
|
+
return new Logger({ tag: combined });
|
|
131
178
|
}
|
|
132
179
|
configure(options) {
|
|
133
|
-
if (options.level !== undefined)
|
|
134
|
-
this.level = options.level;
|
|
135
|
-
if (options.timestamps !== undefined)
|
|
136
|
-
this.timestamps = options.timestamps;
|
|
137
|
-
if (options.colors !== undefined)
|
|
138
|
-
this.colors = options.colors;
|
|
139
180
|
if (options.tag !== undefined)
|
|
140
181
|
this.tag = options.tag;
|
|
141
|
-
|
|
142
|
-
this.silent = options.silent;
|
|
143
|
-
if (options.json !== undefined)
|
|
144
|
-
this.json = options.json;
|
|
145
|
-
if (options.file !== undefined && options.file !== this.file) {
|
|
146
|
-
this.fileStream?.end();
|
|
147
|
-
this.file = options.file;
|
|
148
|
-
if (this.file) {
|
|
149
|
-
const dir = dirname(this.file);
|
|
150
|
-
if (!existsSync(dir))
|
|
151
|
-
mkdirSync(dir, { recursive: true });
|
|
152
|
-
this.fileStream = createWriteStream(this.file, { flags: "a" });
|
|
153
|
-
} else {
|
|
154
|
-
this.fileStream = undefined;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
182
|
+
applyShared(options);
|
|
157
183
|
}
|
|
158
184
|
close() {
|
|
159
|
-
|
|
185
|
+
mainSink.close();
|
|
186
|
+
llmLogDir = undefined;
|
|
160
187
|
}
|
|
161
188
|
}
|
|
162
189
|
var logger = new Logger;
|
|
190
|
+
function configureLlmLog(logDir) {
|
|
191
|
+
llmLogDir = logDir;
|
|
192
|
+
if (logDir && !existsSync(logDir))
|
|
193
|
+
mkdirSync(logDir, { recursive: true });
|
|
194
|
+
}
|
|
195
|
+
function isLlmLogEnabled() {
|
|
196
|
+
return llmLogDir !== undefined;
|
|
197
|
+
}
|
|
198
|
+
function sanitizeCaller(caller) {
|
|
199
|
+
const s = caller.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
200
|
+
return s.length > 0 ? s : "unknown";
|
|
201
|
+
}
|
|
202
|
+
function dateTimeKey(d = new Date) {
|
|
203
|
+
return `${dateKey(d)}-${pad2(d.getHours())}-${pad2(d.getMinutes())}-${pad2(d.getSeconds())}`;
|
|
204
|
+
}
|
|
205
|
+
function writeLlmExchange(caller, content) {
|
|
206
|
+
if (!llmLogDir)
|
|
207
|
+
return;
|
|
208
|
+
if (!existsSync(llmLogDir))
|
|
209
|
+
mkdirSync(llmLogDir, { recursive: true });
|
|
210
|
+
const base = `${dateTimeKey()}-${sanitizeCaller(caller)}`;
|
|
211
|
+
let path = join(llmLogDir, `${base}.log`);
|
|
212
|
+
for (let n = 2;existsSync(path); n += 1) {
|
|
213
|
+
path = join(llmLogDir, `${base}-${n}.log`);
|
|
214
|
+
}
|
|
215
|
+
writeFileSync(path, content, "utf8");
|
|
216
|
+
}
|
|
163
217
|
|
|
164
218
|
// src/config/loader.ts
|
|
165
|
-
import { dirname as
|
|
219
|
+
import { dirname, join as join2, resolve } from "path";
|
|
166
220
|
import { z, ZodError } from "zod";
|
|
167
221
|
import { homedir } from "os";
|
|
168
222
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
169
|
-
import { mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
170
|
-
var brainboxRoot = process.env["BRAINBOX_ROOT_PATH"] ? resolve(process.cwd(), process.env["BRAINBOX_ROOT_PATH"]) :
|
|
223
|
+
import { mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
224
|
+
var brainboxRoot = process.env["BRAINBOX_ROOT_PATH"] ? resolve(process.cwd(), process.env["BRAINBOX_ROOT_PATH"]) : join2(homedir(), ".brainbox");
|
|
171
225
|
function configFile(file, options) {
|
|
172
|
-
const path = () =>
|
|
226
|
+
const path = () => join2(brainboxRoot, file);
|
|
173
227
|
const read = () => {
|
|
174
228
|
const p = path();
|
|
175
229
|
let raw;
|
|
@@ -181,8 +235,8 @@ function configFile(file, options) {
|
|
|
181
235
|
const defaults = defaultsFromSchema(options.schema);
|
|
182
236
|
const templateStr = (options.header ? options.header + `
|
|
183
237
|
` : "") + (defaults === undefined ? "" : stringifyYaml(defaults));
|
|
184
|
-
mkdirSync2(
|
|
185
|
-
|
|
238
|
+
mkdirSync2(dirname(p), { recursive: true });
|
|
239
|
+
writeFileSync2(p, templateStr);
|
|
186
240
|
raw = defaults ?? {};
|
|
187
241
|
}
|
|
188
242
|
try {
|
|
@@ -199,8 +253,8 @@ function configFile(file, options) {
|
|
|
199
253
|
};
|
|
200
254
|
const write = (value) => {
|
|
201
255
|
const p = path();
|
|
202
|
-
mkdirSync2(
|
|
203
|
-
|
|
256
|
+
mkdirSync2(dirname(p), { recursive: true });
|
|
257
|
+
writeFileSync2(p, stringifyYaml(value));
|
|
204
258
|
};
|
|
205
259
|
const update = (fn) => {
|
|
206
260
|
const next = fn(read());
|
|
@@ -258,7 +312,6 @@ function setSupermemoryKey(key) {
|
|
|
258
312
|
supermemory: { ...root.supermemory, apiKey: key }
|
|
259
313
|
}));
|
|
260
314
|
}
|
|
261
|
-
var root_default = rootCfg.read();
|
|
262
315
|
|
|
263
316
|
// src/config/file/auth.ts
|
|
264
317
|
import z3 from "zod";
|
|
@@ -292,16 +345,25 @@ function removeProviderAuth(provider) {
|
|
|
292
345
|
return next;
|
|
293
346
|
});
|
|
294
347
|
}
|
|
295
|
-
var auth_default = authCfg.read();
|
|
296
348
|
|
|
297
349
|
// src/config/index.ts
|
|
298
350
|
var config = {
|
|
299
|
-
debug
|
|
351
|
+
get debug() {
|
|
352
|
+
return process.env.DEBUG_MODE ? process.env.DEBUG_MODE.toLowerCase() === "true" : readRootFile().debug;
|
|
353
|
+
},
|
|
300
354
|
brainboxRoot,
|
|
301
|
-
supermemoryApiKey
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
355
|
+
get supermemoryApiKey() {
|
|
356
|
+
return readRootFile().supermemory.apiKey;
|
|
357
|
+
},
|
|
358
|
+
get conversationModel() {
|
|
359
|
+
return readRootFile().conversationModel;
|
|
360
|
+
},
|
|
361
|
+
get identityModel() {
|
|
362
|
+
return readRootFile().identityModel;
|
|
363
|
+
},
|
|
364
|
+
get auth() {
|
|
365
|
+
return readAuthFile();
|
|
366
|
+
}
|
|
305
367
|
};
|
|
306
368
|
|
|
307
369
|
// src/commands/index.ts
|
|
@@ -313,7 +375,7 @@ function registerCommand(program, config2) {
|
|
|
313
375
|
|
|
314
376
|
// src/brain/manager.ts
|
|
315
377
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
316
|
-
import { join as
|
|
378
|
+
import { join as join3 } from "path";
|
|
317
379
|
var log = logger.child("brain-manager");
|
|
318
380
|
|
|
319
381
|
class BrainDBManager {
|
|
@@ -322,7 +384,7 @@ class BrainDBManager {
|
|
|
322
384
|
this.root = root;
|
|
323
385
|
}
|
|
324
386
|
dbFile() {
|
|
325
|
-
return
|
|
387
|
+
return join3(this.root, "brains.json");
|
|
326
388
|
}
|
|
327
389
|
async readDB() {
|
|
328
390
|
try {
|
|
@@ -422,6 +484,21 @@ function defaultReasoningEffort(effort, model, identityModel) {
|
|
|
422
484
|
return effort;
|
|
423
485
|
return model === identityModel ? "medium" : "none";
|
|
424
486
|
}
|
|
487
|
+
function stripThinkTags(content) {
|
|
488
|
+
return content.replace(/<think\b[^>]*>[\s\S]*?<\/think>/gi, "").trim();
|
|
489
|
+
}
|
|
490
|
+
function parseModelJson(content) {
|
|
491
|
+
const cleaned = stripThinkTags(content);
|
|
492
|
+
try {
|
|
493
|
+
return JSON.parse(cleaned);
|
|
494
|
+
} catch (first) {
|
|
495
|
+
const fence = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
496
|
+
if (fence?.[1]) {
|
|
497
|
+
return JSON.parse(fence[1].trim());
|
|
498
|
+
}
|
|
499
|
+
throw first;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
425
502
|
function readAuthString(auth, key, envName) {
|
|
426
503
|
const fromAuth = typeof auth?.[key] === "string" ? auth[key] : undefined;
|
|
427
504
|
if (fromAuth)
|
|
@@ -501,6 +578,19 @@ class LLMExecutor {
|
|
|
501
578
|
};
|
|
502
579
|
}
|
|
503
580
|
}
|
|
581
|
+
function resolveLlmCaller(options, fallback = "llm") {
|
|
582
|
+
return options.caller ?? options.jsonSchemaName ?? fallback;
|
|
583
|
+
}
|
|
584
|
+
function logLlmWire(caller, request, response) {
|
|
585
|
+
if (!isLlmLogEnabled())
|
|
586
|
+
return;
|
|
587
|
+
writeLlmExchange(caller, `======== REQUEST ========
|
|
588
|
+
${request}
|
|
589
|
+
|
|
590
|
+
======== RESPONSE ========
|
|
591
|
+
${response}
|
|
592
|
+
`);
|
|
593
|
+
}
|
|
504
594
|
function listProviderNames() {
|
|
505
595
|
return LLMExecutor.listProviderNames();
|
|
506
596
|
}
|
|
@@ -538,7 +628,7 @@ function fromOrChoice(choice) {
|
|
|
538
628
|
}));
|
|
539
629
|
return {
|
|
540
630
|
message: {
|
|
541
|
-
content: typeof msg.content === "string" ? msg.content : undefined,
|
|
631
|
+
content: typeof msg.content === "string" ? stripThinkTags(msg.content) : undefined,
|
|
542
632
|
toolCalls
|
|
543
633
|
}
|
|
544
634
|
};
|
|
@@ -569,53 +659,54 @@ class OpenRouterExecutor extends LLMExecutor {
|
|
|
569
659
|
async call(model, options) {
|
|
570
660
|
const jsonMode = "jsonSchemaName" in options;
|
|
571
661
|
log3.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
const
|
|
662
|
+
const chatRequest = {
|
|
663
|
+
model,
|
|
664
|
+
messages: [
|
|
665
|
+
{ role: "system", content: options.instruction },
|
|
666
|
+
{ role: "user", content: options.message }
|
|
667
|
+
],
|
|
668
|
+
reasoning: {
|
|
669
|
+
effort: options.reasoningEffort ?? (model === this.models.identity ? REASONING_EFFORT_MAP.medium : REASONING_EFFORT_MAP.none)
|
|
670
|
+
},
|
|
671
|
+
responseFormat: jsonMode ? {
|
|
672
|
+
type: "json_schema",
|
|
673
|
+
jsonSchema: {
|
|
674
|
+
name: options.jsonSchemaName,
|
|
675
|
+
schema: options.jsonSchema,
|
|
676
|
+
strict: true
|
|
677
|
+
}
|
|
678
|
+
} : { type: "text" },
|
|
679
|
+
stream: false
|
|
680
|
+
};
|
|
681
|
+
const result = await this.client.chat.send({ chatRequest });
|
|
682
|
+
logLlmWire(resolveLlmCaller(options), JSON.stringify(chatRequest), JSON.stringify(result));
|
|
683
|
+
const raw = result.choices[0]?.message?.content;
|
|
684
|
+
const content = typeof raw === "string" ? stripThinkTags(raw) : raw;
|
|
594
685
|
if (!content) {
|
|
595
686
|
log3.debug(`call: empty content in choice 0`);
|
|
596
687
|
throw new Error("Empty response from model");
|
|
597
688
|
}
|
|
598
689
|
log3.debug(`call: response ${content.length} chars`);
|
|
599
|
-
return jsonMode ?
|
|
690
|
+
return jsonMode ? parseModelJson(content) : content;
|
|
600
691
|
}
|
|
601
692
|
async chatWithTools(model, options) {
|
|
602
693
|
log3.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
694
|
+
const chatRequest = {
|
|
695
|
+
model,
|
|
696
|
+
messages: [
|
|
697
|
+
{ role: "system", content: options.instruction },
|
|
698
|
+
...options.messages.map(toOrMessage)
|
|
699
|
+
],
|
|
700
|
+
reasoning: {
|
|
701
|
+
effort: options.reasoningEffort ?? (model === this.models.identity ? REASONING_EFFORT_MAP.medium : REASONING_EFFORT_MAP.none)
|
|
702
|
+
},
|
|
703
|
+
responseFormat: { type: "text" },
|
|
704
|
+
tools: options.tools.map(toOrTool),
|
|
705
|
+
parallelToolCalls: options.parallelToolCalls ?? false,
|
|
706
|
+
stream: false
|
|
707
|
+
};
|
|
708
|
+
const result = await this.client.chat.send({ chatRequest });
|
|
709
|
+
logLlmWire(resolveLlmCaller(options), JSON.stringify(chatRequest), JSON.stringify(result));
|
|
619
710
|
const choice = result.choices[0];
|
|
620
711
|
if (!choice) {
|
|
621
712
|
log3.debug(`chatWithTools: no choice in response`);
|
|
@@ -655,7 +746,7 @@ function fromChoice(choice) {
|
|
|
655
746
|
}));
|
|
656
747
|
return {
|
|
657
748
|
message: {
|
|
658
|
-
content: typeof msg.content === "string" ? msg.content : undefined,
|
|
749
|
+
content: typeof msg.content === "string" ? stripThinkTags(msg.content) : undefined,
|
|
659
750
|
toolCalls
|
|
660
751
|
}
|
|
661
752
|
};
|
|
@@ -729,7 +820,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
729
820
|
}
|
|
730
821
|
return url.toString();
|
|
731
822
|
}
|
|
732
|
-
async sendRequest(body, reasoningEffort) {
|
|
823
|
+
async sendRequest(body, reasoningEffort, caller) {
|
|
733
824
|
const modelName = body["model"];
|
|
734
825
|
const modelStr = typeof modelName === "string" ? modelName : "";
|
|
735
826
|
const url = this.buildRequestUrl(modelStr, reasoningEffort);
|
|
@@ -739,21 +830,34 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
739
830
|
...authHeader,
|
|
740
831
|
...this.defaultHeaders
|
|
741
832
|
};
|
|
833
|
+
const requestRaw = JSON.stringify(body);
|
|
742
834
|
const res = await fetch(url, {
|
|
743
835
|
method: "POST",
|
|
744
836
|
headers,
|
|
745
|
-
body:
|
|
837
|
+
body: requestRaw
|
|
746
838
|
});
|
|
839
|
+
const responseRaw = await res.text().catch(() => "");
|
|
840
|
+
logLlmWire(caller, requestRaw, responseRaw);
|
|
747
841
|
if (!res.ok) {
|
|
748
|
-
|
|
749
|
-
log4.error(`${this.providerName}: HTTP ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
842
|
+
log4.error(`${this.providerName}: HTTP ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
|
|
750
843
|
throw new Error(`${this.providerName} request failed: ${res.status} ${res.statusText}`);
|
|
751
844
|
}
|
|
752
|
-
|
|
845
|
+
let data;
|
|
846
|
+
try {
|
|
847
|
+
data = JSON.parse(responseRaw);
|
|
848
|
+
} catch {
|
|
849
|
+
throw new Error(`${this.providerName}: invalid JSON response`);
|
|
850
|
+
}
|
|
753
851
|
if (data.error) {
|
|
754
852
|
log4.error(`${this.providerName}: API error ${data.error.type ?? ""} ${data.error.message ?? ""}`);
|
|
755
853
|
throw new Error(`${this.providerName} API error: ${data.error.message ?? "unknown"}`);
|
|
756
854
|
}
|
|
855
|
+
const baseCode = data.base_resp?.status_code;
|
|
856
|
+
if (typeof baseCode === "number" && baseCode !== 0) {
|
|
857
|
+
const msg = data.base_resp?.status_msg?.trim() || `status_code ${baseCode}`;
|
|
858
|
+
log4.error(`${this.providerName}: base_resp ${baseCode} ${msg}`);
|
|
859
|
+
throw new Error(`${this.providerName} API error: ${msg}`);
|
|
860
|
+
}
|
|
757
861
|
return data;
|
|
758
862
|
}
|
|
759
863
|
async call(model, options) {
|
|
@@ -769,14 +873,18 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
769
873
|
responseFormat: jsonMode ? buildResponseFormat(options.jsonSchemaName, options.jsonSchema) : undefined,
|
|
770
874
|
reasoningEffort: reasoning
|
|
771
875
|
});
|
|
772
|
-
const data = await this.sendRequest(body, options.reasoningEffort);
|
|
773
|
-
const
|
|
876
|
+
const data = await this.sendRequest(body, options.reasoningEffort, resolveLlmCaller(options));
|
|
877
|
+
const choice = data.choices?.[0];
|
|
878
|
+
const raw = choice?.message?.content;
|
|
879
|
+
const content = typeof raw === "string" ? stripThinkTags(raw) : raw;
|
|
774
880
|
if (!content) {
|
|
775
|
-
|
|
776
|
-
|
|
881
|
+
const finish = choice?.finish_reason ?? "no-choice";
|
|
882
|
+
const reasoningLen = typeof choice?.message?.reasoning_content === "string" ? choice.message.reasoning_content.length : 0;
|
|
883
|
+
log4.debug(`call: empty content in choice 0 finish_reason=${finish} reasoning_len=${reasoningLen} rawType=${raw === null ? "null" : typeof raw}`);
|
|
884
|
+
throw new Error(reasoningLen > 0 ? `Empty response from model (finish_reason=${finish}; reasoning present but no content)` : "Empty response from model");
|
|
777
885
|
}
|
|
778
886
|
log4.debug(`call: response ${content.length} chars`);
|
|
779
|
-
return jsonMode ?
|
|
887
|
+
return jsonMode ? parseModelJson(content) : content;
|
|
780
888
|
}
|
|
781
889
|
async chatWithTools(model, options) {
|
|
782
890
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
@@ -791,7 +899,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
791
899
|
parallelToolCalls: options.parallelToolCalls ?? false,
|
|
792
900
|
reasoningEffort: reasoning
|
|
793
901
|
});
|
|
794
|
-
const data = await this.sendRequest(body, options.reasoningEffort);
|
|
902
|
+
const data = await this.sendRequest(body, options.reasoningEffort, resolveLlmCaller(options));
|
|
795
903
|
const choice = data.choices?.[0];
|
|
796
904
|
if (!choice) {
|
|
797
905
|
log4.debug(`chatWithTools: no choice in response`);
|
|
@@ -1090,16 +1198,40 @@ class ZenMuxExecutor extends OpenAICompatibleExecutor {
|
|
|
1090
1198
|
}
|
|
1091
1199
|
}
|
|
1092
1200
|
|
|
1093
|
-
// src/provider/providers/
|
|
1094
|
-
class
|
|
1201
|
+
// src/provider/providers/minimax.ts
|
|
1202
|
+
class MiniMaxBase extends OpenAICompatibleExecutor {
|
|
1203
|
+
buildBody(opts) {
|
|
1204
|
+
const jsonMode = opts.responseFormat !== undefined;
|
|
1205
|
+
const body = super.buildBody(opts);
|
|
1206
|
+
delete body["reasoning_effort"];
|
|
1207
|
+
delete body["response_format"];
|
|
1208
|
+
delete body["parallel_tool_calls"];
|
|
1209
|
+
body["reasoning_split"] = true;
|
|
1210
|
+
body["max_completion_tokens"] = 16384;
|
|
1211
|
+
const wantThink = !jsonMode && opts.reasoningEffort !== undefined && opts.reasoningEffort !== "none";
|
|
1212
|
+
body["thinking"] = wantThink ? { type: "adaptive" } : { type: "disabled" };
|
|
1213
|
+
return body;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
function minimaxOpts(providerName, baseURL, opts) {
|
|
1217
|
+
return {
|
|
1218
|
+
providerName,
|
|
1219
|
+
baseURL,
|
|
1220
|
+
apiKey: opts.apiKey,
|
|
1221
|
+
conversationModel: opts.conversationModel,
|
|
1222
|
+
identityModel: opts.identityModel
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
class MiniMaxExecutor extends MiniMaxBase {
|
|
1095
1227
|
constructor(opts) {
|
|
1096
|
-
super(
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1228
|
+
super(minimaxOpts("minimax", "https://api.minimax.io/v1", opts));
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
class MiniMaxCnExecutor extends MiniMaxBase {
|
|
1233
|
+
constructor(opts) {
|
|
1234
|
+
super(minimaxOpts("minimax-cn", "https://api.minimaxi.com/v1", opts));
|
|
1103
1235
|
}
|
|
1104
1236
|
}
|
|
1105
1237
|
|
|
@@ -1252,7 +1384,17 @@ class CloudflareGatewayExecutor extends OpenAICompatibleExecutor {
|
|
|
1252
1384
|
// src/provider/providers/cloudflare_workers.ts
|
|
1253
1385
|
function toCloudflareMessage(m) {
|
|
1254
1386
|
if (m.role === "assistant") {
|
|
1255
|
-
return {
|
|
1387
|
+
return {
|
|
1388
|
+
role: "assistant",
|
|
1389
|
+
content: m.content ?? "",
|
|
1390
|
+
tool_calls: m.toolCalls?.map((c) => {
|
|
1391
|
+
let args = c.function.arguments;
|
|
1392
|
+
try {
|
|
1393
|
+
args = JSON.parse(c.function.arguments);
|
|
1394
|
+
} catch {}
|
|
1395
|
+
return { id: c.id, name: c.function.name, arguments: args };
|
|
1396
|
+
})
|
|
1397
|
+
};
|
|
1256
1398
|
}
|
|
1257
1399
|
if (m.role === "tool") {
|
|
1258
1400
|
return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
|
|
@@ -1275,21 +1417,23 @@ class CloudflareWorkersExecutor extends LLMExecutor {
|
|
|
1275
1417
|
const accountId = readAuthString(opts.auth, "accountId", "CLOUDFLARE_ACCOUNT_ID");
|
|
1276
1418
|
this.baseURL = accountId ? `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run` : "https://api.cloudflare.com/client/v4/accounts/__cf_account_id__/ai/run";
|
|
1277
1419
|
}
|
|
1278
|
-
async run(model, body) {
|
|
1420
|
+
async run(model, body, caller) {
|
|
1279
1421
|
const url = `${this.baseURL}/${encodeURIComponent(model)}`;
|
|
1422
|
+
const requestRaw = JSON.stringify(body);
|
|
1280
1423
|
const res = await fetch(url, {
|
|
1281
1424
|
method: "POST",
|
|
1282
1425
|
headers: {
|
|
1283
1426
|
Authorization: `Bearer ${this.apiKey}`,
|
|
1284
1427
|
"Content-Type": "application/json"
|
|
1285
1428
|
},
|
|
1286
|
-
body:
|
|
1429
|
+
body: requestRaw
|
|
1287
1430
|
});
|
|
1431
|
+
const responseRaw = await res.text().catch(() => "");
|
|
1432
|
+
logLlmWire(caller, requestRaw, responseRaw);
|
|
1288
1433
|
if (!res.ok) {
|
|
1289
|
-
|
|
1290
|
-
throw new Error(`cloudflare-workers request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
1434
|
+
throw new Error(`cloudflare-workers request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
|
|
1291
1435
|
}
|
|
1292
|
-
return
|
|
1436
|
+
return JSON.parse(responseRaw);
|
|
1293
1437
|
}
|
|
1294
1438
|
async call(model, options) {
|
|
1295
1439
|
const jsonMode = "jsonSchemaName" in options;
|
|
@@ -1310,15 +1454,15 @@ class CloudflareWorkersExecutor extends LLMExecutor {
|
|
|
1310
1454
|
if (reasoning !== "none") {
|
|
1311
1455
|
body["reasoning_effort"] = reasoning;
|
|
1312
1456
|
}
|
|
1313
|
-
const data = await this.run(model, body);
|
|
1457
|
+
const data = await this.run(model, body, resolveLlmCaller(options));
|
|
1314
1458
|
if (data.errors && data.errors.length > 0) {
|
|
1315
1459
|
throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
|
|
1316
1460
|
}
|
|
1317
|
-
const content = data.result?.response ?? "";
|
|
1461
|
+
const content = stripThinkTags(data.result?.response ?? "");
|
|
1318
1462
|
if (!content) {
|
|
1319
1463
|
throw new Error("Empty response from model");
|
|
1320
1464
|
}
|
|
1321
|
-
return jsonMode ?
|
|
1465
|
+
return jsonMode ? parseModelJson(content) : content;
|
|
1322
1466
|
}
|
|
1323
1467
|
async chatWithTools(model, options) {
|
|
1324
1468
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
@@ -1339,11 +1483,11 @@ class CloudflareWorkersExecutor extends LLMExecutor {
|
|
|
1339
1483
|
if (reasoning !== "none") {
|
|
1340
1484
|
body["reasoning_effort"] = reasoning;
|
|
1341
1485
|
}
|
|
1342
|
-
const data = await this.run(model, body);
|
|
1486
|
+
const data = await this.run(model, body, resolveLlmCaller(options));
|
|
1343
1487
|
if (data.errors && data.errors.length > 0) {
|
|
1344
1488
|
throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
|
|
1345
1489
|
}
|
|
1346
|
-
const content = data.result?.response ?? "";
|
|
1490
|
+
const content = stripThinkTags(data.result?.response ?? "");
|
|
1347
1491
|
const toolCalls = data.result?.tool_calls?.map((c, idx) => ({
|
|
1348
1492
|
id: `call_${idx}`,
|
|
1349
1493
|
function: {
|
|
@@ -1381,7 +1525,8 @@ class AzureOpenAIExecutor extends OpenAICompatibleExecutor {
|
|
|
1381
1525
|
super({
|
|
1382
1526
|
providerName: "azure-openai",
|
|
1383
1527
|
baseURL: `https://${resource || "__resource__"}.openai.azure.com/openai/deployments`,
|
|
1384
|
-
apiKey:
|
|
1528
|
+
apiKey: "",
|
|
1529
|
+
defaultHeaders: { "api-key": opts.apiKey },
|
|
1385
1530
|
conversationModel: opts.conversationModel,
|
|
1386
1531
|
identityModel: opts.identityModel
|
|
1387
1532
|
});
|
|
@@ -1401,7 +1546,8 @@ class AzureCognitiveExecutor extends OpenAICompatibleExecutor {
|
|
|
1401
1546
|
super({
|
|
1402
1547
|
providerName: "azure-cognitive",
|
|
1403
1548
|
baseURL: `https://${resource || "__resource__"}.cognitiveservices.azure.com/openai/deployments`,
|
|
1404
|
-
apiKey:
|
|
1549
|
+
apiKey: "",
|
|
1550
|
+
defaultHeaders: { "api-key": opts.apiKey },
|
|
1405
1551
|
conversationModel: opts.conversationModel,
|
|
1406
1552
|
identityModel: opts.identityModel
|
|
1407
1553
|
});
|
|
@@ -1504,7 +1650,8 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1504
1650
|
this.baseURL = (extra.baseURL ?? readAuthString(opts.auth, "baseURL", "ANTHROPIC_BASE_URL") ?? "https://api.anthropic.com").replace(/\/v1\/?$/, "");
|
|
1505
1651
|
this.apiVersion = extra.apiVersion ?? "2023-06-01";
|
|
1506
1652
|
}
|
|
1507
|
-
async send(body) {
|
|
1653
|
+
async send(body, caller) {
|
|
1654
|
+
const requestRaw = JSON.stringify(body);
|
|
1508
1655
|
const res = await fetch(`${this.baseURL}/v1/messages`, {
|
|
1509
1656
|
method: "POST",
|
|
1510
1657
|
headers: {
|
|
@@ -1512,65 +1659,72 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1512
1659
|
"anthropic-version": this.apiVersion,
|
|
1513
1660
|
"Content-Type": "application/json"
|
|
1514
1661
|
},
|
|
1515
|
-
body:
|
|
1662
|
+
body: requestRaw
|
|
1516
1663
|
});
|
|
1664
|
+
const responseRaw = await res.text().catch(() => "");
|
|
1665
|
+
logLlmWire(caller, requestRaw, responseRaw);
|
|
1517
1666
|
if (!res.ok) {
|
|
1518
|
-
|
|
1519
|
-
throw new Error(`anthropic request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
1667
|
+
throw new Error(`anthropic request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
|
|
1520
1668
|
}
|
|
1521
|
-
return
|
|
1669
|
+
return JSON.parse(responseRaw);
|
|
1522
1670
|
}
|
|
1523
1671
|
async call(model, options) {
|
|
1524
1672
|
const jsonMode = "jsonSchemaName" in options;
|
|
1525
1673
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
1526
1674
|
const log5 = logger.child("llm:anthropic");
|
|
1527
1675
|
log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
1676
|
+
const outputCap = 4096;
|
|
1528
1677
|
const body = {
|
|
1529
1678
|
model,
|
|
1530
|
-
max_tokens:
|
|
1679
|
+
max_tokens: outputCap,
|
|
1531
1680
|
system: options.instruction,
|
|
1532
1681
|
messages: [{ role: "user", content: options.message }]
|
|
1533
1682
|
};
|
|
1534
1683
|
if (reasoning !== "none") {
|
|
1684
|
+
const budget = REASONING_BUDGET[reasoning];
|
|
1685
|
+
body["max_tokens"] = budget + outputCap;
|
|
1535
1686
|
body["thinking"] = {
|
|
1536
1687
|
type: "enabled",
|
|
1537
|
-
budget_tokens:
|
|
1688
|
+
budget_tokens: budget
|
|
1538
1689
|
};
|
|
1539
1690
|
}
|
|
1540
|
-
const data = await this.send(body);
|
|
1691
|
+
const data = await this.send(body, resolveLlmCaller(options));
|
|
1541
1692
|
if (data.error) {
|
|
1542
1693
|
throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
|
|
1543
1694
|
}
|
|
1544
|
-
const text = (data.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
1695
|
+
const text = stripThinkTags((data.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join(""));
|
|
1545
1696
|
if (!text) {
|
|
1546
1697
|
throw new Error("Empty response from model");
|
|
1547
1698
|
}
|
|
1548
|
-
return jsonMode ?
|
|
1699
|
+
return jsonMode ? parseModelJson(text) : text;
|
|
1549
1700
|
}
|
|
1550
1701
|
async chatWithTools(model, options) {
|
|
1551
1702
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
1552
1703
|
const log5 = logger.child("llm:anthropic");
|
|
1553
1704
|
log5.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
|
|
1554
1705
|
const { system, msgs } = toAnthropicMessages(options.messages);
|
|
1706
|
+
const outputCap = 4096;
|
|
1555
1707
|
const body = {
|
|
1556
1708
|
model,
|
|
1557
|
-
max_tokens:
|
|
1709
|
+
max_tokens: outputCap,
|
|
1558
1710
|
system: system ?? options.instruction,
|
|
1559
1711
|
messages: msgs,
|
|
1560
1712
|
tools: options.tools.map(toAnthropicTool)
|
|
1561
1713
|
};
|
|
1562
1714
|
if (reasoning !== "none") {
|
|
1715
|
+
const budget = REASONING_BUDGET[reasoning];
|
|
1716
|
+
body["max_tokens"] = budget + outputCap;
|
|
1563
1717
|
body["thinking"] = {
|
|
1564
1718
|
type: "enabled",
|
|
1565
|
-
budget_tokens:
|
|
1719
|
+
budget_tokens: budget
|
|
1566
1720
|
};
|
|
1567
1721
|
}
|
|
1568
|
-
const data = await this.send(body);
|
|
1722
|
+
const data = await this.send(body, resolveLlmCaller(options));
|
|
1569
1723
|
if (data.error) {
|
|
1570
1724
|
throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
|
|
1571
1725
|
}
|
|
1572
1726
|
const blocks = data.content ?? [];
|
|
1573
|
-
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
1727
|
+
const text = stripThinkTags(blocks.filter((b) => b.type === "text").map((b) => b.text).join(""));
|
|
1574
1728
|
const toolCalls = blocks.filter((b) => b.type === "tool_use").map((b) => ({
|
|
1575
1729
|
id: b.id,
|
|
1576
1730
|
function: {
|
|
@@ -1584,11 +1738,11 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1584
1738
|
|
|
1585
1739
|
// src/provider/providers/bedrock.ts
|
|
1586
1740
|
import { createHmac, createHash } from "node:crypto";
|
|
1587
|
-
var
|
|
1741
|
+
var pad22 = (n) => n.toString().padStart(2, "0");
|
|
1588
1742
|
function amzDate(now) {
|
|
1589
1743
|
return {
|
|
1590
|
-
date: `${now.getUTCFullYear()}${
|
|
1591
|
-
datetime: `${now.getUTCFullYear()}${
|
|
1744
|
+
date: `${now.getUTCFullYear()}${pad22(now.getUTCMonth() + 1)}${pad22(now.getUTCDate())}`,
|
|
1745
|
+
datetime: `${now.getUTCFullYear()}${pad22(now.getUTCMonth() + 1)}${pad22(now.getUTCDate())}T` + `${pad22(now.getUTCHours())}${pad22(now.getUTCMinutes())}${pad22(now.getUTCSeconds())}Z`
|
|
1592
1746
|
};
|
|
1593
1747
|
}
|
|
1594
1748
|
function signRequest(opts) {
|
|
@@ -1696,7 +1850,7 @@ function toAnthropicTool2(t) {
|
|
|
1696
1850
|
}
|
|
1697
1851
|
function extractAnthropicContent(data) {
|
|
1698
1852
|
const content = data["content"] ?? [];
|
|
1699
|
-
const text = content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("");
|
|
1853
|
+
const text = stripThinkTags(content.filter((b) => b["type"] === "text").map((b) => b["text"]).join(""));
|
|
1700
1854
|
const toolCalls = content.filter((b) => b["type"] === "tool_use").map((b) => ({
|
|
1701
1855
|
id: b["id"],
|
|
1702
1856
|
function: {
|
|
@@ -1728,7 +1882,7 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1728
1882
|
this.sessionToken = readAuthString(opts.auth, "sessionToken", "AWS_SESSION_TOKEN");
|
|
1729
1883
|
this.baseURL = `https://bedrock-runtime.${this.region}.amazonaws.com`;
|
|
1730
1884
|
}
|
|
1731
|
-
async invoke(model, body) {
|
|
1885
|
+
async invoke(model, body, caller) {
|
|
1732
1886
|
const bodyStr = JSON.stringify(body);
|
|
1733
1887
|
const path = `/model/${encodeURIComponent(model)}/invoke`;
|
|
1734
1888
|
const headers = signRequest({
|
|
@@ -1747,11 +1901,12 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1747
1901
|
headers,
|
|
1748
1902
|
body: bodyStr
|
|
1749
1903
|
});
|
|
1904
|
+
const responseRaw = await res.text().catch(() => "");
|
|
1905
|
+
logLlmWire(caller, bodyStr, responseRaw);
|
|
1750
1906
|
if (!res.ok) {
|
|
1751
|
-
|
|
1752
|
-
throw new Error(`bedrock request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
1907
|
+
throw new Error(`bedrock request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
|
|
1753
1908
|
}
|
|
1754
|
-
return
|
|
1909
|
+
return JSON.parse(responseRaw);
|
|
1755
1910
|
}
|
|
1756
1911
|
async call(model, options) {
|
|
1757
1912
|
const jsonMode = "jsonSchemaName" in options;
|
|
@@ -1766,12 +1921,12 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1766
1921
|
system: options.instruction,
|
|
1767
1922
|
messages: [{ role: "user", content: options.message }]
|
|
1768
1923
|
};
|
|
1769
|
-
const data = await this.invoke(model, body);
|
|
1924
|
+
const data = await this.invoke(model, body, resolveLlmCaller(options));
|
|
1770
1925
|
const { text } = extractAnthropicContent(data);
|
|
1771
1926
|
if (!text) {
|
|
1772
1927
|
throw new Error("Empty response from model");
|
|
1773
1928
|
}
|
|
1774
|
-
return jsonMode ?
|
|
1929
|
+
return jsonMode ? parseModelJson(text) : text;
|
|
1775
1930
|
}
|
|
1776
1931
|
async chatWithTools(model, options) {
|
|
1777
1932
|
const log5 = logger.child("llm:bedrock");
|
|
@@ -1787,7 +1942,7 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1787
1942
|
messages: msgs,
|
|
1788
1943
|
tools: options.tools.map(toAnthropicTool2)
|
|
1789
1944
|
};
|
|
1790
|
-
const data = await this.invoke(model, body);
|
|
1945
|
+
const data = await this.invoke(model, body, resolveLlmCaller(options));
|
|
1791
1946
|
const { text, toolCalls } = extractAnthropicContent(data);
|
|
1792
1947
|
return { message: { content: text || undefined, toolCalls } };
|
|
1793
1948
|
}
|
|
@@ -1801,6 +1956,7 @@ var VertexAuthSchema = z5.object({
|
|
|
1801
1956
|
}).loose();
|
|
1802
1957
|
function toGeminiContents(messages) {
|
|
1803
1958
|
const out = [];
|
|
1959
|
+
const nameByCallId = new Map;
|
|
1804
1960
|
for (const m of messages) {
|
|
1805
1961
|
if (m.role === "system")
|
|
1806
1962
|
continue;
|
|
@@ -1814,6 +1970,7 @@ function toGeminiContents(messages) {
|
|
|
1814
1970
|
parts.push({ text: m.content });
|
|
1815
1971
|
if (m.toolCalls) {
|
|
1816
1972
|
for (const c of m.toolCalls) {
|
|
1973
|
+
nameByCallId.set(c.id, c.function.name);
|
|
1817
1974
|
let args = {};
|
|
1818
1975
|
try {
|
|
1819
1976
|
args = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
@@ -1835,21 +1992,26 @@ function toGeminiContents(messages) {
|
|
|
1835
1992
|
}
|
|
1836
1993
|
out.push({
|
|
1837
1994
|
role: "user",
|
|
1838
|
-
parts: [
|
|
1995
|
+
parts: [
|
|
1996
|
+
{
|
|
1997
|
+
functionResponse: {
|
|
1998
|
+
name: nameByCallId.get(m.toolCallId) ?? "",
|
|
1999
|
+
response
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
]
|
|
1839
2003
|
});
|
|
1840
2004
|
}
|
|
1841
2005
|
}
|
|
1842
2006
|
return out;
|
|
1843
2007
|
}
|
|
1844
|
-
function
|
|
2008
|
+
function toGeminiTools(tools) {
|
|
1845
2009
|
return {
|
|
1846
|
-
functionDeclarations:
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
}
|
|
1852
|
-
]
|
|
2010
|
+
functionDeclarations: tools.map((t) => ({
|
|
2011
|
+
name: t.name,
|
|
2012
|
+
description: t.description,
|
|
2013
|
+
parameters: t.parameters ?? { type: "object", properties: {} }
|
|
2014
|
+
}))
|
|
1853
2015
|
};
|
|
1854
2016
|
}
|
|
1855
2017
|
function extractFromGemini(data) {
|
|
@@ -1870,7 +2032,10 @@ function extractFromGemini(data) {
|
|
|
1870
2032
|
});
|
|
1871
2033
|
}
|
|
1872
2034
|
}
|
|
1873
|
-
return {
|
|
2035
|
+
return {
|
|
2036
|
+
text: stripThinkTags(text),
|
|
2037
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
2038
|
+
};
|
|
1874
2039
|
}
|
|
1875
2040
|
|
|
1876
2041
|
class VertexExecutor extends LLMExecutor {
|
|
@@ -1893,21 +2058,23 @@ class VertexExecutor extends LLMExecutor {
|
|
|
1893
2058
|
this.region = extra.region ?? readAuthString(opts.auth, "region", "GOOGLE_CLOUD_REGION") ?? "us-central1";
|
|
1894
2059
|
this.baseURL = this.project ? `https://${this.region}-aiplatform.googleapis.com/v1/projects/${this.project}/locations/${this.region}/publishers/google/models` : "https://us-central1-aiplatform.googleapis.com/v1";
|
|
1895
2060
|
}
|
|
1896
|
-
async generate(model, body) {
|
|
2061
|
+
async generate(model, body, caller) {
|
|
1897
2062
|
const url = `${this.baseURL}/${encodeURIComponent(model)}:generateContent`;
|
|
2063
|
+
const requestRaw = JSON.stringify(body);
|
|
1898
2064
|
const res = await fetch(url, {
|
|
1899
2065
|
method: "POST",
|
|
1900
2066
|
headers: {
|
|
1901
2067
|
Authorization: `Bearer ${this.apiKey}`,
|
|
1902
2068
|
"Content-Type": "application/json"
|
|
1903
2069
|
},
|
|
1904
|
-
body:
|
|
2070
|
+
body: requestRaw
|
|
1905
2071
|
});
|
|
2072
|
+
const responseRaw = await res.text().catch(() => "");
|
|
2073
|
+
logLlmWire(caller, requestRaw, responseRaw);
|
|
1906
2074
|
if (!res.ok) {
|
|
1907
|
-
|
|
1908
|
-
throw new Error(`vertex request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
2075
|
+
throw new Error(`vertex request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
|
|
1909
2076
|
}
|
|
1910
|
-
return
|
|
2077
|
+
return JSON.parse(responseRaw);
|
|
1911
2078
|
}
|
|
1912
2079
|
async call(model, options) {
|
|
1913
2080
|
const jsonMode = "jsonSchemaName" in options;
|
|
@@ -1928,7 +2095,7 @@ class VertexExecutor extends LLMExecutor {
|
|
|
1928
2095
|
responseSchema: options.jsonSchema
|
|
1929
2096
|
};
|
|
1930
2097
|
}
|
|
1931
|
-
const data = await this.generate(model, body);
|
|
2098
|
+
const data = await this.generate(model, body, resolveLlmCaller(options));
|
|
1932
2099
|
if (data.error) {
|
|
1933
2100
|
throw new Error(`vertex API error: ${data.error.message ?? "unknown"}`);
|
|
1934
2101
|
}
|
|
@@ -1936,7 +2103,7 @@ class VertexExecutor extends LLMExecutor {
|
|
|
1936
2103
|
if (!text) {
|
|
1937
2104
|
throw new Error("Empty response from model");
|
|
1938
2105
|
}
|
|
1939
|
-
return jsonMode ?
|
|
2106
|
+
return jsonMode ? parseModelJson(text) : text;
|
|
1940
2107
|
}
|
|
1941
2108
|
async chatWithTools(model, options) {
|
|
1942
2109
|
const log5 = logger.child("llm:vertex");
|
|
@@ -1945,9 +2112,9 @@ class VertexExecutor extends LLMExecutor {
|
|
|
1945
2112
|
const body = {
|
|
1946
2113
|
contents,
|
|
1947
2114
|
systemInstruction: { parts: [{ text: options.instruction }] },
|
|
1948
|
-
tools: options.tools.length > 0 ? [
|
|
2115
|
+
tools: options.tools.length > 0 ? [toGeminiTools(options.tools)] : undefined
|
|
1949
2116
|
};
|
|
1950
|
-
const data = await this.generate(model, body);
|
|
2117
|
+
const data = await this.generate(model, body, resolveLlmCaller(options));
|
|
1951
2118
|
if (data.error) {
|
|
1952
2119
|
throw new Error(`vertex API error: ${data.error.message ?? "unknown"}`);
|
|
1953
2120
|
}
|
|
@@ -2023,20 +2190,22 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2023
2190
|
const extra = parsed.success ? parsed.data : {};
|
|
2024
2191
|
this.baseURL = (extra.baseURL ?? readAuthString(opts.auth, "baseURL", "GITLAB_BASE_URL") ?? "https://gitlab.com/api/v4/ai/llm/proxy").replace(/\/+$/, "");
|
|
2025
2192
|
}
|
|
2026
|
-
async send(body) {
|
|
2193
|
+
async send(body, caller) {
|
|
2194
|
+
const requestRaw = JSON.stringify(body);
|
|
2027
2195
|
const res = await fetch(this.baseURL, {
|
|
2028
2196
|
method: "POST",
|
|
2029
2197
|
headers: {
|
|
2030
2198
|
Authorization: `Bearer ${this.apiKey}`,
|
|
2031
2199
|
"Content-Type": "application/json"
|
|
2032
2200
|
},
|
|
2033
|
-
body:
|
|
2201
|
+
body: requestRaw
|
|
2034
2202
|
});
|
|
2203
|
+
const responseRaw = await res.text().catch(() => "");
|
|
2204
|
+
logLlmWire(caller, requestRaw, responseRaw);
|
|
2035
2205
|
if (!res.ok) {
|
|
2036
|
-
|
|
2037
|
-
throw new Error(`gitlab-duo request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
2206
|
+
throw new Error(`gitlab-duo request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
|
|
2038
2207
|
}
|
|
2039
|
-
return
|
|
2208
|
+
return JSON.parse(responseRaw);
|
|
2040
2209
|
}
|
|
2041
2210
|
async call(model, options) {
|
|
2042
2211
|
const jsonMode = "jsonSchemaName" in options;
|
|
@@ -2049,15 +2218,15 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2049
2218
|
{ role: "user", content: options.message }
|
|
2050
2219
|
]
|
|
2051
2220
|
};
|
|
2052
|
-
const data = await this.send(body);
|
|
2221
|
+
const data = await this.send(body, resolveLlmCaller(options));
|
|
2053
2222
|
if (data.error) {
|
|
2054
2223
|
throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
|
|
2055
2224
|
}
|
|
2056
|
-
const content = data.choices?.[0]?.message?.content ?? "";
|
|
2225
|
+
const content = stripThinkTags(data.choices?.[0]?.message?.content ?? "");
|
|
2057
2226
|
if (!content) {
|
|
2058
2227
|
throw new Error("Empty response from model");
|
|
2059
2228
|
}
|
|
2060
|
-
return jsonMode ?
|
|
2229
|
+
return jsonMode ? parseModelJson(content) : content;
|
|
2061
2230
|
}
|
|
2062
2231
|
async chatWithTools(model, options) {
|
|
2063
2232
|
const log5 = logger.child("llm:gitlab-duo");
|
|
@@ -2070,7 +2239,7 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2070
2239
|
],
|
|
2071
2240
|
tools: options.tools.map(toTool)
|
|
2072
2241
|
};
|
|
2073
|
-
const data = await this.send(body);
|
|
2242
|
+
const data = await this.send(body, resolveLlmCaller(options));
|
|
2074
2243
|
if (data.error) {
|
|
2075
2244
|
throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
|
|
2076
2245
|
}
|
|
@@ -2081,7 +2250,7 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2081
2250
|
}));
|
|
2082
2251
|
return {
|
|
2083
2252
|
message: {
|
|
2084
|
-
content: choice?.message?.content
|
|
2253
|
+
content: choice?.message?.content ? stripThinkTags(choice.message.content) : undefined,
|
|
2085
2254
|
toolCalls
|
|
2086
2255
|
}
|
|
2087
2256
|
};
|
|
@@ -2138,20 +2307,22 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2138
2307
|
const account = extra.account ?? readAuthString(opts.auth, "account", "SNOWFLAKE_ACCOUNT") ?? "";
|
|
2139
2308
|
this.baseURL = account ? `https://${account}.snowflakecomputing.com/api/v2/cortex/inference:complete` : "https://__account__.snowflakecomputing.com/api/v2/cortex/inference:complete";
|
|
2140
2309
|
}
|
|
2141
|
-
async send(body) {
|
|
2310
|
+
async send(body, caller) {
|
|
2311
|
+
const requestRaw = JSON.stringify(body);
|
|
2142
2312
|
const res = await fetch(this.baseURL, {
|
|
2143
2313
|
method: "POST",
|
|
2144
2314
|
headers: {
|
|
2145
2315
|
Authorization: `Bearer ${this.apiKey}`,
|
|
2146
2316
|
"Content-Type": "application/json"
|
|
2147
2317
|
},
|
|
2148
|
-
body:
|
|
2318
|
+
body: requestRaw
|
|
2149
2319
|
});
|
|
2320
|
+
const responseRaw = await res.text().catch(() => "");
|
|
2321
|
+
logLlmWire(caller, requestRaw, responseRaw);
|
|
2150
2322
|
if (!res.ok) {
|
|
2151
|
-
|
|
2152
|
-
throw new Error(`snowflake-cortex request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
2323
|
+
throw new Error(`snowflake-cortex request failed: ${res.status} ${res.statusText} body=${responseRaw.slice(0, 500)}`);
|
|
2153
2324
|
}
|
|
2154
|
-
return
|
|
2325
|
+
return JSON.parse(responseRaw);
|
|
2155
2326
|
}
|
|
2156
2327
|
async call(model, options) {
|
|
2157
2328
|
const jsonMode = "jsonSchemaName" in options;
|
|
@@ -2168,15 +2339,15 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2168
2339
|
if (reasoning !== "none") {
|
|
2169
2340
|
body["reasoning_effort"] = reasoning;
|
|
2170
2341
|
}
|
|
2171
|
-
const data = await this.send(body);
|
|
2342
|
+
const data = await this.send(body, resolveLlmCaller(options));
|
|
2172
2343
|
if (data.error) {
|
|
2173
2344
|
throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
|
|
2174
2345
|
}
|
|
2175
|
-
const content = data.choices?.[0]?.message?.content ?? data.message ?? "";
|
|
2346
|
+
const content = stripThinkTags(data.choices?.[0]?.message?.content ?? data.message ?? "");
|
|
2176
2347
|
if (!content) {
|
|
2177
2348
|
throw new Error("Empty response from model");
|
|
2178
2349
|
}
|
|
2179
|
-
return jsonMode ?
|
|
2350
|
+
return jsonMode ? parseModelJson(content) : content;
|
|
2180
2351
|
}
|
|
2181
2352
|
async chatWithTools(model, options) {
|
|
2182
2353
|
const log5 = logger.child("llm:snowflake-cortex");
|
|
@@ -2189,12 +2360,12 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2189
2360
|
],
|
|
2190
2361
|
tools: options.tools.map(toCortexTool)
|
|
2191
2362
|
};
|
|
2192
|
-
const data = await this.send(body);
|
|
2363
|
+
const data = await this.send(body, resolveLlmCaller(options));
|
|
2193
2364
|
if (data.error) {
|
|
2194
2365
|
throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
|
|
2195
2366
|
}
|
|
2196
2367
|
const choice = data.choices?.[0];
|
|
2197
|
-
const content = choice?.message?.content ?? data.message ?? "";
|
|
2368
|
+
const content = stripThinkTags(choice?.message?.content ?? data.message ?? "");
|
|
2198
2369
|
const toolCalls = choice?.message?.tool_calls?.map((c) => ({
|
|
2199
2370
|
id: c.id,
|
|
2200
2371
|
function: {
|
|
@@ -2233,7 +2404,8 @@ register("stackit", StackitExecutor);
|
|
|
2233
2404
|
register("gmi", GmiExecutor);
|
|
2234
2405
|
register("zai", ZaiExecutor);
|
|
2235
2406
|
register("zenmux", ZenMuxExecutor);
|
|
2236
|
-
register("
|
|
2407
|
+
register("minimax", MiniMaxExecutor);
|
|
2408
|
+
register("minimax-cn", MiniMaxCnExecutor);
|
|
2237
2409
|
register("ionet", IoNetExecutor);
|
|
2238
2410
|
register("baseten", BasetenExecutor);
|
|
2239
2411
|
register("cortecs", CortecsExecutor);
|
|
@@ -2267,14 +2439,14 @@ var llm = new Proxy({}, {
|
|
|
2267
2439
|
// src/provider/promptLoader.ts
|
|
2268
2440
|
import { existsSync as existsSync2 } from "fs";
|
|
2269
2441
|
import { readFile as readFile2 } from "fs/promises";
|
|
2270
|
-
import path, { dirname as
|
|
2442
|
+
import path, { dirname as dirname2 } from "path";
|
|
2271
2443
|
import { fileURLToPath } from "url";
|
|
2272
2444
|
var log5 = logger.child("prompt-loader");
|
|
2273
2445
|
function fileName(promptKey) {
|
|
2274
2446
|
return promptKey.toLowerCase() + ".md";
|
|
2275
2447
|
}
|
|
2276
2448
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
2277
|
-
var __dirname2 =
|
|
2449
|
+
var __dirname2 = dirname2(__filename2);
|
|
2278
2450
|
var PROMPTS_DIR = existsSync2(path.resolve(__dirname2, "prompts")) ? path.resolve(__dirname2, "prompts") : path.resolve(__dirname2, "../../prompts");
|
|
2279
2451
|
async function loadPrompt(promptKey) {
|
|
2280
2452
|
const filePath = path.join(PROMPTS_DIR, fileName(promptKey));
|
|
@@ -2399,14 +2571,14 @@ function translateMessageHistory(personaName, entries) {
|
|
|
2399
2571
|
}
|
|
2400
2572
|
|
|
2401
2573
|
// src/brain/schedule.ts
|
|
2402
|
-
function
|
|
2574
|
+
function pad23(n) {
|
|
2403
2575
|
return n < 10 ? `0${n}` : `${n}`;
|
|
2404
2576
|
}
|
|
2405
2577
|
function formatDateKey(d) {
|
|
2406
|
-
return `${d.getFullYear()}-${
|
|
2578
|
+
return `${d.getFullYear()}-${pad23(d.getMonth() + 1)}-${pad23(d.getDate())}`;
|
|
2407
2579
|
}
|
|
2408
2580
|
function formatMonthKey(d) {
|
|
2409
|
-
return `${d.getFullYear()}-${
|
|
2581
|
+
return `${d.getFullYear()}-${pad23(d.getMonth() + 1)}`;
|
|
2410
2582
|
}
|
|
2411
2583
|
|
|
2412
2584
|
// src/brain/memory.ts
|
|
@@ -2494,22 +2666,22 @@ class Brain {
|
|
|
2494
2666
|
db;
|
|
2495
2667
|
space;
|
|
2496
2668
|
brainbase;
|
|
2497
|
-
memory;
|
|
2498
2669
|
availabilityCache = new Map;
|
|
2499
|
-
|
|
2670
|
+
memory;
|
|
2671
|
+
constructor(db, space, brainbase, memory) {
|
|
2500
2672
|
this.db = db;
|
|
2501
2673
|
this.space = space;
|
|
2502
2674
|
this.brainbase = brainbase;
|
|
2503
|
-
this.memory = memory;
|
|
2675
|
+
this.memory = memory ?? new Memory(this.db, this.space);
|
|
2504
2676
|
log7.debug(`Brain constructed: id=${brainbase.brainId} name=${brainbase.displayName} space=${space.name}`);
|
|
2505
2677
|
}
|
|
2506
2678
|
async createDailySchedule(datetime) {
|
|
2507
|
-
const
|
|
2508
|
-
log7.debug(`createDailySchedule: starting for ${
|
|
2679
|
+
const dateKey2 = formatDateKey(datetime);
|
|
2680
|
+
log7.debug(`createDailySchedule: starting for ${dateKey2}`);
|
|
2509
2681
|
try {
|
|
2510
|
-
const existing = await this.memory.get(`daily-schedule:${
|
|
2682
|
+
const existing = await this.memory.get(`daily-schedule:${dateKey2}`);
|
|
2511
2683
|
if (existing) {
|
|
2512
|
-
log7.debug(`createDailySchedule: cache hit for ${
|
|
2684
|
+
log7.debug(`createDailySchedule: cache hit for ${dateKey2}`);
|
|
2513
2685
|
try {
|
|
2514
2686
|
return JSON.parse(existing.content);
|
|
2515
2687
|
} catch (parseErr) {
|
|
@@ -2537,7 +2709,7 @@ class Brain {
|
|
|
2537
2709
|
}
|
|
2538
2710
|
const instruction = await loadPrompt("DAILY_SCHEDULE");
|
|
2539
2711
|
const promptMessage = [
|
|
2540
|
-
`Target date: ${
|
|
2712
|
+
`Target date: ${dateKey2} (${datetime.toLocaleDateString("en-US", { weekday: "long" })})`,
|
|
2541
2713
|
`Personality: ${this.brainbase.baseSystemPrompt}`,
|
|
2542
2714
|
monthlySummary ? `Monthly summary for this day: ${monthlySummary}` : "(no monthly summary available for this date)",
|
|
2543
2715
|
`Recent schedule (${twoDaysAgoKey}, 2 days ago): ${twoDaysAgoSchedule ? twoDaysAgoSchedule.items.map((s) => `${s.start} ${s.activity}`).join(", ") : "(no schedule on file for 2 days ago)"}`,
|
|
@@ -2548,6 +2720,7 @@ class Brain {
|
|
|
2548
2720
|
`);
|
|
2549
2721
|
log7.debug(`createDailySchedule: calling identity model`);
|
|
2550
2722
|
const schedule = await llm.call(llm.models.identity, {
|
|
2723
|
+
caller: "daily-schedule",
|
|
2551
2724
|
instruction,
|
|
2552
2725
|
message: promptMessage,
|
|
2553
2726
|
jsonSchemaName: "daily-schedule",
|
|
@@ -2555,15 +2728,15 @@ class Brain {
|
|
|
2555
2728
|
});
|
|
2556
2729
|
log7.debug(`createDailySchedule: model returned ${schedule.items.length} slots`);
|
|
2557
2730
|
await this.memory.add({
|
|
2558
|
-
customId: `daily-schedule:${
|
|
2731
|
+
customId: `daily-schedule:${dateKey2}`,
|
|
2559
2732
|
content: JSON.stringify(schedule),
|
|
2560
2733
|
metadata: {
|
|
2561
2734
|
kind: "schedule",
|
|
2562
2735
|
source: "createDailySchedule",
|
|
2563
|
-
date:
|
|
2736
|
+
date: dateKey2
|
|
2564
2737
|
}
|
|
2565
2738
|
});
|
|
2566
|
-
log7.debug(`createDailySchedule: persisted ${
|
|
2739
|
+
log7.debug(`createDailySchedule: persisted ${dateKey2}`);
|
|
2567
2740
|
return schedule;
|
|
2568
2741
|
} catch (error) {
|
|
2569
2742
|
let reason = error instanceof Error ? error.message + `(${error.name})` : String(error);
|
|
@@ -2575,17 +2748,21 @@ class Brain {
|
|
|
2575
2748
|
}
|
|
2576
2749
|
async regenerateSchedules() {
|
|
2577
2750
|
const today = new Date;
|
|
2751
|
+
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
|
2752
|
+
const monthly = await this.createMonthlySchedule(nextMonth);
|
|
2753
|
+
if (!monthly) {
|
|
2754
|
+
log7.debug(`regenerateSchedules: skip daily — monthly schedule generation failed`);
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2578
2757
|
const tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
|
|
2579
2758
|
await this.createDailySchedule(tomorrow);
|
|
2580
2759
|
await this.createDailySchedule(today);
|
|
2581
|
-
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
|
2582
|
-
await this.createMonthlySchedule(nextMonth);
|
|
2583
2760
|
}
|
|
2584
2761
|
async createMonthlySchedule(datetime) {
|
|
2585
2762
|
const year = datetime.getMonth() === 11 ? datetime.getFullYear() + 1 : datetime.getFullYear();
|
|
2586
2763
|
const month = (datetime.getMonth() + 1) % 12;
|
|
2587
2764
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
2588
|
-
const monthKey = `${year}-${
|
|
2765
|
+
const monthKey = `${year}-${pad23(month + 1)}`;
|
|
2589
2766
|
log7.debug(`createMonthlySchedule: starting for ${monthKey}`);
|
|
2590
2767
|
try {
|
|
2591
2768
|
const existing = await this.memory.get(`monthly-schedule:${monthKey}`);
|
|
@@ -2598,7 +2775,7 @@ class Brain {
|
|
|
2598
2775
|
}
|
|
2599
2776
|
}
|
|
2600
2777
|
const twoMonthsAgo = new Date(year, month - 2, 1);
|
|
2601
|
-
const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${
|
|
2778
|
+
const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${pad23(twoMonthsAgo.getMonth() + 1)}`;
|
|
2602
2779
|
log7.debug(`createMonthlySchedule: gathering context (history, ${twoMonthsAgoKey})`);
|
|
2603
2780
|
const [history, twoMonthsAgoStored] = await Promise.all([
|
|
2604
2781
|
this.getHistoryFacts(),
|
|
@@ -2626,6 +2803,7 @@ class Brain {
|
|
|
2626
2803
|
`);
|
|
2627
2804
|
log7.debug(`createMonthlySchedule: calling identity model`);
|
|
2628
2805
|
const schedule = await llm.call(llm.models.identity, {
|
|
2806
|
+
caller: "monthly-schedule",
|
|
2629
2807
|
instruction,
|
|
2630
2808
|
message: promptMessage,
|
|
2631
2809
|
jsonSchemaName: "monthly-schedule",
|
|
@@ -2656,11 +2834,11 @@ class Brain {
|
|
|
2656
2834
|
}
|
|
2657
2835
|
log7.debug(`sleepMemory: starting, ${history.length} messages`);
|
|
2658
2836
|
try {
|
|
2659
|
-
const
|
|
2837
|
+
const dateKey2 = formatDateKey(datetime);
|
|
2660
2838
|
const instruction = await loadPrompt("MEMOIR");
|
|
2661
2839
|
const historyBlock = translateMessageHistory(this.brainbase.displayName, history);
|
|
2662
2840
|
const promptMessage = [
|
|
2663
|
-
`Date: ${
|
|
2841
|
+
`Date: ${dateKey2}`,
|
|
2664
2842
|
`Personality: ${this.brainbase.baseSystemPrompt}`,
|
|
2665
2843
|
`Conversation log:`,
|
|
2666
2844
|
historyBlock
|
|
@@ -2669,20 +2847,21 @@ class Brain {
|
|
|
2669
2847
|
`);
|
|
2670
2848
|
log7.debug(`sleepMemory: calling identity model`);
|
|
2671
2849
|
const memoir = await llm.call(llm.models.identity, {
|
|
2850
|
+
caller: "sleep-memory",
|
|
2672
2851
|
instruction,
|
|
2673
2852
|
message: promptMessage
|
|
2674
2853
|
});
|
|
2675
2854
|
log7.debug(`sleepMemory: model returned ${memoir.length} chars`);
|
|
2676
2855
|
await this.memory.add({
|
|
2677
|
-
customId: `daily-journal:${
|
|
2856
|
+
customId: `daily-journal:${dateKey2}`,
|
|
2678
2857
|
content: memoir,
|
|
2679
2858
|
metadata: {
|
|
2680
2859
|
kind: "daily-journal",
|
|
2681
2860
|
source: "sleepMemory",
|
|
2682
|
-
date:
|
|
2861
|
+
date: dateKey2
|
|
2683
2862
|
}
|
|
2684
2863
|
});
|
|
2685
|
-
log7.debug(`sleepMemory: journal persisted for ${
|
|
2864
|
+
log7.debug(`sleepMemory: journal persisted for ${dateKey2}`);
|
|
2686
2865
|
return memoir;
|
|
2687
2866
|
} catch (error) {
|
|
2688
2867
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -2691,22 +2870,22 @@ class Brain {
|
|
|
2691
2870
|
}
|
|
2692
2871
|
}
|
|
2693
2872
|
async getTodayScheduledAvailability(datetime) {
|
|
2694
|
-
const
|
|
2873
|
+
const dateKey2 = formatDateKey(datetime);
|
|
2695
2874
|
try {
|
|
2696
|
-
const cached = this.availabilityCache.get(
|
|
2875
|
+
const cached = this.availabilityCache.get(dateKey2);
|
|
2697
2876
|
if (cached) {
|
|
2698
|
-
log7.debug(`getTodayScheduledAvailability: cache hit for ${
|
|
2877
|
+
log7.debug(`getTodayScheduledAvailability: cache hit for ${dateKey2}`);
|
|
2699
2878
|
return cached;
|
|
2700
2879
|
}
|
|
2701
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
2880
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
|
|
2702
2881
|
if (!stored) {
|
|
2703
|
-
log7.debug(`getTodayScheduledAvailability: no schedule for ${
|
|
2882
|
+
log7.debug(`getTodayScheduledAvailability: no schedule for ${dateKey2}`);
|
|
2704
2883
|
return null;
|
|
2705
2884
|
}
|
|
2706
2885
|
const dailySchedule = JSON.parse(stored.content);
|
|
2707
2886
|
const availability = await this.deriveAvailabilityFromSchedule(dailySchedule);
|
|
2708
|
-
this.availabilityCache.set(
|
|
2709
|
-
log7.debug(`getTodayScheduledAvailability: cached ${availability.items.length} windows for ${
|
|
2887
|
+
this.availabilityCache.set(dateKey2, availability);
|
|
2888
|
+
log7.debug(`getTodayScheduledAvailability: cached ${availability.items.length} windows for ${dateKey2}`);
|
|
2710
2889
|
return availability;
|
|
2711
2890
|
} catch (error) {
|
|
2712
2891
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -2717,7 +2896,7 @@ class Brain {
|
|
|
2717
2896
|
async getAvailability(datetime = new Date) {
|
|
2718
2897
|
const h = datetime.getHours();
|
|
2719
2898
|
const m = datetime.getMinutes();
|
|
2720
|
-
const hhmm = `${
|
|
2899
|
+
const hhmm = `${pad23(h)}:${pad23(m)}`;
|
|
2721
2900
|
const current = h * 60 + m;
|
|
2722
2901
|
const toMinutes = (s) => {
|
|
2723
2902
|
const [hh = 0, mm = 0] = s.split(":").map((x) => parseInt(x, 10));
|
|
@@ -2730,10 +2909,10 @@ class Brain {
|
|
|
2730
2909
|
return result;
|
|
2731
2910
|
}
|
|
2732
2911
|
async getCurrentAndAdjacentSlots(now) {
|
|
2733
|
-
const
|
|
2734
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
2912
|
+
const dateKey2 = formatDateKey(now);
|
|
2913
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
|
|
2735
2914
|
if (!stored) {
|
|
2736
|
-
log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${
|
|
2915
|
+
log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${dateKey2}`);
|
|
2737
2916
|
return [];
|
|
2738
2917
|
}
|
|
2739
2918
|
let schedule;
|
|
@@ -2766,6 +2945,7 @@ class Brain {
|
|
|
2766
2945
|
personality: this.brainbase.baseSystemPrompt
|
|
2767
2946
|
});
|
|
2768
2947
|
const result = await llm.call(llm.models.identity, {
|
|
2948
|
+
caller: "availability",
|
|
2769
2949
|
instruction,
|
|
2770
2950
|
message: promptMessage,
|
|
2771
2951
|
jsonSchemaName: "availability",
|
|
@@ -2841,6 +3021,7 @@ class Brain {
|
|
|
2841
3021
|
let choice;
|
|
2842
3022
|
try {
|
|
2843
3023
|
choice = await llm.chatWithTools(llm.models.conversation, {
|
|
3024
|
+
caller: initiate ? "start-conversation" : "send-message",
|
|
2844
3025
|
instruction: `${this.brainbase.baseSystemPrompt}
|
|
2845
3026
|
|
|
2846
3027
|
${instruction}`,
|
|
@@ -2912,11 +3093,11 @@ ${instruction}`,
|
|
|
2912
3093
|
${facts || "(none indexed)"}`;
|
|
2913
3094
|
}
|
|
2914
3095
|
async buildScheduleBlock(now) {
|
|
2915
|
-
const
|
|
3096
|
+
const dateKey2 = formatDateKey(now);
|
|
2916
3097
|
const currentSlots = await this.getCurrentAndAdjacentSlots(now);
|
|
2917
3098
|
const currentBlock = currentSlots.length > 0 ? `Currently (around ${now.toTimeString().slice(0, 5)}):
|
|
2918
3099
|
${currentSlots.map((s) => ` ${s.start}-${s.end} ${s.activity}${s.notes ? ` (${s.notes})` : ""}`).join(`
|
|
2919
|
-
`)}` : `Currently (${
|
|
3100
|
+
`)}` : `Currently (${dateKey2} ${now.toTimeString().slice(0, 5)}): (no matching slot in today's schedule)`;
|
|
2920
3101
|
const days = [
|
|
2921
3102
|
{
|
|
2922
3103
|
label: "Yesterday",
|
|
@@ -2939,9 +3120,9 @@ ${currentBlock}
|
|
|
2939
3120
|
${blocks.join(`
|
|
2940
3121
|
`)}`;
|
|
2941
3122
|
}
|
|
2942
|
-
async getDailyScheduleSummary(
|
|
3123
|
+
async getDailyScheduleSummary(dateKey2) {
|
|
2943
3124
|
try {
|
|
2944
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
3125
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey2}`);
|
|
2945
3126
|
if (!stored)
|
|
2946
3127
|
return null;
|
|
2947
3128
|
const schedule = JSON.parse(stored.content);
|
|
@@ -3012,6 +3193,7 @@ ${blocks.join(`
|
|
|
3012
3193
|
const personaInitInstruction = await loadPrompt("PERSONA_INIT");
|
|
3013
3194
|
log7.debug(`Brain.create: generating description`);
|
|
3014
3195
|
const description = await llm.call(llm.models.identity, {
|
|
3196
|
+
caller: "persona-init",
|
|
3015
3197
|
instruction: personaInitInstruction,
|
|
3016
3198
|
message: seed
|
|
3017
3199
|
});
|
|
@@ -3019,6 +3201,7 @@ ${blocks.join(`
|
|
|
3019
3201
|
const personaSystemInstruction = await loadPrompt("PERSONA_BASE_SYSTEM_PROMPT");
|
|
3020
3202
|
log7.debug(`Brain.create: generating base system prompt + dials`);
|
|
3021
3203
|
const generated = await llm.call(llm.models.identity, {
|
|
3204
|
+
caller: "base-system-prompt",
|
|
3022
3205
|
instruction: personaSystemInstruction,
|
|
3023
3206
|
message: description,
|
|
3024
3207
|
jsonSchemaName: "base-system-prompt",
|
|
@@ -3056,9 +3239,12 @@ ${personaSystemFixed}`;
|
|
|
3056
3239
|
log7.debug(`Brain.create: brainbase saved (id=${brainId})`);
|
|
3057
3240
|
return { brainId, brain: new Brain(db, space, brainbase, memory) };
|
|
3058
3241
|
} catch (error) {
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3242
|
+
let reason = error instanceof Error ? `${error.message} (${error.name})` : String(error);
|
|
3243
|
+
if (error instanceof BadRequestResponseError) {
|
|
3244
|
+
reason = `${reason} ${JSON.stringify(error.body)}`;
|
|
3245
|
+
}
|
|
3246
|
+
log7.debug(`Brain.create failed: ${reason}`);
|
|
3247
|
+
return { error: reason };
|
|
3062
3248
|
}
|
|
3063
3249
|
}
|
|
3064
3250
|
static async delete(brainId) {
|
|
@@ -3176,10 +3362,18 @@ var SLEEP_MEMORY_CRON_KEY = "__sleep-memory__";
|
|
|
3176
3362
|
var SLEEP_MEMORY_CRON_PATTERN = "0 * * * *";
|
|
3177
3363
|
var START_CONVERSATION_CRON_KEY = "__start-conversation__";
|
|
3178
3364
|
var START_CONVERSATION_CRON_PATTERN = "*/10 * * * *";
|
|
3179
|
-
var
|
|
3180
|
-
var
|
|
3181
|
-
var
|
|
3182
|
-
var
|
|
3365
|
+
var SCHEDULE_CRON_KEY = "__schedule__";
|
|
3366
|
+
var SCHEDULE_CRON_PATTERN = "0 0 * * *";
|
|
3367
|
+
var SCHEDULE_NOON_CRON_KEY = "__schedule-noon__";
|
|
3368
|
+
var SCHEDULE_NOON_CRON_PATTERN = "0 12 * * *";
|
|
3369
|
+
var DO_ACTIONS = ["generateSchedule", "sleepMemory"];
|
|
3370
|
+
var VIEW_THINGS = [
|
|
3371
|
+
"daily-schedule",
|
|
3372
|
+
"monthly-schedule",
|
|
3373
|
+
"sending-queue",
|
|
3374
|
+
"deferred-queue",
|
|
3375
|
+
"today-availability"
|
|
3376
|
+
];
|
|
3183
3377
|
|
|
3184
3378
|
class BaseChannel {
|
|
3185
3379
|
brain;
|
|
@@ -3199,24 +3393,27 @@ class BaseChannel {
|
|
|
3199
3393
|
static activeChannels = new Map;
|
|
3200
3394
|
constructor(brain) {
|
|
3201
3395
|
this.brain = brain;
|
|
3202
|
-
this.registerCron(SLEEP_MEMORY_CRON_KEY, SLEEP_MEMORY_CRON_PATTERN,
|
|
3203
|
-
|
|
3396
|
+
this.registerCron(SLEEP_MEMORY_CRON_KEY, SLEEP_MEMORY_CRON_PATTERN, () => this.runSleepMemory());
|
|
3397
|
+
this.registerCron(SCHEDULE_CRON_KEY, SCHEDULE_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3398
|
+
this.registerCron(SCHEDULE_NOON_CRON_KEY, SCHEDULE_NOON_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3399
|
+
this.registerCron(START_CONVERSATION_CRON_KEY, START_CONVERSATION_CRON_PATTERN, () => this.runStartConversation());
|
|
3400
|
+
}
|
|
3401
|
+
async runSleepMemory(force = false) {
|
|
3402
|
+
const dateKey2 = formatDateKey(new Date);
|
|
3403
|
+
if (!force) {
|
|
3204
3404
|
const availability = await this.brain.getAvailability();
|
|
3205
3405
|
if (availability.status !== "offline") {
|
|
3206
3406
|
logger.debug(`sleepMemory cron: skip — availability=${availability.status}`);
|
|
3207
3407
|
return;
|
|
3208
3408
|
}
|
|
3209
|
-
const existing = await this.brain.memory.get(`daily-journal:${
|
|
3409
|
+
const existing = await this.brain.memory.get(`daily-journal:${dateKey2}`);
|
|
3210
3410
|
if (existing) {
|
|
3211
|
-
logger.debug(`sleepMemory cron: skip — journal for ${
|
|
3411
|
+
logger.debug(`sleepMemory cron: skip — journal for ${dateKey2} exists`);
|
|
3212
3412
|
return;
|
|
3213
3413
|
}
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
this.registerCron(DAILY_SCHEDULE_CRON_KEY, DAILY_SCHEDULE_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3218
|
-
this.registerCron(DAILY_SCHEDULE_NOON_CRON_KEY, DAILY_SCHEDULE_NOON_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3219
|
-
this.registerCron(START_CONVERSATION_CRON_KEY, START_CONVERSATION_CRON_PATTERN, () => this.runStartConversation());
|
|
3414
|
+
}
|
|
3415
|
+
const history = await this.getMessageHistoryBetween(new Date(Date.now() - 24 * 60 * 60 * 1000), new Date);
|
|
3416
|
+
await this.brain.sleepMemory(new Date, history);
|
|
3220
3417
|
}
|
|
3221
3418
|
async regenerateSchedules() {
|
|
3222
3419
|
logger.debug(`regenerateSchedules: tick for ${this.brain.brainbase.displayName}`);
|
|
@@ -3237,8 +3434,8 @@ class BaseChannel {
|
|
|
3237
3434
|
return;
|
|
3238
3435
|
}
|
|
3239
3436
|
const now = new Date;
|
|
3240
|
-
const
|
|
3241
|
-
const count = this.startConversationCounters.get(
|
|
3437
|
+
const dateKey2 = formatDateKey(now);
|
|
3438
|
+
const count = this.startConversationCounters.get(dateKey2) ?? 0;
|
|
3242
3439
|
const countThreshold = this.brain.brainbase.startConversationCountThreshold;
|
|
3243
3440
|
if (count >= countThreshold)
|
|
3244
3441
|
return;
|
|
@@ -3251,7 +3448,7 @@ class BaseChannel {
|
|
|
3251
3448
|
});
|
|
3252
3449
|
if (replies.length === 0)
|
|
3253
3450
|
return;
|
|
3254
|
-
this.startConversationCounters.set(
|
|
3451
|
+
this.startConversationCounters.set(dateKey2, count + 1);
|
|
3255
3452
|
this.startConversationTimeout = true;
|
|
3256
3453
|
setTimeout(() => {
|
|
3257
3454
|
this.startConversationTimeout = false;
|
|
@@ -3280,7 +3477,7 @@ class BaseChannel {
|
|
|
3280
3477
|
}, callback);
|
|
3281
3478
|
}
|
|
3282
3479
|
getRegisteredCrons() {
|
|
3283
|
-
return scheduledJobs.filter((c) => c.name && c.name.startsWith(this.resolveCronPrefix()))
|
|
3480
|
+
return scheduledJobs.filter((c) => c.name && c.name.startsWith(this.resolveCronPrefix()));
|
|
3284
3481
|
}
|
|
3285
3482
|
pauseCron(key) {
|
|
3286
3483
|
const job = scheduledJobs.find((c) => c.name === this.resolveCronName(key));
|
|
@@ -3371,6 +3568,13 @@ class BaseChannel {
|
|
|
3371
3568
|
this.isChattingDebounce = null;
|
|
3372
3569
|
}, IS_CHATTING_DEBOUNCE_MS);
|
|
3373
3570
|
}
|
|
3571
|
+
async initAvailability() {
|
|
3572
|
+
const current = await this.brain.getAvailability();
|
|
3573
|
+
this.previousAvailability = current.status;
|
|
3574
|
+
logger.debug(`initAvailability: ${current.status}`);
|
|
3575
|
+
await this.setAvailability(current.status);
|
|
3576
|
+
this.ensureAvailabilityWatcher();
|
|
3577
|
+
}
|
|
3374
3578
|
ensureAvailabilityWatcher() {
|
|
3375
3579
|
if (this.isCronStarted(AVAILABILITY_WATCHER_KEY))
|
|
3376
3580
|
return;
|
|
@@ -3380,6 +3584,9 @@ class BaseChannel {
|
|
|
3380
3584
|
const prev = this.previousAvailability;
|
|
3381
3585
|
this.previousAvailability = current.status;
|
|
3382
3586
|
logger.debug(`availabilityWatcher: ${prev ?? "(initial)"} → ${current.status}`);
|
|
3587
|
+
if (prev !== current.status) {
|
|
3588
|
+
await this.setAvailability(current.status);
|
|
3589
|
+
}
|
|
3383
3590
|
if (prev !== null && prev !== "online" && current.status === "online") {
|
|
3384
3591
|
await this.flushDeferred();
|
|
3385
3592
|
}
|
|
@@ -3428,6 +3635,88 @@ class BaseChannel {
|
|
|
3428
3635
|
static all() {
|
|
3429
3636
|
return Array.from(BaseChannel.activeChannels.values());
|
|
3430
3637
|
}
|
|
3638
|
+
static forceDo(brainId, action) {
|
|
3639
|
+
const channel = BaseChannel.activeChannels.get(brainId);
|
|
3640
|
+
if (!channel) {
|
|
3641
|
+
return {
|
|
3642
|
+
ok: false,
|
|
3643
|
+
error: `no active channel for brain "${brainId}" (is it activated and daemon running?)`
|
|
3644
|
+
};
|
|
3645
|
+
}
|
|
3646
|
+
const displayName = channel.brain.brainbase.displayName;
|
|
3647
|
+
logger.info(`do ${action}: queued for "${displayName}" (${brainId})`);
|
|
3648
|
+
(async () => {
|
|
3649
|
+
try {
|
|
3650
|
+
if (action === "generateSchedule") {
|
|
3651
|
+
await channel.regenerateSchedules();
|
|
3652
|
+
} else {
|
|
3653
|
+
await channel.runSleepMemory(true);
|
|
3654
|
+
}
|
|
3655
|
+
logger.success(`do ${action}: done for "${displayName}" (${brainId})`);
|
|
3656
|
+
} catch (error) {
|
|
3657
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
3658
|
+
logger.error(`do ${action}: failed for "${displayName}" (${brainId}): ${reason}`);
|
|
3659
|
+
}
|
|
3660
|
+
})();
|
|
3661
|
+
return { ok: true, displayName };
|
|
3662
|
+
}
|
|
3663
|
+
static async view(brainId, thing) {
|
|
3664
|
+
const channel = BaseChannel.activeChannels.get(brainId);
|
|
3665
|
+
if (!channel) {
|
|
3666
|
+
return {
|
|
3667
|
+
ok: false,
|
|
3668
|
+
error: `no active channel for brain "${brainId}" (is it activated and daemon running?)`
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
const displayName = channel.brain.brainbase.displayName;
|
|
3672
|
+
logger.debug(`view ${thing}: "${displayName}" (${brainId})`);
|
|
3673
|
+
return {
|
|
3674
|
+
ok: true,
|
|
3675
|
+
displayName,
|
|
3676
|
+
value: await channel.readView(thing)
|
|
3677
|
+
};
|
|
3678
|
+
}
|
|
3679
|
+
async readView(thing) {
|
|
3680
|
+
const now = new Date;
|
|
3681
|
+
switch (thing) {
|
|
3682
|
+
case "daily-schedule": {
|
|
3683
|
+
const key = formatDateKey(now);
|
|
3684
|
+
const stored = await this.brain.memory.get(`daily-schedule:${key}`);
|
|
3685
|
+
if (!stored)
|
|
3686
|
+
return null;
|
|
3687
|
+
try {
|
|
3688
|
+
return { key, schedule: JSON.parse(stored.content) };
|
|
3689
|
+
} catch {
|
|
3690
|
+
return { key, raw: stored.content };
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
case "monthly-schedule": {
|
|
3694
|
+
const key = formatMonthKey(now);
|
|
3695
|
+
const stored = await this.brain.memory.get(`monthly-schedule:${key}`);
|
|
3696
|
+
if (!stored)
|
|
3697
|
+
return null;
|
|
3698
|
+
try {
|
|
3699
|
+
return { key, schedule: JSON.parse(stored.content) };
|
|
3700
|
+
} catch {
|
|
3701
|
+
return { key, raw: stored.content };
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
case "sending-queue":
|
|
3705
|
+
return this.isSendingQueue.map((m) => ({
|
|
3706
|
+
sender: m.sender,
|
|
3707
|
+
time: m.time.toISOString(),
|
|
3708
|
+
content: m.content
|
|
3709
|
+
}));
|
|
3710
|
+
case "deferred-queue":
|
|
3711
|
+
return this.deferredQueue.map((m) => ({
|
|
3712
|
+
sender: m.sender,
|
|
3713
|
+
time: m.time.toISOString(),
|
|
3714
|
+
content: m.content
|
|
3715
|
+
}));
|
|
3716
|
+
case "today-availability":
|
|
3717
|
+
return await this.brain.getTodayScheduledAvailability(now);
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3431
3720
|
static async shutdownAll() {
|
|
3432
3721
|
await Promise.all(BaseChannel.all().map((c) => c.shutdown()));
|
|
3433
3722
|
}
|
|
@@ -3440,8 +3729,8 @@ class BaseChannel {
|
|
|
3440
3729
|
logger.debug(`shutdown: done`);
|
|
3441
3730
|
}
|
|
3442
3731
|
stopOwnCrons() {
|
|
3443
|
-
for (const
|
|
3444
|
-
|
|
3732
|
+
for (const cron of this.getRegisteredCrons()) {
|
|
3733
|
+
cron.stop();
|
|
3445
3734
|
}
|
|
3446
3735
|
}
|
|
3447
3736
|
clearTimers() {
|
|
@@ -3551,6 +3840,7 @@ class DiscordChannel extends BaseChannel {
|
|
|
3551
3840
|
logger.debug(`DiscordClientReady: resolving configured channel ${channelId}`);
|
|
3552
3841
|
this.resolveConfiguredChannel(channelId);
|
|
3553
3842
|
}
|
|
3843
|
+
this.initAvailability();
|
|
3554
3844
|
});
|
|
3555
3845
|
this.client.on(Events.MessageCreate, (msg) => {
|
|
3556
3846
|
if (msg.author.bot)
|
|
@@ -3719,6 +4009,7 @@ class TelegramChannel extends BaseChannel {
|
|
|
3719
4009
|
this.registerActive();
|
|
3720
4010
|
this.bot.onStart(({ info }) => {
|
|
3721
4011
|
logger.success(`Telegram ready as @${info.username}`);
|
|
4012
|
+
this.initAvailability();
|
|
3722
4013
|
});
|
|
3723
4014
|
this.bot.on("message", (ctx) => {
|
|
3724
4015
|
if (ctx.from?.isBot())
|
|
@@ -3811,8 +4102,8 @@ class TelegramChannel extends BaseChannel {
|
|
|
3811
4102
|
|
|
3812
4103
|
// src/utils/daemonClient.ts
|
|
3813
4104
|
import { connect } from "node:net";
|
|
3814
|
-
import { join as
|
|
3815
|
-
var DAEMON_SOCKET_PATH =
|
|
4105
|
+
import { join as join4 } from "node:path";
|
|
4106
|
+
var DAEMON_SOCKET_PATH = join4(config.brainboxRoot, "daemon.sock");
|
|
3816
4107
|
async function sendToDaemon(payload) {
|
|
3817
4108
|
logger.debug(`sendToDaemon: command="${payload.command}" args=${JSON.stringify(payload.args) ?? "null"}`);
|
|
3818
4109
|
let reply;
|
|
@@ -3874,6 +4165,7 @@ function exchangeOnce(payload) {
|
|
|
3874
4165
|
// src/commands/daemon.ts
|
|
3875
4166
|
import { createServer } from "node:net";
|
|
3876
4167
|
import { chmodSync, unlinkSync } from "node:fs";
|
|
4168
|
+
import { join as join5 } from "node:path";
|
|
3877
4169
|
|
|
3878
4170
|
// src/commands/daemon/commands.ts
|
|
3879
4171
|
var log8 = logger.child("daemon-cmd");
|
|
@@ -3934,6 +4226,63 @@ defineCommand({
|
|
|
3934
4226
|
}
|
|
3935
4227
|
});
|
|
3936
4228
|
|
|
4229
|
+
// src/commands/daemon/doCommand.ts
|
|
4230
|
+
defineCommand({
|
|
4231
|
+
name: "do",
|
|
4232
|
+
handler: async (args) => {
|
|
4233
|
+
const action = args?.action;
|
|
4234
|
+
const brainId = args?.brainId;
|
|
4235
|
+
logger.debug(`do handler: action="${action}" brainId="${brainId}"`);
|
|
4236
|
+
if (typeof action !== "string" || !DO_ACTIONS.includes(action)) {
|
|
4237
|
+
return {
|
|
4238
|
+
ok: false,
|
|
4239
|
+
error: `invalid action (expected one of: ${DO_ACTIONS.join(", ")})`
|
|
4240
|
+
};
|
|
4241
|
+
}
|
|
4242
|
+
if (typeof brainId !== "string" || brainId.trim().length === 0) {
|
|
4243
|
+
return { ok: false, error: "missing brainId" };
|
|
4244
|
+
}
|
|
4245
|
+
const result = BaseChannel.forceDo(brainId.trim(), action);
|
|
4246
|
+
if (!result.ok)
|
|
4247
|
+
return result;
|
|
4248
|
+
return {
|
|
4249
|
+
ok: true,
|
|
4250
|
+
result: { action, brainId: brainId.trim(), displayName: result.displayName }
|
|
4251
|
+
};
|
|
4252
|
+
}
|
|
4253
|
+
});
|
|
4254
|
+
|
|
4255
|
+
// src/commands/daemon/viewCommand.ts
|
|
4256
|
+
defineCommand({
|
|
4257
|
+
name: "view",
|
|
4258
|
+
handler: async (args) => {
|
|
4259
|
+
const thing = args?.thing;
|
|
4260
|
+
const brainId = args?.brainId;
|
|
4261
|
+
logger.debug(`view handler: thing="${thing}" brainId="${brainId}"`);
|
|
4262
|
+
if (typeof thing !== "string" || !VIEW_THINGS.includes(thing)) {
|
|
4263
|
+
return {
|
|
4264
|
+
ok: false,
|
|
4265
|
+
error: `invalid thing (expected one of: ${VIEW_THINGS.join(", ")})`
|
|
4266
|
+
};
|
|
4267
|
+
}
|
|
4268
|
+
if (typeof brainId !== "string" || brainId.trim().length === 0) {
|
|
4269
|
+
return { ok: false, error: "missing brainId" };
|
|
4270
|
+
}
|
|
4271
|
+
const result = await BaseChannel.view(brainId.trim(), thing);
|
|
4272
|
+
if (!result.ok)
|
|
4273
|
+
return result;
|
|
4274
|
+
return {
|
|
4275
|
+
ok: true,
|
|
4276
|
+
result: {
|
|
4277
|
+
thing,
|
|
4278
|
+
brainId: brainId.trim(),
|
|
4279
|
+
displayName: result.displayName,
|
|
4280
|
+
value: result.value
|
|
4281
|
+
}
|
|
4282
|
+
};
|
|
4283
|
+
}
|
|
4284
|
+
});
|
|
4285
|
+
|
|
3937
4286
|
// src/commands/daemon.ts
|
|
3938
4287
|
async function startChannels() {
|
|
3939
4288
|
const items = await brainManager.listAvailableBrain();
|
|
@@ -3968,7 +4317,10 @@ async function startChannels() {
|
|
|
3968
4317
|
return started;
|
|
3969
4318
|
}
|
|
3970
4319
|
async function daemon() {
|
|
3971
|
-
|
|
4320
|
+
const logDir = join5(config.brainboxRoot, "logs");
|
|
4321
|
+
logger.configure({ logDir });
|
|
4322
|
+
configureLlmLog(config.debug ? join5(logDir, "llm") : undefined);
|
|
4323
|
+
logger.debug(`daemon: boot (debug=${config.debug}, logDir=${logDir}, llmLog=${config.debug})`);
|
|
3972
4324
|
const started = await startChannels();
|
|
3973
4325
|
if (started === 0) {
|
|
3974
4326
|
logger.info("No activated brains with channels. Daemon idling.");
|
|
@@ -4108,7 +4460,8 @@ async function deactivateBrain(brainId) {
|
|
|
4108
4460
|
async function createBrain(displayName, seed, options) {
|
|
4109
4461
|
logger.debug(`createBrain: name="${displayName}" seed length=${seed.length} schedule=${options.schedule}`);
|
|
4110
4462
|
const result = await Brain.create(displayName, seed);
|
|
4111
|
-
if (
|
|
4463
|
+
if ("error" in result) {
|
|
4464
|
+
logger.error(`Failed to create brain "${displayName}": ${result.error}`);
|
|
4112
4465
|
process.exitCode = 1;
|
|
4113
4466
|
return;
|
|
4114
4467
|
}
|
|
@@ -4134,6 +4487,33 @@ async function removeBrain(brainId) {
|
|
|
4134
4487
|
}
|
|
4135
4488
|
logger.success(`Removed brain "${brain.displayName}" (${chalk2.cyan(brainId)})`);
|
|
4136
4489
|
}
|
|
4490
|
+
async function doAction(action, brainId) {
|
|
4491
|
+
if (!DO_ACTIONS.includes(action)) {
|
|
4492
|
+
logger.error(`Unknown action "${action}". Expected one of: ${DO_ACTIONS.join(", ")}`);
|
|
4493
|
+
process.exit(1);
|
|
4494
|
+
}
|
|
4495
|
+
logger.debug(`do: action=${action} brainId=${brainId}`);
|
|
4496
|
+
const response = await sendToDaemon({
|
|
4497
|
+
command: "do",
|
|
4498
|
+
args: { action, brainId }
|
|
4499
|
+
});
|
|
4500
|
+
const name = response.result?.displayName ?? brainId;
|
|
4501
|
+
logger.success(`Successfully sent ${action} for "${name}" (${brainId}).`);
|
|
4502
|
+
}
|
|
4503
|
+
async function viewThing(thing, brainId) {
|
|
4504
|
+
if (!VIEW_THINGS.includes(thing)) {
|
|
4505
|
+
logger.error(`Unknown thing "${thing}". Expected one of: ${VIEW_THINGS.join(", ")}`);
|
|
4506
|
+
process.exit(1);
|
|
4507
|
+
}
|
|
4508
|
+
logger.debug(`view: thing=${thing} brainId=${brainId}`);
|
|
4509
|
+
const response = await sendToDaemon({
|
|
4510
|
+
command: "view",
|
|
4511
|
+
args: { thing, brainId }
|
|
4512
|
+
});
|
|
4513
|
+
const name = response.result?.displayName ?? brainId;
|
|
4514
|
+
logger.info(`${thing} — "${name}" (${brainId})`);
|
|
4515
|
+
console.log(JSON.stringify(response.result?.value ?? null, null, 2));
|
|
4516
|
+
}
|
|
4137
4517
|
function register3(program) {
|
|
4138
4518
|
const cmd = registerCommand(program, {
|
|
4139
4519
|
name: "brain",
|
|
@@ -4144,6 +4524,8 @@ function register3(program) {
|
|
|
4144
4524
|
cmd.command("remove <brainId>").description("Remove a brain and its memory").action(removeBrain);
|
|
4145
4525
|
cmd.command("activate <brainId>").description("Activate a brain").action(activateBrain);
|
|
4146
4526
|
cmd.command("deactivate <brainId>").description("Deactivate a brain").action(deactivateBrain);
|
|
4527
|
+
cmd.command("do <action> <brainId>").description(`Force-run a daemon job (${DO_ACTIONS.join(" | ")}) for a live brain`).action(doAction);
|
|
4528
|
+
cmd.command("view <thing> <brainId>").description(`Inspect a live brain value (${VIEW_THINGS.join(" | ")})`).action(viewThing);
|
|
4147
4529
|
return cmd;
|
|
4148
4530
|
}
|
|
4149
4531
|
|
|
@@ -4265,6 +4647,9 @@ function RawInput({
|
|
|
4265
4647
|
onSubmit
|
|
4266
4648
|
}) {
|
|
4267
4649
|
const [value, setValue] = useState(initialValue);
|
|
4650
|
+
useEffect(() => {
|
|
4651
|
+
setValue(initialValue);
|
|
4652
|
+
}, [prompt, initialValue]);
|
|
4268
4653
|
useInput((input, key) => {
|
|
4269
4654
|
if (key.return) {
|
|
4270
4655
|
onSubmit(value);
|
|
@@ -4960,6 +5345,19 @@ function Select(props) {
|
|
|
4960
5345
|
|
|
4961
5346
|
// src/commands/onboard.tsx
|
|
4962
5347
|
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
5348
|
+
function ok(msg) {
|
|
5349
|
+
console.log(chalk3.green(`✔ ${msg}`));
|
|
5350
|
+
}
|
|
5351
|
+
function info(msg) {
|
|
5352
|
+
console.log(msg);
|
|
5353
|
+
}
|
|
5354
|
+
function show(active, node, status) {
|
|
5355
|
+
active.current.unmount();
|
|
5356
|
+
console.clear();
|
|
5357
|
+
if (status)
|
|
5358
|
+
ok(status);
|
|
5359
|
+
active.current = render3(node);
|
|
5360
|
+
}
|
|
4963
5361
|
function ProviderApp({
|
|
4964
5362
|
providers,
|
|
4965
5363
|
onDone
|
|
@@ -5031,7 +5429,6 @@ function ProviderApp({
|
|
|
5031
5429
|
const extras = PROVIDER_EXTRA_FIELDS[stage.provider] ?? [];
|
|
5032
5430
|
if (extras.length === 0) {
|
|
5033
5431
|
setProviderAuth(stage.provider, { apiKey });
|
|
5034
|
-
logger.success(`Saved ${stage.provider} to auth.yaml`);
|
|
5035
5432
|
onDone({ provider: stage.provider });
|
|
5036
5433
|
return;
|
|
5037
5434
|
}
|
|
@@ -5052,13 +5449,6 @@ function ProviderApp({
|
|
|
5052
5449
|
}, undefined, true, undefined, this);
|
|
5053
5450
|
}
|
|
5054
5451
|
const nextField = stage.fields[0];
|
|
5055
|
-
if (!nextField) {
|
|
5056
|
-
setProviderAuth(stage.provider, stage.values);
|
|
5057
|
-
logger.success(`Saved ${stage.provider} to auth.yaml`);
|
|
5058
|
-
return /* @__PURE__ */ jsxDEV5(Text5, {
|
|
5059
|
-
children: "Continuing…"
|
|
5060
|
-
}, undefined, false, undefined, this);
|
|
5061
|
-
}
|
|
5062
5452
|
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
5063
5453
|
flexDirection: "column",
|
|
5064
5454
|
children: [
|
|
@@ -5084,6 +5474,11 @@ function ProviderApp({
|
|
|
5084
5474
|
const values = { ...stage.values };
|
|
5085
5475
|
if (value)
|
|
5086
5476
|
values[nextField] = value;
|
|
5477
|
+
if (remaining.length === 0) {
|
|
5478
|
+
setProviderAuth(stage.provider, values);
|
|
5479
|
+
onDone({ provider: stage.provider });
|
|
5480
|
+
return;
|
|
5481
|
+
}
|
|
5087
5482
|
setStage({
|
|
5088
5483
|
kind: "extras",
|
|
5089
5484
|
provider: stage.provider,
|
|
@@ -5112,7 +5507,7 @@ function ModelApp2({
|
|
|
5112
5507
|
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5113
5508
|
dimColor: true,
|
|
5114
5509
|
children: [
|
|
5115
|
-
"
|
|
5510
|
+
"model name, or ",
|
|
5116
5511
|
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5117
5512
|
color: "cyan",
|
|
5118
5513
|
children: [
|
|
@@ -5120,7 +5515,7 @@ function ModelApp2({
|
|
|
5120
5515
|
"/"
|
|
5121
5516
|
]
|
|
5122
5517
|
}, undefined, true, undefined, this),
|
|
5123
|
-
"model
|
|
5518
|
+
"model — fine-tune later with ",
|
|
5124
5519
|
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5125
5520
|
color: "cyan",
|
|
5126
5521
|
children: "brainbox model"
|
|
@@ -5135,14 +5530,15 @@ function ModelApp2({
|
|
|
5135
5530
|
setError("Model cannot be empty");
|
|
5136
5531
|
return;
|
|
5137
5532
|
}
|
|
5138
|
-
|
|
5139
|
-
|
|
5533
|
+
const prefix = `${provider}/`;
|
|
5534
|
+
const full = value.startsWith(prefix) ? value : `${prefix}${value}`;
|
|
5535
|
+
if (full === prefix) {
|
|
5536
|
+
setError("Model cannot be empty");
|
|
5140
5537
|
return;
|
|
5141
5538
|
}
|
|
5142
|
-
setModelSlot("identity",
|
|
5143
|
-
setModelSlot("conversation",
|
|
5144
|
-
|
|
5145
|
-
onDone();
|
|
5539
|
+
setModelSlot("identity", full);
|
|
5540
|
+
setModelSlot("conversation", full);
|
|
5541
|
+
onDone(full);
|
|
5146
5542
|
}
|
|
5147
5543
|
}, undefined, false, undefined, this),
|
|
5148
5544
|
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
@@ -5178,7 +5574,6 @@ function SuperMemoryApp({
|
|
|
5178
5574
|
return;
|
|
5179
5575
|
}
|
|
5180
5576
|
setSupermemoryKey(key);
|
|
5181
|
-
logger.success("Saved supermemory key to brainbox.yaml");
|
|
5182
5577
|
onDone();
|
|
5183
5578
|
}
|
|
5184
5579
|
}, undefined, false, undefined, this),
|
|
@@ -5194,6 +5589,7 @@ function BrainApp({
|
|
|
5194
5589
|
}) {
|
|
5195
5590
|
const [stage, setStage] = useState5({ kind: "name" });
|
|
5196
5591
|
const [error, setError] = useState5(null);
|
|
5592
|
+
const [busy, setBusy] = useState5(false);
|
|
5197
5593
|
if (stage.kind === "name") {
|
|
5198
5594
|
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
5199
5595
|
flexDirection: "column",
|
|
@@ -5245,12 +5641,14 @@ function BrainApp({
|
|
|
5245
5641
|
dimColor: true,
|
|
5246
5642
|
children: "One sentence about who they are. The model will expand it."
|
|
5247
5643
|
}, undefined, false, undefined, this),
|
|
5248
|
-
/* @__PURE__ */ jsxDEV5(
|
|
5644
|
+
busy ? /* @__PURE__ */ jsxDEV5(Text5, {
|
|
5645
|
+
dimColor: true,
|
|
5646
|
+
children: "Creating brain… (This can take few minutes depending on the model's response speed)"
|
|
5647
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(TextInput, {
|
|
5249
5648
|
prompt: "seed> ",
|
|
5250
|
-
onSubmit:
|
|
5649
|
+
onSubmit: (raw) => {
|
|
5251
5650
|
const seed = raw.trim();
|
|
5252
5651
|
if (seed === "skip") {
|
|
5253
|
-
logger.info("Skipped brain creation.");
|
|
5254
5652
|
onDone({ brainId: "", displayName: stage.displayName });
|
|
5255
5653
|
return;
|
|
5256
5654
|
}
|
|
@@ -5258,13 +5656,19 @@ function BrainApp({
|
|
|
5258
5656
|
setError("Seed cannot be empty (or type 'skip')");
|
|
5259
5657
|
return;
|
|
5260
5658
|
}
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5659
|
+
setBusy(true);
|
|
5660
|
+
setError(null);
|
|
5661
|
+
Brain.create(stage.displayName, seed).then((result) => {
|
|
5662
|
+
setBusy(false);
|
|
5663
|
+
if ("error" in result) {
|
|
5664
|
+
setError(`Brain creation failed: ${result.error} (fix seed, or type 'skip')`);
|
|
5665
|
+
return;
|
|
5666
|
+
}
|
|
5667
|
+
onDone({
|
|
5668
|
+
brainId: result.brainId,
|
|
5669
|
+
displayName: stage.displayName
|
|
5670
|
+
});
|
|
5671
|
+
});
|
|
5268
5672
|
}
|
|
5269
5673
|
}, undefined, false, undefined, this),
|
|
5270
5674
|
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
@@ -5305,8 +5709,7 @@ function ChannelApp({
|
|
|
5305
5709
|
items: ["discord", "telegram", "skip"],
|
|
5306
5710
|
onSelect: (v) => {
|
|
5307
5711
|
if (v === "skip") {
|
|
5308
|
-
|
|
5309
|
-
onDone();
|
|
5712
|
+
onDone("Skipped channel setup.");
|
|
5310
5713
|
return;
|
|
5311
5714
|
}
|
|
5312
5715
|
setError(null);
|
|
@@ -5365,37 +5768,41 @@ function ChannelApp({
|
|
|
5365
5768
|
}, undefined, true, undefined, this),
|
|
5366
5769
|
/* @__PURE__ */ jsxDEV5(TextInput, {
|
|
5367
5770
|
prompt: `${stage.kind_ === "discord" ? "channelId" : "chatId"}> `,
|
|
5368
|
-
onSubmit:
|
|
5771
|
+
onSubmit: (raw) => {
|
|
5369
5772
|
const target = raw.trim();
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
}
|
|
5375
|
-
let updated;
|
|
5376
|
-
if (stage.kind_ === "discord") {
|
|
5377
|
-
updated = {
|
|
5378
|
-
...existing,
|
|
5379
|
-
channel: "discord",
|
|
5380
|
-
discord: { token: stage.token, channelId: target || undefined },
|
|
5381
|
-
activated: true
|
|
5382
|
-
};
|
|
5383
|
-
} else {
|
|
5384
|
-
const chatId = target ? Number(target) : undefined;
|
|
5385
|
-
if (target && Number.isNaN(chatId)) {
|
|
5386
|
-
setError("chatId must be a number");
|
|
5773
|
+
(async () => {
|
|
5774
|
+
const existing = await brainManager.loadBrain(brainId);
|
|
5775
|
+
if (!existing) {
|
|
5776
|
+
setError(`Brain ${brainId} no longer exists`);
|
|
5387
5777
|
return;
|
|
5388
5778
|
}
|
|
5389
|
-
updated
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5779
|
+
let updated;
|
|
5780
|
+
if (stage.kind_ === "discord") {
|
|
5781
|
+
updated = {
|
|
5782
|
+
...existing,
|
|
5783
|
+
channel: "discord",
|
|
5784
|
+
discord: {
|
|
5785
|
+
token: stage.token,
|
|
5786
|
+
channelId: target || undefined
|
|
5787
|
+
},
|
|
5788
|
+
activated: true
|
|
5789
|
+
};
|
|
5790
|
+
} else {
|
|
5791
|
+
const chatId = target ? Number(target) : undefined;
|
|
5792
|
+
if (target && Number.isNaN(chatId)) {
|
|
5793
|
+
setError("chatId must be a number");
|
|
5794
|
+
return;
|
|
5795
|
+
}
|
|
5796
|
+
updated = {
|
|
5797
|
+
...existing,
|
|
5798
|
+
channel: "telegram",
|
|
5799
|
+
telegram: { token: stage.token, chatId },
|
|
5800
|
+
activated: true
|
|
5801
|
+
};
|
|
5802
|
+
}
|
|
5803
|
+
await brainManager.saveBrain(brainId, updated);
|
|
5804
|
+
onDone(`Bound ${displayName} → ${stage.kind_}${target ? ` (${target})` : " (pairing mode)"}`);
|
|
5805
|
+
})();
|
|
5399
5806
|
}
|
|
5400
5807
|
}, undefined, false, undefined, this),
|
|
5401
5808
|
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
@@ -5406,46 +5813,52 @@ function ChannelApp({
|
|
|
5406
5813
|
}, undefined, true, undefined, this);
|
|
5407
5814
|
}
|
|
5408
5815
|
async function runOnboard() {
|
|
5409
|
-
|
|
5816
|
+
console.clear();
|
|
5817
|
+
info(`Welcome — let's get ${chalk3.bold("brainbox")} ready.`);
|
|
5410
5818
|
const providers = listProviderNames().slice().sort();
|
|
5411
5819
|
const { promise, resolve: resolve2 } = Promise.withResolvers();
|
|
5412
|
-
|
|
5820
|
+
const active = { current: null };
|
|
5821
|
+
active.current = render3(/* @__PURE__ */ jsxDEV5(ProviderApp, {
|
|
5413
5822
|
providers,
|
|
5414
5823
|
onDone: (p) => {
|
|
5415
|
-
active
|
|
5416
|
-
active = render3(/* @__PURE__ */ jsxDEV5(ModelApp2, {
|
|
5824
|
+
show(active, /* @__PURE__ */ jsxDEV5(ModelApp2, {
|
|
5417
5825
|
provider: p.provider,
|
|
5418
|
-
onDone: () => {
|
|
5419
|
-
active
|
|
5420
|
-
active = render3(/* @__PURE__ */ jsxDEV5(SuperMemoryApp, {
|
|
5826
|
+
onDone: (model) => {
|
|
5827
|
+
show(active, /* @__PURE__ */ jsxDEV5(SuperMemoryApp, {
|
|
5421
5828
|
onDone: () => {
|
|
5422
|
-
active
|
|
5423
|
-
active = render3(/* @__PURE__ */ jsxDEV5(BrainApp, {
|
|
5829
|
+
show(active, /* @__PURE__ */ jsxDEV5(BrainApp, {
|
|
5424
5830
|
onDone: (b) => {
|
|
5425
|
-
active.unmount();
|
|
5426
5831
|
if (!b.brainId) {
|
|
5832
|
+
active.current.unmount();
|
|
5833
|
+
console.clear();
|
|
5834
|
+
info("Skipped brain creation.");
|
|
5427
5835
|
resolve2();
|
|
5428
5836
|
return;
|
|
5429
5837
|
}
|
|
5430
|
-
active
|
|
5838
|
+
show(active, /* @__PURE__ */ jsxDEV5(ChannelApp, {
|
|
5431
5839
|
brainId: b.brainId,
|
|
5432
5840
|
displayName: b.displayName,
|
|
5433
|
-
onDone: () => {
|
|
5434
|
-
active.unmount();
|
|
5841
|
+
onDone: (status) => {
|
|
5842
|
+
active.current.unmount();
|
|
5843
|
+
console.clear();
|
|
5844
|
+
if (status.startsWith("Skipped"))
|
|
5845
|
+
info(status);
|
|
5846
|
+
else
|
|
5847
|
+
ok(status);
|
|
5435
5848
|
resolve2();
|
|
5436
5849
|
}
|
|
5437
|
-
}, undefined, false, undefined, this));
|
|
5850
|
+
}, undefined, false, undefined, this), `Created brain "${b.displayName}" (${chalk3.cyan(b.brainId)})`);
|
|
5438
5851
|
}
|
|
5439
|
-
}, undefined, false, undefined, this));
|
|
5852
|
+
}, undefined, false, undefined, this), "Saved supermemory key to brainbox.yaml");
|
|
5440
5853
|
}
|
|
5441
|
-
}, undefined, false, undefined, this));
|
|
5854
|
+
}, undefined, false, undefined, this), `Set identity + conversation model to ${model}`);
|
|
5442
5855
|
}
|
|
5443
|
-
}, undefined, false, undefined, this));
|
|
5856
|
+
}, undefined, false, undefined, this), `Saved ${p.provider} to auth.yaml`);
|
|
5444
5857
|
}
|
|
5445
5858
|
}, undefined, false, undefined, this));
|
|
5446
5859
|
await promise;
|
|
5447
|
-
|
|
5448
|
-
|
|
5860
|
+
ok("Onboarding complete.");
|
|
5861
|
+
info(`Run ${chalk3.cyan("brainbox daemon")} to bring it online.`);
|
|
5449
5862
|
}
|
|
5450
5863
|
function register8(program) {
|
|
5451
5864
|
return registerCommand(program, {
|
|
@@ -5457,12 +5870,12 @@ function register8(program) {
|
|
|
5457
5870
|
|
|
5458
5871
|
// src/index.ts
|
|
5459
5872
|
var __filename3 = fileURLToPath2(import.meta.url);
|
|
5460
|
-
var __dirname3 =
|
|
5873
|
+
var __dirname3 = dirname3(__filename3);
|
|
5461
5874
|
logger.configure({ level: config.debug ? "debug" : "info" });
|
|
5462
5875
|
logger.debug(`brainbox starting (debug=${config.debug}, root=${config.brainboxRoot})`);
|
|
5463
5876
|
function getVersion() {
|
|
5464
5877
|
try {
|
|
5465
|
-
const pkgPath =
|
|
5878
|
+
const pkgPath = join6(__dirname3, "..", "package.json");
|
|
5466
5879
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
5467
5880
|
return pkg.version ?? "0.0.0";
|
|
5468
5881
|
} catch {
|