@p-sw/brainbox 0.1.2-alpha.12 → 0.1.2-alpha.2
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 +432 -962
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,17 +4,12 @@
|
|
|
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 dirname4, join as join4 } from "path";
|
|
8
8
|
|
|
9
9
|
// src/utils/logger.ts
|
|
10
10
|
import chalk from "chalk";
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
mkdirSync,
|
|
14
|
-
createWriteStream,
|
|
15
|
-
writeFileSync
|
|
16
|
-
} from "fs";
|
|
17
|
-
import { join } from "path";
|
|
11
|
+
import { existsSync, mkdirSync, createWriteStream } from "fs";
|
|
12
|
+
import { dirname } from "path";
|
|
18
13
|
var LEVELS = {
|
|
19
14
|
debug: { rank: 0, color: chalk.gray, stderr: false },
|
|
20
15
|
info: { rank: 1, color: chalk.blue, stderr: false },
|
|
@@ -31,98 +26,48 @@ var ICONS = {
|
|
|
31
26
|
error: "✖",
|
|
32
27
|
fatal: "▲"
|
|
33
28
|
};
|
|
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
|
-
}
|
|
103
29
|
|
|
104
30
|
class Logger {
|
|
31
|
+
level;
|
|
32
|
+
timestamps;
|
|
33
|
+
colors;
|
|
105
34
|
tag;
|
|
35
|
+
file;
|
|
36
|
+
json;
|
|
37
|
+
silent;
|
|
38
|
+
fileStream;
|
|
106
39
|
constructor(options = {}) {
|
|
40
|
+
this.level = options.level ?? "info";
|
|
41
|
+
this.timestamps = options.timestamps ?? true;
|
|
42
|
+
this.colors = options.colors ?? chalk.level > 0;
|
|
107
43
|
this.tag = options.tag;
|
|
108
|
-
|
|
44
|
+
this.file = options.file;
|
|
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
|
+
}
|
|
109
53
|
}
|
|
110
54
|
shouldLog(level) {
|
|
111
|
-
return LEVELS[level].rank >= LEVELS[
|
|
55
|
+
return LEVELS[level].rank >= LEVELS[this.level].rank;
|
|
112
56
|
}
|
|
113
57
|
formatTimestamp() {
|
|
114
58
|
const now = new Date;
|
|
115
|
-
|
|
59
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
60
|
+
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
|
|
116
61
|
}
|
|
117
62
|
format(level, message) {
|
|
118
|
-
const ts =
|
|
63
|
+
const ts = this.timestamps ? `[${this.formatTimestamp()}]` : "";
|
|
119
64
|
const tag = this.tag ? `[${this.tag}]` : "";
|
|
120
65
|
const icon = ICONS[level];
|
|
121
66
|
const levelStr = level.toUpperCase();
|
|
122
67
|
const consoleParts = [
|
|
123
68
|
ts,
|
|
124
69
|
tag,
|
|
125
|
-
|
|
70
|
+
this.colors ? LEVELS[level].color(icon) : icon,
|
|
126
71
|
message
|
|
127
72
|
].filter(Boolean);
|
|
128
73
|
const consoleLine = consoleParts.join(" ");
|
|
@@ -145,13 +90,13 @@ class Logger {
|
|
|
145
90
|
return;
|
|
146
91
|
const { console: consoleLine, file: fileLine } = this.format(level, message);
|
|
147
92
|
const jsonLine = this.formatJson(level, message);
|
|
148
|
-
if (!
|
|
93
|
+
if (!this.silent) {
|
|
149
94
|
const out = LEVELS[level].stderr ? process.stderr : process.stdout;
|
|
150
95
|
out.write(consoleLine + `
|
|
151
96
|
`);
|
|
152
97
|
}
|
|
153
|
-
if (
|
|
154
|
-
|
|
98
|
+
if (this.fileStream) {
|
|
99
|
+
this.fileStream.write(this.json ? jsonLine : fileLine);
|
|
155
100
|
}
|
|
156
101
|
}
|
|
157
102
|
debug(message) {
|
|
@@ -174,56 +119,57 @@ class Logger {
|
|
|
174
119
|
}
|
|
175
120
|
child(tag) {
|
|
176
121
|
const combined = this.tag ? `${this.tag}:${tag}` : tag;
|
|
177
|
-
return new Logger({
|
|
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
|
+
});
|
|
178
131
|
}
|
|
179
132
|
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;
|
|
180
139
|
if (options.tag !== undefined)
|
|
181
140
|
this.tag = options.tag;
|
|
182
|
-
|
|
141
|
+
if (options.silent !== undefined)
|
|
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
|
+
}
|
|
183
157
|
}
|
|
184
158
|
close() {
|
|
185
|
-
|
|
186
|
-
llmLogDir = undefined;
|
|
159
|
+
this.fileStream?.end();
|
|
187
160
|
}
|
|
188
161
|
}
|
|
189
162
|
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
|
-
}
|
|
217
163
|
|
|
218
164
|
// src/config/loader.ts
|
|
219
|
-
import { dirname
|
|
165
|
+
import { dirname as dirname2, join, resolve } from "path";
|
|
220
166
|
import { z, ZodError } from "zod";
|
|
221
167
|
import { homedir } from "os";
|
|
222
168
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
223
|
-
import { mkdirSync as mkdirSync2, readFileSync, writeFileSync
|
|
224
|
-
var brainboxRoot = process.env["BRAINBOX_ROOT_PATH"] ? resolve(process.cwd(), process.env["BRAINBOX_ROOT_PATH"]) :
|
|
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"]) : join(homedir(), ".brainbox");
|
|
225
171
|
function configFile(file, options) {
|
|
226
|
-
const path = () =>
|
|
172
|
+
const path = () => join(brainboxRoot, file);
|
|
227
173
|
const read = () => {
|
|
228
174
|
const p = path();
|
|
229
175
|
let raw;
|
|
@@ -235,8 +181,8 @@ function configFile(file, options) {
|
|
|
235
181
|
const defaults = defaultsFromSchema(options.schema);
|
|
236
182
|
const templateStr = (options.header ? options.header + `
|
|
237
183
|
` : "") + (defaults === undefined ? "" : stringifyYaml(defaults));
|
|
238
|
-
mkdirSync2(
|
|
239
|
-
|
|
184
|
+
mkdirSync2(dirname2(p), { recursive: true });
|
|
185
|
+
writeFileSync(p, templateStr);
|
|
240
186
|
raw = defaults ?? {};
|
|
241
187
|
}
|
|
242
188
|
try {
|
|
@@ -253,8 +199,8 @@ function configFile(file, options) {
|
|
|
253
199
|
};
|
|
254
200
|
const write = (value) => {
|
|
255
201
|
const p = path();
|
|
256
|
-
mkdirSync2(
|
|
257
|
-
|
|
202
|
+
mkdirSync2(dirname2(p), { recursive: true });
|
|
203
|
+
writeFileSync(p, stringifyYaml(value));
|
|
258
204
|
};
|
|
259
205
|
const update = (fn) => {
|
|
260
206
|
const next = fn(read());
|
|
@@ -349,7 +295,7 @@ function removeProviderAuth(provider) {
|
|
|
349
295
|
// src/config/index.ts
|
|
350
296
|
var config = {
|
|
351
297
|
get debug() {
|
|
352
|
-
return
|
|
298
|
+
return readRootFile().debug;
|
|
353
299
|
},
|
|
354
300
|
brainboxRoot,
|
|
355
301
|
get supermemoryApiKey() {
|
|
@@ -375,7 +321,7 @@ function registerCommand(program, config2) {
|
|
|
375
321
|
|
|
376
322
|
// src/brain/manager.ts
|
|
377
323
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
378
|
-
import { join as
|
|
324
|
+
import { join as join2 } from "path";
|
|
379
325
|
var log = logger.child("brain-manager");
|
|
380
326
|
|
|
381
327
|
class BrainDBManager {
|
|
@@ -384,7 +330,7 @@ class BrainDBManager {
|
|
|
384
330
|
this.root = root;
|
|
385
331
|
}
|
|
386
332
|
dbFile() {
|
|
387
|
-
return
|
|
333
|
+
return join2(this.root, "brains.json");
|
|
388
334
|
}
|
|
389
335
|
async readDB() {
|
|
390
336
|
try {
|
|
@@ -484,106 +430,6 @@ function defaultReasoningEffort(effort, model, identityModel) {
|
|
|
484
430
|
return effort;
|
|
485
431
|
return model === identityModel ? "medium" : "none";
|
|
486
432
|
}
|
|
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
|
-
const candidates = [cleaned];
|
|
493
|
-
const fence = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
494
|
-
if (fence?.[1])
|
|
495
|
-
candidates.push(fence[1].trim());
|
|
496
|
-
const extracted = extractJsonSlice(cleaned);
|
|
497
|
-
if (extracted)
|
|
498
|
-
candidates.push(extracted);
|
|
499
|
-
let lastErr;
|
|
500
|
-
for (const candidate of candidates) {
|
|
501
|
-
try {
|
|
502
|
-
return decodeJsonValue(candidate);
|
|
503
|
-
} catch (err) {
|
|
504
|
-
lastErr = err;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
throw lastErr instanceof Error ? lastErr : new Error("Failed to parse model JSON");
|
|
508
|
-
}
|
|
509
|
-
function decodeJsonValue(text) {
|
|
510
|
-
let value = JSON.parse(text);
|
|
511
|
-
for (let i = 0;i < 2 && typeof value === "string"; i++) {
|
|
512
|
-
const inner = value.trim();
|
|
513
|
-
if (!(inner.startsWith("{") && inner.endsWith("}") || inner.startsWith("[") && inner.endsWith("]") || inner.startsWith('"') && inner.endsWith('"'))) {
|
|
514
|
-
break;
|
|
515
|
-
}
|
|
516
|
-
value = JSON.parse(inner);
|
|
517
|
-
}
|
|
518
|
-
return value;
|
|
519
|
-
}
|
|
520
|
-
function extractJsonSlice(text) {
|
|
521
|
-
const start = text.search(/[\{\[]/);
|
|
522
|
-
if (start < 0)
|
|
523
|
-
return;
|
|
524
|
-
const open = text[start];
|
|
525
|
-
const close = open === "{" ? "}" : "]";
|
|
526
|
-
let depth = 0;
|
|
527
|
-
let inString = false;
|
|
528
|
-
let escape = false;
|
|
529
|
-
for (let i = start;i < text.length; i++) {
|
|
530
|
-
const ch = text[i];
|
|
531
|
-
if (inString) {
|
|
532
|
-
if (escape) {
|
|
533
|
-
escape = false;
|
|
534
|
-
} else if (ch === "\\") {
|
|
535
|
-
escape = true;
|
|
536
|
-
} else if (ch === '"') {
|
|
537
|
-
inString = false;
|
|
538
|
-
}
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
if (ch === '"') {
|
|
542
|
-
inString = true;
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
if (ch === open)
|
|
546
|
-
depth++;
|
|
547
|
-
else if (ch === close) {
|
|
548
|
-
depth--;
|
|
549
|
-
if (depth === 0)
|
|
550
|
-
return text.slice(start, i + 1);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
function schemaToolName(name) {
|
|
556
|
-
const cleaned = name.replace(/[^a-zA-Z0-9_]/g, "_").replace(/^(\d)/, "_$1");
|
|
557
|
-
return cleaned.length > 0 ? cleaned : "submit_result";
|
|
558
|
-
}
|
|
559
|
-
function buildStructuredJsonRequest(options) {
|
|
560
|
-
const toolName = schemaToolName(options.jsonSchemaName);
|
|
561
|
-
return {
|
|
562
|
-
toolName,
|
|
563
|
-
tool: {
|
|
564
|
-
name: toolName,
|
|
565
|
-
description: `Submit the structured ${options.jsonSchemaName} result.`,
|
|
566
|
-
parameters: options.jsonSchema ?? {
|
|
567
|
-
type: "object",
|
|
568
|
-
additionalProperties: true
|
|
569
|
-
}
|
|
570
|
-
},
|
|
571
|
-
instruction: `${options.instruction}
|
|
572
|
-
|
|
573
|
-
You MUST call the \`${toolName}\` tool exactly once with the complete answer.
|
|
574
|
-
Do not write the JSON as plain text or inside a markdown code fence.`
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
function parseStructuredJsonResult(choice, toolName) {
|
|
578
|
-
const call = choice.message.toolCalls?.find((c) => c.function.name === toolName) ?? choice.message.toolCalls?.[0];
|
|
579
|
-
if (call?.function.arguments) {
|
|
580
|
-
return parseModelJson(call.function.arguments);
|
|
581
|
-
}
|
|
582
|
-
if (choice.message.content) {
|
|
583
|
-
return parseModelJson(choice.message.content);
|
|
584
|
-
}
|
|
585
|
-
throw new Error("Empty response from model");
|
|
586
|
-
}
|
|
587
433
|
function readAuthString(auth, key, envName) {
|
|
588
434
|
const fromAuth = typeof auth?.[key] === "string" ? auth[key] : undefined;
|
|
589
435
|
if (fromAuth)
|
|
@@ -663,19 +509,6 @@ class LLMExecutor {
|
|
|
663
509
|
};
|
|
664
510
|
}
|
|
665
511
|
}
|
|
666
|
-
function resolveLlmCaller(options, fallback = "llm") {
|
|
667
|
-
return options.caller ?? options.jsonSchemaName ?? fallback;
|
|
668
|
-
}
|
|
669
|
-
function logLlmWire(caller, request, response) {
|
|
670
|
-
if (!isLlmLogEnabled())
|
|
671
|
-
return;
|
|
672
|
-
writeLlmExchange(caller, `======== REQUEST ========
|
|
673
|
-
${request}
|
|
674
|
-
|
|
675
|
-
======== RESPONSE ========
|
|
676
|
-
${response}
|
|
677
|
-
`);
|
|
678
|
-
}
|
|
679
512
|
function listProviderNames() {
|
|
680
513
|
return LLMExecutor.listProviderNames();
|
|
681
514
|
}
|
|
@@ -713,7 +546,7 @@ function fromOrChoice(choice) {
|
|
|
713
546
|
}));
|
|
714
547
|
return {
|
|
715
548
|
message: {
|
|
716
|
-
content: typeof msg.content === "string" ?
|
|
549
|
+
content: typeof msg.content === "string" ? msg.content : undefined,
|
|
717
550
|
toolCalls
|
|
718
551
|
}
|
|
719
552
|
};
|
|
@@ -744,54 +577,53 @@ class OpenRouterExecutor extends LLMExecutor {
|
|
|
744
577
|
async call(model, options) {
|
|
745
578
|
const jsonMode = "jsonSchemaName" in options;
|
|
746
579
|
log3.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
const
|
|
769
|
-
const content = typeof raw === "string" ? stripThinkTags(raw) : raw;
|
|
580
|
+
const result = await this.client.chat.send({
|
|
581
|
+
chatRequest: {
|
|
582
|
+
model,
|
|
583
|
+
messages: [
|
|
584
|
+
{ role: "system", content: options.instruction },
|
|
585
|
+
{ role: "user", content: options.message }
|
|
586
|
+
],
|
|
587
|
+
reasoning: {
|
|
588
|
+
effort: options.reasoningEffort ?? (model === this.models.identity ? REASONING_EFFORT_MAP.medium : REASONING_EFFORT_MAP.none)
|
|
589
|
+
},
|
|
590
|
+
responseFormat: jsonMode ? {
|
|
591
|
+
type: "json_schema",
|
|
592
|
+
jsonSchema: {
|
|
593
|
+
name: options.jsonSchemaName,
|
|
594
|
+
schema: options.jsonSchema,
|
|
595
|
+
strict: true
|
|
596
|
+
}
|
|
597
|
+
} : { type: "text" },
|
|
598
|
+
stream: false
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
const content = result.choices[0]?.message?.content;
|
|
770
602
|
if (!content) {
|
|
771
603
|
log3.debug(`call: empty content in choice 0`);
|
|
772
604
|
throw new Error("Empty response from model");
|
|
773
605
|
}
|
|
774
606
|
log3.debug(`call: response ${content.length} chars`);
|
|
775
|
-
return jsonMode ?
|
|
607
|
+
return jsonMode ? JSON.parse(content) : content;
|
|
776
608
|
}
|
|
777
609
|
async chatWithTools(model, options) {
|
|
778
610
|
log3.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
611
|
+
const result = await this.client.chat.send({
|
|
612
|
+
chatRequest: {
|
|
613
|
+
model,
|
|
614
|
+
messages: [
|
|
615
|
+
{ role: "system", content: options.instruction },
|
|
616
|
+
...options.messages.map(toOrMessage)
|
|
617
|
+
],
|
|
618
|
+
reasoning: {
|
|
619
|
+
effort: options.reasoningEffort ?? (model === this.models.identity ? REASONING_EFFORT_MAP.medium : REASONING_EFFORT_MAP.none)
|
|
620
|
+
},
|
|
621
|
+
responseFormat: { type: "text" },
|
|
622
|
+
tools: options.tools.map(toOrTool),
|
|
623
|
+
parallelToolCalls: options.parallelToolCalls ?? false,
|
|
624
|
+
stream: false
|
|
625
|
+
}
|
|
626
|
+
});
|
|
795
627
|
const choice = result.choices[0];
|
|
796
628
|
if (!choice) {
|
|
797
629
|
log3.debug(`chatWithTools: no choice in response`);
|
|
@@ -831,7 +663,7 @@ function fromChoice(choice) {
|
|
|
831
663
|
}));
|
|
832
664
|
return {
|
|
833
665
|
message: {
|
|
834
|
-
content: typeof msg.content === "string" ?
|
|
666
|
+
content: typeof msg.content === "string" ? msg.content : undefined,
|
|
835
667
|
toolCalls
|
|
836
668
|
}
|
|
837
669
|
};
|
|
@@ -866,7 +698,6 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
866
698
|
chatPath;
|
|
867
699
|
noBearerPrefix;
|
|
868
700
|
reasoningEffortInQuery;
|
|
869
|
-
supportsResponseFormat;
|
|
870
701
|
constructor(opts) {
|
|
871
702
|
super();
|
|
872
703
|
this.providerName = opts.providerName;
|
|
@@ -876,7 +707,6 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
876
707
|
this.chatPath = opts.chatPath ?? "/chat/completions";
|
|
877
708
|
this.noBearerPrefix = opts.noBearerPrefix ?? false;
|
|
878
709
|
this.reasoningEffortInQuery = opts.reasoningEffortInQuery ?? false;
|
|
879
|
-
this.supportsResponseFormat = opts.supportsResponseFormat ?? true;
|
|
880
710
|
this.models = {
|
|
881
711
|
conversation: opts.conversationModel,
|
|
882
712
|
identity: opts.identityModel
|
|
@@ -907,7 +737,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
907
737
|
}
|
|
908
738
|
return url.toString();
|
|
909
739
|
}
|
|
910
|
-
async sendRequest(body, reasoningEffort
|
|
740
|
+
async sendRequest(body, reasoningEffort) {
|
|
911
741
|
const modelName = body["model"];
|
|
912
742
|
const modelStr = typeof modelName === "string" ? modelName : "";
|
|
913
743
|
const url = this.buildRequestUrl(modelStr, reasoningEffort);
|
|
@@ -917,40 +747,24 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
917
747
|
...authHeader,
|
|
918
748
|
...this.defaultHeaders
|
|
919
749
|
};
|
|
920
|
-
const requestRaw = JSON.stringify(body);
|
|
921
750
|
const res = await fetch(url, {
|
|
922
751
|
method: "POST",
|
|
923
752
|
headers,
|
|
924
|
-
body:
|
|
753
|
+
body: JSON.stringify(body)
|
|
925
754
|
});
|
|
926
|
-
const responseRaw = await res.text().catch(() => "");
|
|
927
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
928
755
|
if (!res.ok) {
|
|
929
|
-
|
|
756
|
+
const text = await res.text().catch(() => "");
|
|
757
|
+
log4.error(`${this.providerName}: HTTP ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
930
758
|
throw new Error(`${this.providerName} request failed: ${res.status} ${res.statusText}`);
|
|
931
759
|
}
|
|
932
|
-
|
|
933
|
-
try {
|
|
934
|
-
data = JSON.parse(responseRaw);
|
|
935
|
-
} catch {
|
|
936
|
-
throw new Error(`${this.providerName}: invalid JSON response`);
|
|
937
|
-
}
|
|
760
|
+
const data = await res.json();
|
|
938
761
|
if (data.error) {
|
|
939
762
|
log4.error(`${this.providerName}: API error ${data.error.type ?? ""} ${data.error.message ?? ""}`);
|
|
940
763
|
throw new Error(`${this.providerName} API error: ${data.error.message ?? "unknown"}`);
|
|
941
764
|
}
|
|
942
|
-
const baseCode = data.base_resp?.status_code;
|
|
943
|
-
if (typeof baseCode === "number" && baseCode !== 0) {
|
|
944
|
-
const msg = data.base_resp?.status_msg?.trim() || `status_code ${baseCode}`;
|
|
945
|
-
log4.error(`${this.providerName}: base_resp ${baseCode} ${msg}`);
|
|
946
|
-
throw new Error(`${this.providerName} API error: ${msg}`);
|
|
947
|
-
}
|
|
948
765
|
return data;
|
|
949
766
|
}
|
|
950
767
|
async call(model, options) {
|
|
951
|
-
if ("jsonSchemaName" in options && !this.supportsResponseFormat) {
|
|
952
|
-
return this.callJsonViaTool(model, options);
|
|
953
|
-
}
|
|
954
768
|
const jsonMode = "jsonSchemaName" in options;
|
|
955
769
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
956
770
|
log4.debug(`call: provider=${this.providerName} model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
@@ -963,31 +777,14 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
963
777
|
responseFormat: jsonMode ? buildResponseFormat(options.jsonSchemaName, options.jsonSchema) : undefined,
|
|
964
778
|
reasoningEffort: reasoning
|
|
965
779
|
});
|
|
966
|
-
const data = await this.sendRequest(body, options.reasoningEffort
|
|
967
|
-
const
|
|
968
|
-
const raw = choice?.message?.content;
|
|
969
|
-
const content = typeof raw === "string" ? stripThinkTags(raw) : raw;
|
|
780
|
+
const data = await this.sendRequest(body, options.reasoningEffort);
|
|
781
|
+
const content = data.choices?.[0]?.message?.content;
|
|
970
782
|
if (!content) {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
log4.debug(`call: empty content in choice 0 finish_reason=${finish} reasoning_len=${reasoningLen} rawType=${raw === null ? "null" : typeof raw}`);
|
|
974
|
-
throw new Error(reasoningLen > 0 ? `Empty response from model (finish_reason=${finish}; reasoning present but no content)` : "Empty response from model");
|
|
783
|
+
log4.debug(`call: empty content in choice 0`);
|
|
784
|
+
throw new Error("Empty response from model");
|
|
975
785
|
}
|
|
976
786
|
log4.debug(`call: response ${content.length} chars`);
|
|
977
|
-
return jsonMode ?
|
|
978
|
-
}
|
|
979
|
-
async callJsonViaTool(model, options) {
|
|
980
|
-
const { toolName, tool, instruction } = buildStructuredJsonRequest(options);
|
|
981
|
-
log4.debug(`callJsonViaTool: provider=${this.providerName} model=${model} tool=${toolName}`);
|
|
982
|
-
const choice = await this.chatWithTools(model, {
|
|
983
|
-
caller: options.caller ?? options.jsonSchemaName,
|
|
984
|
-
instruction,
|
|
985
|
-
messages: [{ role: "user", content: options.message }],
|
|
986
|
-
tools: [tool],
|
|
987
|
-
reasoningEffort: "none",
|
|
988
|
-
parallelToolCalls: false
|
|
989
|
-
});
|
|
990
|
-
return parseStructuredJsonResult(choice, toolName);
|
|
787
|
+
return jsonMode ? JSON.parse(content) : content;
|
|
991
788
|
}
|
|
992
789
|
async chatWithTools(model, options) {
|
|
993
790
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
@@ -1002,7 +799,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
1002
799
|
parallelToolCalls: options.parallelToolCalls ?? false,
|
|
1003
800
|
reasoningEffort: reasoning
|
|
1004
801
|
});
|
|
1005
|
-
const data = await this.sendRequest(body, options.reasoningEffort
|
|
802
|
+
const data = await this.sendRequest(body, options.reasoningEffort);
|
|
1006
803
|
const choice = data.choices?.[0];
|
|
1007
804
|
if (!choice) {
|
|
1008
805
|
log4.debug(`chatWithTools: no choice in response`);
|
|
@@ -1301,41 +1098,16 @@ class ZenMuxExecutor extends OpenAICompatibleExecutor {
|
|
|
1301
1098
|
}
|
|
1302
1099
|
}
|
|
1303
1100
|
|
|
1304
|
-
// src/provider/providers/
|
|
1305
|
-
class
|
|
1306
|
-
buildBody(opts) {
|
|
1307
|
-
const jsonMode = opts.responseFormat !== undefined;
|
|
1308
|
-
const body = super.buildBody(opts);
|
|
1309
|
-
delete body["reasoning_effort"];
|
|
1310
|
-
delete body["response_format"];
|
|
1311
|
-
delete body["parallel_tool_calls"];
|
|
1312
|
-
body["reasoning_split"] = true;
|
|
1313
|
-
body["max_completion_tokens"] = 16384;
|
|
1314
|
-
const wantThink = !jsonMode && opts.reasoningEffort !== undefined && opts.reasoningEffort !== "none";
|
|
1315
|
-
body["thinking"] = wantThink ? { type: "adaptive" } : { type: "disabled" };
|
|
1316
|
-
return body;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
function minimaxOpts(providerName, baseURL, opts) {
|
|
1320
|
-
return {
|
|
1321
|
-
providerName,
|
|
1322
|
-
baseURL,
|
|
1323
|
-
apiKey: opts.apiKey,
|
|
1324
|
-
conversationModel: opts.conversationModel,
|
|
1325
|
-
identityModel: opts.identityModel,
|
|
1326
|
-
supportsResponseFormat: false
|
|
1327
|
-
};
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
class MiniMaxExecutor extends MiniMaxBase {
|
|
1331
|
-
constructor(opts) {
|
|
1332
|
-
super(minimaxOpts("minimax", "https://api.minimax.io/v1", opts));
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
class MiniMaxCnExecutor extends MiniMaxBase {
|
|
1101
|
+
// src/provider/providers/MiniMax.ts
|
|
1102
|
+
class MiniMaxExecutor extends OpenAICompatibleExecutor {
|
|
1337
1103
|
constructor(opts) {
|
|
1338
|
-
super(
|
|
1104
|
+
super({
|
|
1105
|
+
providerName: "MiniMax",
|
|
1106
|
+
baseURL: "https://api.MiniMax.chat/v1",
|
|
1107
|
+
apiKey: opts.apiKey,
|
|
1108
|
+
conversationModel: opts.conversationModel,
|
|
1109
|
+
identityModel: opts.identityModel
|
|
1110
|
+
});
|
|
1339
1111
|
}
|
|
1340
1112
|
}
|
|
1341
1113
|
|
|
@@ -1399,8 +1171,7 @@ class LmStudioExecutor extends OpenAICompatibleExecutor {
|
|
|
1399
1171
|
baseURL: "http://127.0.0.1:1234/v1",
|
|
1400
1172
|
apiKey: opts.apiKey || "lm-studio",
|
|
1401
1173
|
conversationModel: opts.conversationModel,
|
|
1402
|
-
identityModel: opts.identityModel
|
|
1403
|
-
supportsResponseFormat: false
|
|
1174
|
+
identityModel: opts.identityModel
|
|
1404
1175
|
});
|
|
1405
1176
|
}
|
|
1406
1177
|
}
|
|
@@ -1413,8 +1184,7 @@ class OllamaExecutor extends OpenAICompatibleExecutor {
|
|
|
1413
1184
|
baseURL: "http://127.0.0.1:11434/v1",
|
|
1414
1185
|
apiKey: opts.apiKey || "ollama",
|
|
1415
1186
|
conversationModel: opts.conversationModel,
|
|
1416
|
-
identityModel: opts.identityModel
|
|
1417
|
-
supportsResponseFormat: false
|
|
1187
|
+
identityModel: opts.identityModel
|
|
1418
1188
|
});
|
|
1419
1189
|
}
|
|
1420
1190
|
}
|
|
@@ -1440,8 +1210,7 @@ class LlamaCppExecutor extends OpenAICompatibleExecutor {
|
|
|
1440
1210
|
baseURL: "http://127.0.0.1:8080/v1",
|
|
1441
1211
|
apiKey: opts.apiKey || "llama.cpp",
|
|
1442
1212
|
conversationModel: opts.conversationModel,
|
|
1443
|
-
identityModel: opts.identityModel
|
|
1444
|
-
supportsResponseFormat: false
|
|
1213
|
+
identityModel: opts.identityModel
|
|
1445
1214
|
});
|
|
1446
1215
|
}
|
|
1447
1216
|
}
|
|
@@ -1491,17 +1260,7 @@ class CloudflareGatewayExecutor extends OpenAICompatibleExecutor {
|
|
|
1491
1260
|
// src/provider/providers/cloudflare_workers.ts
|
|
1492
1261
|
function toCloudflareMessage(m) {
|
|
1493
1262
|
if (m.role === "assistant") {
|
|
1494
|
-
return {
|
|
1495
|
-
role: "assistant",
|
|
1496
|
-
content: m.content ?? "",
|
|
1497
|
-
tool_calls: m.toolCalls?.map((c) => {
|
|
1498
|
-
let args = c.function.arguments;
|
|
1499
|
-
try {
|
|
1500
|
-
args = JSON.parse(c.function.arguments);
|
|
1501
|
-
} catch {}
|
|
1502
|
-
return { id: c.id, name: c.function.name, arguments: args };
|
|
1503
|
-
})
|
|
1504
|
-
};
|
|
1263
|
+
return { role: "assistant", content: m.content ?? "" };
|
|
1505
1264
|
}
|
|
1506
1265
|
if (m.role === "tool") {
|
|
1507
1266
|
return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
|
|
@@ -1524,23 +1283,21 @@ class CloudflareWorkersExecutor extends LLMExecutor {
|
|
|
1524
1283
|
const accountId = readAuthString(opts.auth, "accountId", "CLOUDFLARE_ACCOUNT_ID");
|
|
1525
1284
|
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";
|
|
1526
1285
|
}
|
|
1527
|
-
async run(model, body
|
|
1286
|
+
async run(model, body) {
|
|
1528
1287
|
const url = `${this.baseURL}/${encodeURIComponent(model)}`;
|
|
1529
|
-
const requestRaw = JSON.stringify(body);
|
|
1530
1288
|
const res = await fetch(url, {
|
|
1531
1289
|
method: "POST",
|
|
1532
1290
|
headers: {
|
|
1533
1291
|
Authorization: `Bearer ${this.apiKey}`,
|
|
1534
1292
|
"Content-Type": "application/json"
|
|
1535
1293
|
},
|
|
1536
|
-
body:
|
|
1294
|
+
body: JSON.stringify(body)
|
|
1537
1295
|
});
|
|
1538
|
-
const responseRaw = await res.text().catch(() => "");
|
|
1539
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
1540
1296
|
if (!res.ok) {
|
|
1541
|
-
|
|
1297
|
+
const text = await res.text().catch(() => "");
|
|
1298
|
+
throw new Error(`cloudflare-workers request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
1542
1299
|
}
|
|
1543
|
-
return
|
|
1300
|
+
return await res.json();
|
|
1544
1301
|
}
|
|
1545
1302
|
async call(model, options) {
|
|
1546
1303
|
const jsonMode = "jsonSchemaName" in options;
|
|
@@ -1561,15 +1318,15 @@ class CloudflareWorkersExecutor extends LLMExecutor {
|
|
|
1561
1318
|
if (reasoning !== "none") {
|
|
1562
1319
|
body["reasoning_effort"] = reasoning;
|
|
1563
1320
|
}
|
|
1564
|
-
const data = await this.run(model, body
|
|
1321
|
+
const data = await this.run(model, body);
|
|
1565
1322
|
if (data.errors && data.errors.length > 0) {
|
|
1566
1323
|
throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
|
|
1567
1324
|
}
|
|
1568
|
-
const content =
|
|
1325
|
+
const content = data.result?.response ?? "";
|
|
1569
1326
|
if (!content) {
|
|
1570
1327
|
throw new Error("Empty response from model");
|
|
1571
1328
|
}
|
|
1572
|
-
return jsonMode ?
|
|
1329
|
+
return jsonMode ? JSON.parse(content) : content;
|
|
1573
1330
|
}
|
|
1574
1331
|
async chatWithTools(model, options) {
|
|
1575
1332
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
@@ -1590,11 +1347,11 @@ class CloudflareWorkersExecutor extends LLMExecutor {
|
|
|
1590
1347
|
if (reasoning !== "none") {
|
|
1591
1348
|
body["reasoning_effort"] = reasoning;
|
|
1592
1349
|
}
|
|
1593
|
-
const data = await this.run(model, body
|
|
1350
|
+
const data = await this.run(model, body);
|
|
1594
1351
|
if (data.errors && data.errors.length > 0) {
|
|
1595
1352
|
throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
|
|
1596
1353
|
}
|
|
1597
|
-
const content =
|
|
1354
|
+
const content = data.result?.response ?? "";
|
|
1598
1355
|
const toolCalls = data.result?.tool_calls?.map((c, idx) => ({
|
|
1599
1356
|
id: `call_${idx}`,
|
|
1600
1357
|
function: {
|
|
@@ -1632,8 +1389,7 @@ class AzureOpenAIExecutor extends OpenAICompatibleExecutor {
|
|
|
1632
1389
|
super({
|
|
1633
1390
|
providerName: "azure-openai",
|
|
1634
1391
|
baseURL: `https://${resource || "__resource__"}.openai.azure.com/openai/deployments`,
|
|
1635
|
-
apiKey:
|
|
1636
|
-
defaultHeaders: { "api-key": opts.apiKey },
|
|
1392
|
+
apiKey: opts.apiKey,
|
|
1637
1393
|
conversationModel: opts.conversationModel,
|
|
1638
1394
|
identityModel: opts.identityModel
|
|
1639
1395
|
});
|
|
@@ -1653,8 +1409,7 @@ class AzureCognitiveExecutor extends OpenAICompatibleExecutor {
|
|
|
1653
1409
|
super({
|
|
1654
1410
|
providerName: "azure-cognitive",
|
|
1655
1411
|
baseURL: `https://${resource || "__resource__"}.cognitiveservices.azure.com/openai/deployments`,
|
|
1656
|
-
apiKey:
|
|
1657
|
-
defaultHeaders: { "api-key": opts.apiKey },
|
|
1412
|
+
apiKey: opts.apiKey,
|
|
1658
1413
|
conversationModel: opts.conversationModel,
|
|
1659
1414
|
identityModel: opts.identityModel
|
|
1660
1415
|
});
|
|
@@ -1680,17 +1435,17 @@ var AnthropicAuthSchema = z4.object({
|
|
|
1680
1435
|
apiVersion: z4.string().optional()
|
|
1681
1436
|
}).loose();
|
|
1682
1437
|
function toAnthropicMessages(messages) {
|
|
1683
|
-
let
|
|
1684
|
-
const
|
|
1438
|
+
let system;
|
|
1439
|
+
const msgs = [];
|
|
1685
1440
|
for (const m of messages) {
|
|
1686
1441
|
if (m.role === "system") {
|
|
1687
|
-
|
|
1442
|
+
system = (system ? system + `
|
|
1688
1443
|
|
|
1689
1444
|
` : "") + m.content;
|
|
1690
1445
|
continue;
|
|
1691
1446
|
}
|
|
1692
1447
|
if (m.role === "user") {
|
|
1693
|
-
|
|
1448
|
+
msgs.push({ role: "user", content: m.content });
|
|
1694
1449
|
continue;
|
|
1695
1450
|
}
|
|
1696
1451
|
if (m.role === "assistant") {
|
|
@@ -1713,11 +1468,11 @@ function toAnthropicMessages(messages) {
|
|
|
1713
1468
|
});
|
|
1714
1469
|
}
|
|
1715
1470
|
}
|
|
1716
|
-
|
|
1471
|
+
msgs.push({ role: "assistant", content: blocks });
|
|
1717
1472
|
continue;
|
|
1718
1473
|
}
|
|
1719
1474
|
if (m.role === "tool") {
|
|
1720
|
-
|
|
1475
|
+
msgs.push({
|
|
1721
1476
|
role: "user",
|
|
1722
1477
|
content: [
|
|
1723
1478
|
{
|
|
@@ -1729,7 +1484,7 @@ function toAnthropicMessages(messages) {
|
|
|
1729
1484
|
});
|
|
1730
1485
|
}
|
|
1731
1486
|
}
|
|
1732
|
-
return { system
|
|
1487
|
+
return { system, msgs };
|
|
1733
1488
|
}
|
|
1734
1489
|
function toAnthropicTool(t) {
|
|
1735
1490
|
return {
|
|
@@ -1757,8 +1512,7 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1757
1512
|
this.baseURL = (extra.baseURL ?? readAuthString(opts.auth, "baseURL", "ANTHROPIC_BASE_URL") ?? "https://api.anthropic.com").replace(/\/v1\/?$/, "");
|
|
1758
1513
|
this.apiVersion = extra.apiVersion ?? "2023-06-01";
|
|
1759
1514
|
}
|
|
1760
|
-
async send(body
|
|
1761
|
-
const requestRaw = JSON.stringify(body);
|
|
1515
|
+
async send(body) {
|
|
1762
1516
|
const res = await fetch(`${this.baseURL}/v1/messages`, {
|
|
1763
1517
|
method: "POST",
|
|
1764
1518
|
headers: {
|
|
@@ -1766,93 +1520,65 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1766
1520
|
"anthropic-version": this.apiVersion,
|
|
1767
1521
|
"Content-Type": "application/json"
|
|
1768
1522
|
},
|
|
1769
|
-
body:
|
|
1523
|
+
body: JSON.stringify(body)
|
|
1770
1524
|
});
|
|
1771
|
-
const responseRaw = await res.text().catch(() => "");
|
|
1772
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
1773
1525
|
if (!res.ok) {
|
|
1774
|
-
|
|
1526
|
+
const text = await res.text().catch(() => "");
|
|
1527
|
+
throw new Error(`anthropic request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
1775
1528
|
}
|
|
1776
|
-
return
|
|
1529
|
+
return await res.json();
|
|
1777
1530
|
}
|
|
1778
1531
|
async call(model, options) {
|
|
1779
|
-
|
|
1780
|
-
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
1781
|
-
instruction: options.instruction,
|
|
1782
|
-
jsonSchemaName: options.jsonSchemaName,
|
|
1783
|
-
jsonSchema: options.jsonSchema
|
|
1784
|
-
});
|
|
1785
|
-
const choice = await this.chatWithTools(model, {
|
|
1786
|
-
caller: options.caller ?? options.jsonSchemaName,
|
|
1787
|
-
instruction,
|
|
1788
|
-
messages: [{ role: "user", content: options.message }],
|
|
1789
|
-
tools: [tool],
|
|
1790
|
-
reasoningEffort: "none",
|
|
1791
|
-
toolChoice: { type: "tool", name: toolName }
|
|
1792
|
-
});
|
|
1793
|
-
return parseStructuredJsonResult(choice, toolName);
|
|
1794
|
-
}
|
|
1532
|
+
const jsonMode = "jsonSchemaName" in options;
|
|
1795
1533
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
1796
1534
|
const log5 = logger.child("llm:anthropic");
|
|
1797
|
-
log5.debug(`call: model=${model} jsonSchema
|
|
1798
|
-
const outputCap = 4096;
|
|
1535
|
+
log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
1799
1536
|
const body = {
|
|
1800
1537
|
model,
|
|
1801
|
-
max_tokens:
|
|
1538
|
+
max_tokens: 4096,
|
|
1802
1539
|
system: options.instruction,
|
|
1803
1540
|
messages: [{ role: "user", content: options.message }]
|
|
1804
1541
|
};
|
|
1805
1542
|
if (reasoning !== "none") {
|
|
1806
|
-
const budget = REASONING_BUDGET[reasoning];
|
|
1807
|
-
body["max_tokens"] = budget + outputCap;
|
|
1808
1543
|
body["thinking"] = {
|
|
1809
1544
|
type: "enabled",
|
|
1810
|
-
budget_tokens:
|
|
1545
|
+
budget_tokens: REASONING_BUDGET[reasoning]
|
|
1811
1546
|
};
|
|
1812
1547
|
}
|
|
1813
|
-
const data = await this.send(body
|
|
1548
|
+
const data = await this.send(body);
|
|
1814
1549
|
if (data.error) {
|
|
1815
1550
|
throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
|
|
1816
1551
|
}
|
|
1817
|
-
const text =
|
|
1552
|
+
const text = (data.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
1818
1553
|
if (!text) {
|
|
1819
1554
|
throw new Error("Empty response from model");
|
|
1820
1555
|
}
|
|
1821
|
-
return text;
|
|
1556
|
+
return jsonMode ? JSON.parse(text) : text;
|
|
1822
1557
|
}
|
|
1823
1558
|
async chatWithTools(model, options) {
|
|
1824
1559
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
1825
1560
|
const log5 = logger.child("llm:anthropic");
|
|
1826
1561
|
log5.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
|
|
1827
|
-
const { system
|
|
1828
|
-
const outputCap = 4096;
|
|
1562
|
+
const { system, msgs } = toAnthropicMessages(options.messages);
|
|
1829
1563
|
const body = {
|
|
1830
1564
|
model,
|
|
1831
|
-
max_tokens:
|
|
1832
|
-
system:
|
|
1833
|
-
messages:
|
|
1565
|
+
max_tokens: 4096,
|
|
1566
|
+
system: system ?? options.instruction,
|
|
1567
|
+
messages: msgs,
|
|
1834
1568
|
tools: options.tools.map(toAnthropicTool)
|
|
1835
1569
|
};
|
|
1836
|
-
if (options.toolChoice) {
|
|
1837
|
-
body["tool_choice"] = {
|
|
1838
|
-
type: "tool",
|
|
1839
|
-
name: options.toolChoice.name
|
|
1840
|
-
};
|
|
1841
|
-
}
|
|
1842
1570
|
if (reasoning !== "none") {
|
|
1843
|
-
const budget = REASONING_BUDGET[reasoning];
|
|
1844
|
-
body["max_tokens"] = budget + outputCap;
|
|
1845
1571
|
body["thinking"] = {
|
|
1846
1572
|
type: "enabled",
|
|
1847
|
-
budget_tokens:
|
|
1573
|
+
budget_tokens: REASONING_BUDGET[reasoning]
|
|
1848
1574
|
};
|
|
1849
1575
|
}
|
|
1850
|
-
const data = await this.send(body
|
|
1576
|
+
const data = await this.send(body);
|
|
1851
1577
|
if (data.error) {
|
|
1852
1578
|
throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
|
|
1853
1579
|
}
|
|
1854
1580
|
const blocks = data.content ?? [];
|
|
1855
|
-
const text =
|
|
1581
|
+
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
1856
1582
|
const toolCalls = blocks.filter((b) => b.type === "tool_use").map((b) => ({
|
|
1857
1583
|
id: b.id,
|
|
1858
1584
|
function: {
|
|
@@ -1866,11 +1592,11 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1866
1592
|
|
|
1867
1593
|
// src/provider/providers/bedrock.ts
|
|
1868
1594
|
import { createHmac, createHash } from "node:crypto";
|
|
1869
|
-
var
|
|
1595
|
+
var pad2 = (n) => n.toString().padStart(2, "0");
|
|
1870
1596
|
function amzDate(now) {
|
|
1871
1597
|
return {
|
|
1872
|
-
date: `${now.getUTCFullYear()}${
|
|
1873
|
-
datetime: `${now.getUTCFullYear()}${
|
|
1598
|
+
date: `${now.getUTCFullYear()}${pad2(now.getUTCMonth() + 1)}${pad2(now.getUTCDate())}`,
|
|
1599
|
+
datetime: `${now.getUTCFullYear()}${pad2(now.getUTCMonth() + 1)}${pad2(now.getUTCDate())}T` + `${pad2(now.getUTCHours())}${pad2(now.getUTCMinutes())}${pad2(now.getUTCSeconds())}Z`
|
|
1874
1600
|
};
|
|
1875
1601
|
}
|
|
1876
1602
|
function signRequest(opts) {
|
|
@@ -1917,6 +1643,58 @@ function signRequest(opts) {
|
|
|
1917
1643
|
headers["X-Amz-Security-Token"] = opts.sessionToken;
|
|
1918
1644
|
return headers;
|
|
1919
1645
|
}
|
|
1646
|
+
function toAnthropicMessages2(messages) {
|
|
1647
|
+
let system;
|
|
1648
|
+
const msgs = [];
|
|
1649
|
+
for (const m of messages) {
|
|
1650
|
+
if (m.role === "system") {
|
|
1651
|
+
system = (system ? system + `
|
|
1652
|
+
|
|
1653
|
+
` : "") + m.content;
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
if (m.role === "user") {
|
|
1657
|
+
msgs.push({ role: "user", content: [{ type: "text", text: m.content }] });
|
|
1658
|
+
continue;
|
|
1659
|
+
}
|
|
1660
|
+
if (m.role === "assistant") {
|
|
1661
|
+
const blocks = [];
|
|
1662
|
+
if (m.content)
|
|
1663
|
+
blocks.push({ type: "text", text: m.content });
|
|
1664
|
+
if (m.toolCalls) {
|
|
1665
|
+
for (const c of m.toolCalls) {
|
|
1666
|
+
let input = {};
|
|
1667
|
+
try {
|
|
1668
|
+
input = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
1669
|
+
} catch {
|
|
1670
|
+
input = {};
|
|
1671
|
+
}
|
|
1672
|
+
blocks.push({
|
|
1673
|
+
type: "tool_use",
|
|
1674
|
+
id: c.id,
|
|
1675
|
+
name: c.function.name,
|
|
1676
|
+
input
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
msgs.push({ role: "assistant", content: blocks });
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
if (m.role === "tool") {
|
|
1684
|
+
msgs.push({
|
|
1685
|
+
role: "user",
|
|
1686
|
+
content: [
|
|
1687
|
+
{
|
|
1688
|
+
type: "tool_result",
|
|
1689
|
+
tool_use_id: m.toolCallId,
|
|
1690
|
+
content: [{ type: "text", text: m.content }]
|
|
1691
|
+
}
|
|
1692
|
+
]
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
return { system, msgs };
|
|
1697
|
+
}
|
|
1920
1698
|
function toAnthropicTool2(t) {
|
|
1921
1699
|
return {
|
|
1922
1700
|
name: t.name,
|
|
@@ -1926,7 +1704,7 @@ function toAnthropicTool2(t) {
|
|
|
1926
1704
|
}
|
|
1927
1705
|
function extractAnthropicContent(data) {
|
|
1928
1706
|
const content = data["content"] ?? [];
|
|
1929
|
-
const text =
|
|
1707
|
+
const text = content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("");
|
|
1930
1708
|
const toolCalls = content.filter((b) => b["type"] === "tool_use").map((b) => ({
|
|
1931
1709
|
id: b["id"],
|
|
1932
1710
|
function: {
|
|
@@ -1958,7 +1736,7 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1958
1736
|
this.sessionToken = readAuthString(opts.auth, "sessionToken", "AWS_SESSION_TOKEN");
|
|
1959
1737
|
this.baseURL = `https://bedrock-runtime.${this.region}.amazonaws.com`;
|
|
1960
1738
|
}
|
|
1961
|
-
async invoke(model, body
|
|
1739
|
+
async invoke(model, body) {
|
|
1962
1740
|
const bodyStr = JSON.stringify(body);
|
|
1963
1741
|
const path = `/model/${encodeURIComponent(model)}/invoke`;
|
|
1964
1742
|
const headers = signRequest({
|
|
@@ -1977,32 +1755,16 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1977
1755
|
headers,
|
|
1978
1756
|
body: bodyStr
|
|
1979
1757
|
});
|
|
1980
|
-
const responseRaw = await res.text().catch(() => "");
|
|
1981
|
-
logLlmWire(caller, bodyStr, responseRaw);
|
|
1982
1758
|
if (!res.ok) {
|
|
1983
|
-
|
|
1759
|
+
const text = await res.text().catch(() => "");
|
|
1760
|
+
throw new Error(`bedrock request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
1984
1761
|
}
|
|
1985
|
-
return
|
|
1762
|
+
return await res.json();
|
|
1986
1763
|
}
|
|
1987
1764
|
async call(model, options) {
|
|
1765
|
+
const jsonMode = "jsonSchemaName" in options;
|
|
1988
1766
|
const log5 = logger.child("llm:bedrock");
|
|
1989
|
-
|
|
1990
|
-
log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
|
|
1991
|
-
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
1992
|
-
instruction: options.instruction,
|
|
1993
|
-
jsonSchemaName: options.jsonSchemaName,
|
|
1994
|
-
jsonSchema: options.jsonSchema
|
|
1995
|
-
});
|
|
1996
|
-
const choice = await this.chatWithTools(model, {
|
|
1997
|
-
caller: options.caller ?? options.jsonSchemaName,
|
|
1998
|
-
instruction,
|
|
1999
|
-
messages: [{ role: "user", content: options.message }],
|
|
2000
|
-
tools: [tool],
|
|
2001
|
-
toolChoice: { type: "tool", name: toolName }
|
|
2002
|
-
});
|
|
2003
|
-
return parseStructuredJsonResult(choice, toolName);
|
|
2004
|
-
}
|
|
2005
|
-
log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
|
|
1767
|
+
log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
2006
1768
|
if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
|
|
2007
1769
|
throw new Error(`bedrock provider currently only supports Anthropic models (got ${model})`);
|
|
2008
1770
|
}
|
|
@@ -2012,12 +1774,12 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
2012
1774
|
system: options.instruction,
|
|
2013
1775
|
messages: [{ role: "user", content: options.message }]
|
|
2014
1776
|
};
|
|
2015
|
-
const data = await this.invoke(model, body
|
|
1777
|
+
const data = await this.invoke(model, body);
|
|
2016
1778
|
const { text } = extractAnthropicContent(data);
|
|
2017
1779
|
if (!text) {
|
|
2018
1780
|
throw new Error("Empty response from model");
|
|
2019
1781
|
}
|
|
2020
|
-
return text;
|
|
1782
|
+
return jsonMode ? JSON.parse(text) : text;
|
|
2021
1783
|
}
|
|
2022
1784
|
async chatWithTools(model, options) {
|
|
2023
1785
|
const log5 = logger.child("llm:bedrock");
|
|
@@ -2025,6 +1787,7 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
2025
1787
|
if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
|
|
2026
1788
|
throw new Error(`bedrock provider currently only supports Anthropic models (got ${model})`);
|
|
2027
1789
|
}
|
|
1790
|
+
const { system, msgs } = toAnthropicMessages2(options.messages);
|
|
2028
1791
|
const body = {
|
|
2029
1792
|
anthropic_version: "bedrock-2023-05-31",
|
|
2030
1793
|
max_tokens: 4096,
|
|
@@ -2032,13 +1795,7 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
2032
1795
|
messages: msgs,
|
|
2033
1796
|
tools: options.tools.map(toAnthropicTool2)
|
|
2034
1797
|
};
|
|
2035
|
-
|
|
2036
|
-
body.tool_choice = {
|
|
2037
|
-
type: "tool",
|
|
2038
|
-
name: options.toolChoice.name
|
|
2039
|
-
};
|
|
2040
|
-
}
|
|
2041
|
-
const data = await this.invoke(model, body, resolveLlmCaller(options));
|
|
1798
|
+
const data = await this.invoke(model, body);
|
|
2042
1799
|
const { text, toolCalls } = extractAnthropicContent(data);
|
|
2043
1800
|
return { message: { content: text || undefined, toolCalls } };
|
|
2044
1801
|
}
|
|
@@ -2052,7 +1809,6 @@ var VertexAuthSchema = z5.object({
|
|
|
2052
1809
|
}).loose();
|
|
2053
1810
|
function toGeminiContents(messages) {
|
|
2054
1811
|
const out = [];
|
|
2055
|
-
const nameByCallId = new Map;
|
|
2056
1812
|
for (const m of messages) {
|
|
2057
1813
|
if (m.role === "system")
|
|
2058
1814
|
continue;
|
|
@@ -2066,7 +1822,6 @@ function toGeminiContents(messages) {
|
|
|
2066
1822
|
parts.push({ text: m.content });
|
|
2067
1823
|
if (m.toolCalls) {
|
|
2068
1824
|
for (const c of m.toolCalls) {
|
|
2069
|
-
nameByCallId.set(c.id, c.function.name);
|
|
2070
1825
|
let args = {};
|
|
2071
1826
|
try {
|
|
2072
1827
|
args = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
@@ -2088,26 +1843,21 @@ function toGeminiContents(messages) {
|
|
|
2088
1843
|
}
|
|
2089
1844
|
out.push({
|
|
2090
1845
|
role: "user",
|
|
2091
|
-
parts: [
|
|
2092
|
-
{
|
|
2093
|
-
functionResponse: {
|
|
2094
|
-
name: nameByCallId.get(m.toolCallId) ?? "",
|
|
2095
|
-
response
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
]
|
|
1846
|
+
parts: [{ functionResponse: { name: "", response } }]
|
|
2099
1847
|
});
|
|
2100
1848
|
}
|
|
2101
1849
|
}
|
|
2102
1850
|
return out;
|
|
2103
1851
|
}
|
|
2104
|
-
function
|
|
1852
|
+
function toGeminiTool(t) {
|
|
2105
1853
|
return {
|
|
2106
|
-
functionDeclarations:
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
1854
|
+
functionDeclarations: [
|
|
1855
|
+
{
|
|
1856
|
+
name: t.name,
|
|
1857
|
+
description: t.description,
|
|
1858
|
+
parameters: t.parameters ?? { type: "object", properties: {} }
|
|
1859
|
+
}
|
|
1860
|
+
]
|
|
2111
1861
|
};
|
|
2112
1862
|
}
|
|
2113
1863
|
function extractFromGemini(data) {
|
|
@@ -2128,10 +1878,7 @@ function extractFromGemini(data) {
|
|
|
2128
1878
|
});
|
|
2129
1879
|
}
|
|
2130
1880
|
}
|
|
2131
|
-
return {
|
|
2132
|
-
text: stripThinkTags(text),
|
|
2133
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
2134
|
-
};
|
|
1881
|
+
return { text, toolCalls: toolCalls.length > 0 ? toolCalls : undefined };
|
|
2135
1882
|
}
|
|
2136
1883
|
|
|
2137
1884
|
class VertexExecutor extends LLMExecutor {
|
|
@@ -2154,23 +1901,21 @@ class VertexExecutor extends LLMExecutor {
|
|
|
2154
1901
|
this.region = extra.region ?? readAuthString(opts.auth, "region", "GOOGLE_CLOUD_REGION") ?? "us-central1";
|
|
2155
1902
|
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";
|
|
2156
1903
|
}
|
|
2157
|
-
async generate(model, body
|
|
1904
|
+
async generate(model, body) {
|
|
2158
1905
|
const url = `${this.baseURL}/${encodeURIComponent(model)}:generateContent`;
|
|
2159
|
-
const requestRaw = JSON.stringify(body);
|
|
2160
1906
|
const res = await fetch(url, {
|
|
2161
1907
|
method: "POST",
|
|
2162
1908
|
headers: {
|
|
2163
1909
|
Authorization: `Bearer ${this.apiKey}`,
|
|
2164
1910
|
"Content-Type": "application/json"
|
|
2165
1911
|
},
|
|
2166
|
-
body:
|
|
1912
|
+
body: JSON.stringify(body)
|
|
2167
1913
|
});
|
|
2168
|
-
const responseRaw = await res.text().catch(() => "");
|
|
2169
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
2170
1914
|
if (!res.ok) {
|
|
2171
|
-
|
|
1915
|
+
const text = await res.text().catch(() => "");
|
|
1916
|
+
throw new Error(`vertex request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
2172
1917
|
}
|
|
2173
|
-
return
|
|
1918
|
+
return await res.json();
|
|
2174
1919
|
}
|
|
2175
1920
|
async call(model, options) {
|
|
2176
1921
|
const jsonMode = "jsonSchemaName" in options;
|
|
@@ -2191,7 +1936,7 @@ class VertexExecutor extends LLMExecutor {
|
|
|
2191
1936
|
responseSchema: options.jsonSchema
|
|
2192
1937
|
};
|
|
2193
1938
|
}
|
|
2194
|
-
const data = await this.generate(model, body
|
|
1939
|
+
const data = await this.generate(model, body);
|
|
2195
1940
|
if (data.error) {
|
|
2196
1941
|
throw new Error(`vertex API error: ${data.error.message ?? "unknown"}`);
|
|
2197
1942
|
}
|
|
@@ -2199,7 +1944,7 @@ class VertexExecutor extends LLMExecutor {
|
|
|
2199
1944
|
if (!text) {
|
|
2200
1945
|
throw new Error("Empty response from model");
|
|
2201
1946
|
}
|
|
2202
|
-
return jsonMode ?
|
|
1947
|
+
return jsonMode ? JSON.parse(text) : text;
|
|
2203
1948
|
}
|
|
2204
1949
|
async chatWithTools(model, options) {
|
|
2205
1950
|
const log5 = logger.child("llm:vertex");
|
|
@@ -2208,9 +1953,9 @@ class VertexExecutor extends LLMExecutor {
|
|
|
2208
1953
|
const body = {
|
|
2209
1954
|
contents,
|
|
2210
1955
|
systemInstruction: { parts: [{ text: options.instruction }] },
|
|
2211
|
-
tools: options.tools.length > 0 ? [
|
|
1956
|
+
tools: options.tools.length > 0 ? [toGeminiTool(options.tools[0])] : undefined
|
|
2212
1957
|
};
|
|
2213
|
-
const data = await this.generate(model, body
|
|
1958
|
+
const data = await this.generate(model, body);
|
|
2214
1959
|
if (data.error) {
|
|
2215
1960
|
throw new Error(`vertex API error: ${data.error.message ?? "unknown"}`);
|
|
2216
1961
|
}
|
|
@@ -2286,41 +2031,25 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2286
2031
|
const extra = parsed.success ? parsed.data : {};
|
|
2287
2032
|
this.baseURL = (extra.baseURL ?? readAuthString(opts.auth, "baseURL", "GITLAB_BASE_URL") ?? "https://gitlab.com/api/v4/ai/llm/proxy").replace(/\/+$/, "");
|
|
2288
2033
|
}
|
|
2289
|
-
async send(body
|
|
2290
|
-
const requestRaw = JSON.stringify(body);
|
|
2034
|
+
async send(body) {
|
|
2291
2035
|
const res = await fetch(this.baseURL, {
|
|
2292
2036
|
method: "POST",
|
|
2293
2037
|
headers: {
|
|
2294
2038
|
Authorization: `Bearer ${this.apiKey}`,
|
|
2295
2039
|
"Content-Type": "application/json"
|
|
2296
2040
|
},
|
|
2297
|
-
body:
|
|
2041
|
+
body: JSON.stringify(body)
|
|
2298
2042
|
});
|
|
2299
|
-
const responseRaw = await res.text().catch(() => "");
|
|
2300
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
2301
2043
|
if (!res.ok) {
|
|
2302
|
-
|
|
2044
|
+
const text = await res.text().catch(() => "");
|
|
2045
|
+
throw new Error(`gitlab-duo request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
2303
2046
|
}
|
|
2304
|
-
return
|
|
2047
|
+
return await res.json();
|
|
2305
2048
|
}
|
|
2306
2049
|
async call(model, options) {
|
|
2050
|
+
const jsonMode = "jsonSchemaName" in options;
|
|
2307
2051
|
const log5 = logger.child("llm:gitlab-duo");
|
|
2308
|
-
|
|
2309
|
-
log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
|
|
2310
|
-
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
2311
|
-
instruction: options.instruction,
|
|
2312
|
-
jsonSchemaName: options.jsonSchemaName,
|
|
2313
|
-
jsonSchema: options.jsonSchema
|
|
2314
|
-
});
|
|
2315
|
-
const choice = await this.chatWithTools(model, {
|
|
2316
|
-
caller: options.caller ?? options.jsonSchemaName,
|
|
2317
|
-
instruction,
|
|
2318
|
-
messages: [{ role: "user", content: options.message }],
|
|
2319
|
-
tools: [tool]
|
|
2320
|
-
});
|
|
2321
|
-
return parseStructuredJsonResult(choice, toolName);
|
|
2322
|
-
}
|
|
2323
|
-
log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
|
|
2052
|
+
log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
2324
2053
|
const body = {
|
|
2325
2054
|
model,
|
|
2326
2055
|
messages: [
|
|
@@ -2328,15 +2057,15 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2328
2057
|
{ role: "user", content: options.message }
|
|
2329
2058
|
]
|
|
2330
2059
|
};
|
|
2331
|
-
const data = await this.send(body
|
|
2060
|
+
const data = await this.send(body);
|
|
2332
2061
|
if (data.error) {
|
|
2333
2062
|
throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
|
|
2334
2063
|
}
|
|
2335
|
-
const content =
|
|
2064
|
+
const content = data.choices?.[0]?.message?.content ?? "";
|
|
2336
2065
|
if (!content) {
|
|
2337
2066
|
throw new Error("Empty response from model");
|
|
2338
2067
|
}
|
|
2339
|
-
return content;
|
|
2068
|
+
return jsonMode ? JSON.parse(content) : content;
|
|
2340
2069
|
}
|
|
2341
2070
|
async chatWithTools(model, options) {
|
|
2342
2071
|
const log5 = logger.child("llm:gitlab-duo");
|
|
@@ -2349,7 +2078,7 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2349
2078
|
],
|
|
2350
2079
|
tools: options.tools.map(toTool)
|
|
2351
2080
|
};
|
|
2352
|
-
const data = await this.send(body
|
|
2081
|
+
const data = await this.send(body);
|
|
2353
2082
|
if (data.error) {
|
|
2354
2083
|
throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
|
|
2355
2084
|
}
|
|
@@ -2360,7 +2089,7 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2360
2089
|
}));
|
|
2361
2090
|
return {
|
|
2362
2091
|
message: {
|
|
2363
|
-
content: choice?.message?.content
|
|
2092
|
+
content: choice?.message?.content ?? undefined,
|
|
2364
2093
|
toolCalls
|
|
2365
2094
|
}
|
|
2366
2095
|
};
|
|
@@ -2417,43 +2146,26 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2417
2146
|
const account = extra.account ?? readAuthString(opts.auth, "account", "SNOWFLAKE_ACCOUNT") ?? "";
|
|
2418
2147
|
this.baseURL = account ? `https://${account}.snowflakecomputing.com/api/v2/cortex/inference:complete` : "https://__account__.snowflakecomputing.com/api/v2/cortex/inference:complete";
|
|
2419
2148
|
}
|
|
2420
|
-
async send(body
|
|
2421
|
-
const requestRaw = JSON.stringify(body);
|
|
2149
|
+
async send(body) {
|
|
2422
2150
|
const res = await fetch(this.baseURL, {
|
|
2423
2151
|
method: "POST",
|
|
2424
2152
|
headers: {
|
|
2425
2153
|
Authorization: `Bearer ${this.apiKey}`,
|
|
2426
2154
|
"Content-Type": "application/json"
|
|
2427
2155
|
},
|
|
2428
|
-
body:
|
|
2156
|
+
body: JSON.stringify(body)
|
|
2429
2157
|
});
|
|
2430
|
-
const responseRaw = await res.text().catch(() => "");
|
|
2431
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
2432
2158
|
if (!res.ok) {
|
|
2433
|
-
|
|
2159
|
+
const text = await res.text().catch(() => "");
|
|
2160
|
+
throw new Error(`snowflake-cortex request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
2434
2161
|
}
|
|
2435
|
-
return
|
|
2162
|
+
return await res.json();
|
|
2436
2163
|
}
|
|
2437
2164
|
async call(model, options) {
|
|
2438
|
-
const
|
|
2439
|
-
if ("jsonSchemaName" in options) {
|
|
2440
|
-
log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
|
|
2441
|
-
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
2442
|
-
instruction: options.instruction,
|
|
2443
|
-
jsonSchemaName: options.jsonSchemaName,
|
|
2444
|
-
jsonSchema: options.jsonSchema
|
|
2445
|
-
});
|
|
2446
|
-
const choice = await this.chatWithTools(model, {
|
|
2447
|
-
caller: options.caller ?? options.jsonSchemaName,
|
|
2448
|
-
instruction,
|
|
2449
|
-
messages: [{ role: "user", content: options.message }],
|
|
2450
|
-
tools: [tool],
|
|
2451
|
-
reasoningEffort: "none"
|
|
2452
|
-
});
|
|
2453
|
-
return parseStructuredJsonResult(choice, toolName);
|
|
2454
|
-
}
|
|
2165
|
+
const jsonMode = "jsonSchemaName" in options;
|
|
2455
2166
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
2456
|
-
log5.
|
|
2167
|
+
const log5 = logger.child("llm:snowflake-cortex");
|
|
2168
|
+
log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
2457
2169
|
const body = {
|
|
2458
2170
|
model,
|
|
2459
2171
|
messages: [
|
|
@@ -2464,15 +2176,15 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2464
2176
|
if (reasoning !== "none") {
|
|
2465
2177
|
body["reasoning_effort"] = reasoning;
|
|
2466
2178
|
}
|
|
2467
|
-
const data = await this.send(body
|
|
2179
|
+
const data = await this.send(body);
|
|
2468
2180
|
if (data.error) {
|
|
2469
2181
|
throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
|
|
2470
2182
|
}
|
|
2471
|
-
const content =
|
|
2183
|
+
const content = data.choices?.[0]?.message?.content ?? data.message ?? "";
|
|
2472
2184
|
if (!content) {
|
|
2473
2185
|
throw new Error("Empty response from model");
|
|
2474
2186
|
}
|
|
2475
|
-
return content;
|
|
2187
|
+
return jsonMode ? JSON.parse(content) : content;
|
|
2476
2188
|
}
|
|
2477
2189
|
async chatWithTools(model, options) {
|
|
2478
2190
|
const log5 = logger.child("llm:snowflake-cortex");
|
|
@@ -2485,12 +2197,12 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2485
2197
|
],
|
|
2486
2198
|
tools: options.tools.map(toCortexTool)
|
|
2487
2199
|
};
|
|
2488
|
-
const data = await this.send(body
|
|
2200
|
+
const data = await this.send(body);
|
|
2489
2201
|
if (data.error) {
|
|
2490
2202
|
throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
|
|
2491
2203
|
}
|
|
2492
2204
|
const choice = data.choices?.[0];
|
|
2493
|
-
const content =
|
|
2205
|
+
const content = choice?.message?.content ?? data.message ?? "";
|
|
2494
2206
|
const toolCalls = choice?.message?.tool_calls?.map((c) => ({
|
|
2495
2207
|
id: c.id,
|
|
2496
2208
|
function: {
|
|
@@ -2529,8 +2241,7 @@ register("stackit", StackitExecutor);
|
|
|
2529
2241
|
register("gmi", GmiExecutor);
|
|
2530
2242
|
register("zai", ZaiExecutor);
|
|
2531
2243
|
register("zenmux", ZenMuxExecutor);
|
|
2532
|
-
register("
|
|
2533
|
-
register("minimax-cn", MiniMaxCnExecutor);
|
|
2244
|
+
register("MiniMax", MiniMaxExecutor);
|
|
2534
2245
|
register("ionet", IoNetExecutor);
|
|
2535
2246
|
register("baseten", BasetenExecutor);
|
|
2536
2247
|
register("cortecs", CortecsExecutor);
|
|
@@ -2564,14 +2275,14 @@ var llm = new Proxy({}, {
|
|
|
2564
2275
|
// src/provider/promptLoader.ts
|
|
2565
2276
|
import { existsSync as existsSync2 } from "fs";
|
|
2566
2277
|
import { readFile as readFile2 } from "fs/promises";
|
|
2567
|
-
import path, { dirname as
|
|
2278
|
+
import path, { dirname as dirname3 } from "path";
|
|
2568
2279
|
import { fileURLToPath } from "url";
|
|
2569
2280
|
var log5 = logger.child("prompt-loader");
|
|
2570
2281
|
function fileName(promptKey) {
|
|
2571
2282
|
return promptKey.toLowerCase() + ".md";
|
|
2572
2283
|
}
|
|
2573
2284
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
2574
|
-
var __dirname2 =
|
|
2285
|
+
var __dirname2 = dirname3(__filename2);
|
|
2575
2286
|
var PROMPTS_DIR = existsSync2(path.resolve(__dirname2, "prompts")) ? path.resolve(__dirname2, "prompts") : path.resolve(__dirname2, "../../prompts");
|
|
2576
2287
|
async function loadPrompt(promptKey) {
|
|
2577
2288
|
const filePath = path.join(PROMPTS_DIR, fileName(promptKey));
|
|
@@ -2696,14 +2407,14 @@ function translateMessageHistory(personaName, entries) {
|
|
|
2696
2407
|
}
|
|
2697
2408
|
|
|
2698
2409
|
// src/brain/schedule.ts
|
|
2699
|
-
function
|
|
2410
|
+
function pad22(n) {
|
|
2700
2411
|
return n < 10 ? `0${n}` : `${n}`;
|
|
2701
2412
|
}
|
|
2702
2413
|
function formatDateKey(d) {
|
|
2703
|
-
return `${d.getFullYear()}-${
|
|
2414
|
+
return `${d.getFullYear()}-${pad22(d.getMonth() + 1)}-${pad22(d.getDate())}`;
|
|
2704
2415
|
}
|
|
2705
2416
|
function formatMonthKey(d) {
|
|
2706
|
-
return `${d.getFullYear()}-${
|
|
2417
|
+
return `${d.getFullYear()}-${pad22(d.getMonth() + 1)}`;
|
|
2707
2418
|
}
|
|
2708
2419
|
|
|
2709
2420
|
// src/brain/memory.ts
|
|
@@ -2791,22 +2502,22 @@ class Brain {
|
|
|
2791
2502
|
db;
|
|
2792
2503
|
space;
|
|
2793
2504
|
brainbase;
|
|
2794
|
-
availabilityCache = new Map;
|
|
2795
2505
|
memory;
|
|
2796
|
-
|
|
2506
|
+
availabilityCache = new Map;
|
|
2507
|
+
constructor(db, space, brainbase, memory = new Memory(this.db, this.space)) {
|
|
2797
2508
|
this.db = db;
|
|
2798
2509
|
this.space = space;
|
|
2799
2510
|
this.brainbase = brainbase;
|
|
2800
|
-
this.memory = memory
|
|
2511
|
+
this.memory = memory;
|
|
2801
2512
|
log7.debug(`Brain constructed: id=${brainbase.brainId} name=${brainbase.displayName} space=${space.name}`);
|
|
2802
2513
|
}
|
|
2803
2514
|
async createDailySchedule(datetime) {
|
|
2804
|
-
const
|
|
2805
|
-
log7.debug(`createDailySchedule: starting for ${
|
|
2515
|
+
const dateKey = formatDateKey(datetime);
|
|
2516
|
+
log7.debug(`createDailySchedule: starting for ${dateKey}`);
|
|
2806
2517
|
try {
|
|
2807
|
-
const existing = await this.memory.get(`daily-schedule:${
|
|
2518
|
+
const existing = await this.memory.get(`daily-schedule:${dateKey}`);
|
|
2808
2519
|
if (existing) {
|
|
2809
|
-
log7.debug(`createDailySchedule: cache hit for ${
|
|
2520
|
+
log7.debug(`createDailySchedule: cache hit for ${dateKey}`);
|
|
2810
2521
|
try {
|
|
2811
2522
|
return JSON.parse(existing.content);
|
|
2812
2523
|
} catch (parseErr) {
|
|
@@ -2834,7 +2545,7 @@ class Brain {
|
|
|
2834
2545
|
}
|
|
2835
2546
|
const instruction = await loadPrompt("DAILY_SCHEDULE");
|
|
2836
2547
|
const promptMessage = [
|
|
2837
|
-
`Target date: ${
|
|
2548
|
+
`Target date: ${dateKey} (${datetime.toLocaleDateString("en-US", { weekday: "long" })})`,
|
|
2838
2549
|
`Personality: ${this.brainbase.baseSystemPrompt}`,
|
|
2839
2550
|
monthlySummary ? `Monthly summary for this day: ${monthlySummary}` : "(no monthly summary available for this date)",
|
|
2840
2551
|
`Recent schedule (${twoDaysAgoKey}, 2 days ago): ${twoDaysAgoSchedule ? twoDaysAgoSchedule.items.map((s) => `${s.start} ${s.activity}`).join(", ") : "(no schedule on file for 2 days ago)"}`,
|
|
@@ -2845,7 +2556,6 @@ class Brain {
|
|
|
2845
2556
|
`);
|
|
2846
2557
|
log7.debug(`createDailySchedule: calling identity model`);
|
|
2847
2558
|
const schedule = await llm.call(llm.models.identity, {
|
|
2848
|
-
caller: "daily-schedule",
|
|
2849
2559
|
instruction,
|
|
2850
2560
|
message: promptMessage,
|
|
2851
2561
|
jsonSchemaName: "daily-schedule",
|
|
@@ -2853,15 +2563,15 @@ class Brain {
|
|
|
2853
2563
|
});
|
|
2854
2564
|
log7.debug(`createDailySchedule: model returned ${schedule.items.length} slots`);
|
|
2855
2565
|
await this.memory.add({
|
|
2856
|
-
customId: `daily-schedule:${
|
|
2566
|
+
customId: `daily-schedule:${dateKey}`,
|
|
2857
2567
|
content: JSON.stringify(schedule),
|
|
2858
2568
|
metadata: {
|
|
2859
2569
|
kind: "schedule",
|
|
2860
2570
|
source: "createDailySchedule",
|
|
2861
|
-
date:
|
|
2571
|
+
date: dateKey
|
|
2862
2572
|
}
|
|
2863
2573
|
});
|
|
2864
|
-
log7.debug(`createDailySchedule: persisted ${
|
|
2574
|
+
log7.debug(`createDailySchedule: persisted ${dateKey}`);
|
|
2865
2575
|
return schedule;
|
|
2866
2576
|
} catch (error) {
|
|
2867
2577
|
let reason = error instanceof Error ? error.message + `(${error.name})` : String(error);
|
|
@@ -2873,21 +2583,17 @@ class Brain {
|
|
|
2873
2583
|
}
|
|
2874
2584
|
async regenerateSchedules() {
|
|
2875
2585
|
const today = new Date;
|
|
2876
|
-
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
|
2877
|
-
const monthly = await this.createMonthlySchedule(nextMonth);
|
|
2878
|
-
if (!monthly) {
|
|
2879
|
-
log7.debug(`regenerateSchedules: skip daily — monthly schedule generation failed`);
|
|
2880
|
-
return;
|
|
2881
|
-
}
|
|
2882
2586
|
const tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
|
|
2883
2587
|
await this.createDailySchedule(tomorrow);
|
|
2884
2588
|
await this.createDailySchedule(today);
|
|
2589
|
+
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
|
2590
|
+
await this.createMonthlySchedule(nextMonth);
|
|
2885
2591
|
}
|
|
2886
2592
|
async createMonthlySchedule(datetime) {
|
|
2887
2593
|
const year = datetime.getMonth() === 11 ? datetime.getFullYear() + 1 : datetime.getFullYear();
|
|
2888
2594
|
const month = (datetime.getMonth() + 1) % 12;
|
|
2889
2595
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
2890
|
-
const monthKey = `${year}-${
|
|
2596
|
+
const monthKey = `${year}-${pad22(month + 1)}`;
|
|
2891
2597
|
log7.debug(`createMonthlySchedule: starting for ${monthKey}`);
|
|
2892
2598
|
try {
|
|
2893
2599
|
const existing = await this.memory.get(`monthly-schedule:${monthKey}`);
|
|
@@ -2900,7 +2606,7 @@ class Brain {
|
|
|
2900
2606
|
}
|
|
2901
2607
|
}
|
|
2902
2608
|
const twoMonthsAgo = new Date(year, month - 2, 1);
|
|
2903
|
-
const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${
|
|
2609
|
+
const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${pad22(twoMonthsAgo.getMonth() + 1)}`;
|
|
2904
2610
|
log7.debug(`createMonthlySchedule: gathering context (history, ${twoMonthsAgoKey})`);
|
|
2905
2611
|
const [history, twoMonthsAgoStored] = await Promise.all([
|
|
2906
2612
|
this.getHistoryFacts(),
|
|
@@ -2928,7 +2634,6 @@ class Brain {
|
|
|
2928
2634
|
`);
|
|
2929
2635
|
log7.debug(`createMonthlySchedule: calling identity model`);
|
|
2930
2636
|
const schedule = await llm.call(llm.models.identity, {
|
|
2931
|
-
caller: "monthly-schedule",
|
|
2932
2637
|
instruction,
|
|
2933
2638
|
message: promptMessage,
|
|
2934
2639
|
jsonSchemaName: "monthly-schedule",
|
|
@@ -2959,11 +2664,11 @@ class Brain {
|
|
|
2959
2664
|
}
|
|
2960
2665
|
log7.debug(`sleepMemory: starting, ${history.length} messages`);
|
|
2961
2666
|
try {
|
|
2962
|
-
const
|
|
2667
|
+
const dateKey = formatDateKey(datetime);
|
|
2963
2668
|
const instruction = await loadPrompt("MEMOIR");
|
|
2964
2669
|
const historyBlock = translateMessageHistory(this.brainbase.displayName, history);
|
|
2965
2670
|
const promptMessage = [
|
|
2966
|
-
`Date: ${
|
|
2671
|
+
`Date: ${dateKey}`,
|
|
2967
2672
|
`Personality: ${this.brainbase.baseSystemPrompt}`,
|
|
2968
2673
|
`Conversation log:`,
|
|
2969
2674
|
historyBlock
|
|
@@ -2972,21 +2677,20 @@ class Brain {
|
|
|
2972
2677
|
`);
|
|
2973
2678
|
log7.debug(`sleepMemory: calling identity model`);
|
|
2974
2679
|
const memoir = await llm.call(llm.models.identity, {
|
|
2975
|
-
caller: "sleep-memory",
|
|
2976
2680
|
instruction,
|
|
2977
2681
|
message: promptMessage
|
|
2978
2682
|
});
|
|
2979
2683
|
log7.debug(`sleepMemory: model returned ${memoir.length} chars`);
|
|
2980
2684
|
await this.memory.add({
|
|
2981
|
-
customId: `daily-journal:${
|
|
2685
|
+
customId: `daily-journal:${dateKey}`,
|
|
2982
2686
|
content: memoir,
|
|
2983
2687
|
metadata: {
|
|
2984
2688
|
kind: "daily-journal",
|
|
2985
2689
|
source: "sleepMemory",
|
|
2986
|
-
date:
|
|
2690
|
+
date: dateKey
|
|
2987
2691
|
}
|
|
2988
2692
|
});
|
|
2989
|
-
log7.debug(`sleepMemory: journal persisted for ${
|
|
2693
|
+
log7.debug(`sleepMemory: journal persisted for ${dateKey}`);
|
|
2990
2694
|
return memoir;
|
|
2991
2695
|
} catch (error) {
|
|
2992
2696
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -2995,22 +2699,22 @@ class Brain {
|
|
|
2995
2699
|
}
|
|
2996
2700
|
}
|
|
2997
2701
|
async getTodayScheduledAvailability(datetime) {
|
|
2998
|
-
const
|
|
2702
|
+
const dateKey = formatDateKey(datetime);
|
|
2999
2703
|
try {
|
|
3000
|
-
const cached = this.availabilityCache.get(
|
|
2704
|
+
const cached = this.availabilityCache.get(dateKey);
|
|
3001
2705
|
if (cached) {
|
|
3002
|
-
log7.debug(`getTodayScheduledAvailability: cache hit for ${
|
|
2706
|
+
log7.debug(`getTodayScheduledAvailability: cache hit for ${dateKey}`);
|
|
3003
2707
|
return cached;
|
|
3004
2708
|
}
|
|
3005
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
2709
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey}`);
|
|
3006
2710
|
if (!stored) {
|
|
3007
|
-
log7.debug(`getTodayScheduledAvailability: no schedule for ${
|
|
2711
|
+
log7.debug(`getTodayScheduledAvailability: no schedule for ${dateKey}`);
|
|
3008
2712
|
return null;
|
|
3009
2713
|
}
|
|
3010
2714
|
const dailySchedule = JSON.parse(stored.content);
|
|
3011
2715
|
const availability = await this.deriveAvailabilityFromSchedule(dailySchedule);
|
|
3012
|
-
this.availabilityCache.set(
|
|
3013
|
-
log7.debug(`getTodayScheduledAvailability: cached ${availability.items.length} windows for ${
|
|
2716
|
+
this.availabilityCache.set(dateKey, availability);
|
|
2717
|
+
log7.debug(`getTodayScheduledAvailability: cached ${availability.items.length} windows for ${dateKey}`);
|
|
3014
2718
|
return availability;
|
|
3015
2719
|
} catch (error) {
|
|
3016
2720
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -3021,7 +2725,7 @@ class Brain {
|
|
|
3021
2725
|
async getAvailability(datetime = new Date) {
|
|
3022
2726
|
const h = datetime.getHours();
|
|
3023
2727
|
const m = datetime.getMinutes();
|
|
3024
|
-
const hhmm = `${
|
|
2728
|
+
const hhmm = `${pad22(h)}:${pad22(m)}`;
|
|
3025
2729
|
const current = h * 60 + m;
|
|
3026
2730
|
const toMinutes = (s) => {
|
|
3027
2731
|
const [hh = 0, mm = 0] = s.split(":").map((x) => parseInt(x, 10));
|
|
@@ -3034,10 +2738,10 @@ class Brain {
|
|
|
3034
2738
|
return result;
|
|
3035
2739
|
}
|
|
3036
2740
|
async getCurrentAndAdjacentSlots(now) {
|
|
3037
|
-
const
|
|
3038
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
2741
|
+
const dateKey = formatDateKey(now);
|
|
2742
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey}`);
|
|
3039
2743
|
if (!stored) {
|
|
3040
|
-
log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${
|
|
2744
|
+
log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${dateKey}`);
|
|
3041
2745
|
return [];
|
|
3042
2746
|
}
|
|
3043
2747
|
let schedule;
|
|
@@ -3070,7 +2774,6 @@ class Brain {
|
|
|
3070
2774
|
personality: this.brainbase.baseSystemPrompt
|
|
3071
2775
|
});
|
|
3072
2776
|
const result = await llm.call(llm.models.identity, {
|
|
3073
|
-
caller: "availability",
|
|
3074
2777
|
instruction,
|
|
3075
2778
|
message: promptMessage,
|
|
3076
2779
|
jsonSchemaName: "availability",
|
|
@@ -3146,7 +2849,6 @@ class Brain {
|
|
|
3146
2849
|
let choice;
|
|
3147
2850
|
try {
|
|
3148
2851
|
choice = await llm.chatWithTools(llm.models.conversation, {
|
|
3149
|
-
caller: initiate ? "start-conversation" : "send-message",
|
|
3150
2852
|
instruction: `${this.brainbase.baseSystemPrompt}
|
|
3151
2853
|
|
|
3152
2854
|
${instruction}`,
|
|
@@ -3218,11 +2920,11 @@ ${instruction}`,
|
|
|
3218
2920
|
${facts || "(none indexed)"}`;
|
|
3219
2921
|
}
|
|
3220
2922
|
async buildScheduleBlock(now) {
|
|
3221
|
-
const
|
|
2923
|
+
const dateKey = formatDateKey(now);
|
|
3222
2924
|
const currentSlots = await this.getCurrentAndAdjacentSlots(now);
|
|
3223
2925
|
const currentBlock = currentSlots.length > 0 ? `Currently (around ${now.toTimeString().slice(0, 5)}):
|
|
3224
2926
|
${currentSlots.map((s) => ` ${s.start}-${s.end} ${s.activity}${s.notes ? ` (${s.notes})` : ""}`).join(`
|
|
3225
|
-
`)}` : `Currently (${
|
|
2927
|
+
`)}` : `Currently (${dateKey} ${now.toTimeString().slice(0, 5)}): (no matching slot in today's schedule)`;
|
|
3226
2928
|
const days = [
|
|
3227
2929
|
{
|
|
3228
2930
|
label: "Yesterday",
|
|
@@ -3245,9 +2947,9 @@ ${currentBlock}
|
|
|
3245
2947
|
${blocks.join(`
|
|
3246
2948
|
`)}`;
|
|
3247
2949
|
}
|
|
3248
|
-
async getDailyScheduleSummary(
|
|
2950
|
+
async getDailyScheduleSummary(dateKey) {
|
|
3249
2951
|
try {
|
|
3250
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
2952
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey}`);
|
|
3251
2953
|
if (!stored)
|
|
3252
2954
|
return null;
|
|
3253
2955
|
const schedule = JSON.parse(stored.content);
|
|
@@ -3318,7 +3020,6 @@ ${blocks.join(`
|
|
|
3318
3020
|
const personaInitInstruction = await loadPrompt("PERSONA_INIT");
|
|
3319
3021
|
log7.debug(`Brain.create: generating description`);
|
|
3320
3022
|
const description = await llm.call(llm.models.identity, {
|
|
3321
|
-
caller: "persona-init",
|
|
3322
3023
|
instruction: personaInitInstruction,
|
|
3323
3024
|
message: seed
|
|
3324
3025
|
});
|
|
@@ -3326,7 +3027,6 @@ ${blocks.join(`
|
|
|
3326
3027
|
const personaSystemInstruction = await loadPrompt("PERSONA_BASE_SYSTEM_PROMPT");
|
|
3327
3028
|
log7.debug(`Brain.create: generating base system prompt + dials`);
|
|
3328
3029
|
const generated = await llm.call(llm.models.identity, {
|
|
3329
|
-
caller: "base-system-prompt",
|
|
3330
3030
|
instruction: personaSystemInstruction,
|
|
3331
3031
|
message: description,
|
|
3332
3032
|
jsonSchemaName: "base-system-prompt",
|
|
@@ -3364,12 +3064,9 @@ ${personaSystemFixed}`;
|
|
|
3364
3064
|
log7.debug(`Brain.create: brainbase saved (id=${brainId})`);
|
|
3365
3065
|
return { brainId, brain: new Brain(db, space, brainbase, memory) };
|
|
3366
3066
|
} catch (error) {
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
}
|
|
3371
|
-
log7.debug(`Brain.create failed: ${reason}`);
|
|
3372
|
-
return { error: reason };
|
|
3067
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
3068
|
+
logger.error(`Failed to create brain "${displayName}": ${reason}`);
|
|
3069
|
+
return null;
|
|
3373
3070
|
}
|
|
3374
3071
|
}
|
|
3375
3072
|
static async delete(brainId) {
|
|
@@ -3487,18 +3184,10 @@ var SLEEP_MEMORY_CRON_KEY = "__sleep-memory__";
|
|
|
3487
3184
|
var SLEEP_MEMORY_CRON_PATTERN = "0 * * * *";
|
|
3488
3185
|
var START_CONVERSATION_CRON_KEY = "__start-conversation__";
|
|
3489
3186
|
var START_CONVERSATION_CRON_PATTERN = "*/10 * * * *";
|
|
3490
|
-
var
|
|
3491
|
-
var
|
|
3492
|
-
var
|
|
3493
|
-
var
|
|
3494
|
-
var DO_ACTIONS = ["generateSchedule", "sleepMemory"];
|
|
3495
|
-
var VIEW_THINGS = [
|
|
3496
|
-
"daily-schedule",
|
|
3497
|
-
"monthly-schedule",
|
|
3498
|
-
"sending-queue",
|
|
3499
|
-
"deferred-queue",
|
|
3500
|
-
"today-availability"
|
|
3501
|
-
];
|
|
3187
|
+
var DAILY_SCHEDULE_CRON_KEY = "__daily-schedule__";
|
|
3188
|
+
var DAILY_SCHEDULE_CRON_PATTERN = "0 0 * * *";
|
|
3189
|
+
var DAILY_SCHEDULE_NOON_CRON_KEY = "__daily-schedule-noon__";
|
|
3190
|
+
var DAILY_SCHEDULE_NOON_CRON_PATTERN = "0 12 * * *";
|
|
3502
3191
|
|
|
3503
3192
|
class BaseChannel {
|
|
3504
3193
|
brain;
|
|
@@ -3518,27 +3207,24 @@ class BaseChannel {
|
|
|
3518
3207
|
static activeChannels = new Map;
|
|
3519
3208
|
constructor(brain) {
|
|
3520
3209
|
this.brain = brain;
|
|
3521
|
-
this.registerCron(SLEEP_MEMORY_CRON_KEY, SLEEP_MEMORY_CRON_PATTERN, () =>
|
|
3522
|
-
|
|
3523
|
-
this.registerCron(SCHEDULE_NOON_CRON_KEY, SCHEDULE_NOON_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3524
|
-
this.registerCron(START_CONVERSATION_CRON_KEY, START_CONVERSATION_CRON_PATTERN, () => this.runStartConversation());
|
|
3525
|
-
}
|
|
3526
|
-
async runSleepMemory(force = false) {
|
|
3527
|
-
const dateKey2 = formatDateKey(new Date);
|
|
3528
|
-
if (!force) {
|
|
3210
|
+
this.registerCron(SLEEP_MEMORY_CRON_KEY, SLEEP_MEMORY_CRON_PATTERN, async () => {
|
|
3211
|
+
const dateKey = formatDateKey(new Date);
|
|
3529
3212
|
const availability = await this.brain.getAvailability();
|
|
3530
3213
|
if (availability.status !== "offline") {
|
|
3531
3214
|
logger.debug(`sleepMemory cron: skip — availability=${availability.status}`);
|
|
3532
3215
|
return;
|
|
3533
3216
|
}
|
|
3534
|
-
const existing = await this.brain.memory.get(`daily-journal:${
|
|
3217
|
+
const existing = await this.brain.memory.get(`daily-journal:${dateKey}`);
|
|
3535
3218
|
if (existing) {
|
|
3536
|
-
logger.debug(`sleepMemory cron: skip — journal for ${
|
|
3219
|
+
logger.debug(`sleepMemory cron: skip — journal for ${dateKey} exists`);
|
|
3537
3220
|
return;
|
|
3538
3221
|
}
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3222
|
+
const history = await this.getMessageHistoryBetween(new Date(Date.now() - 24 * 60 * 60 * 1000), new Date);
|
|
3223
|
+
await this.brain.sleepMemory(new Date, history);
|
|
3224
|
+
});
|
|
3225
|
+
this.registerCron(DAILY_SCHEDULE_CRON_KEY, DAILY_SCHEDULE_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3226
|
+
this.registerCron(DAILY_SCHEDULE_NOON_CRON_KEY, DAILY_SCHEDULE_NOON_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3227
|
+
this.registerCron(START_CONVERSATION_CRON_KEY, START_CONVERSATION_CRON_PATTERN, () => this.runStartConversation());
|
|
3542
3228
|
}
|
|
3543
3229
|
async regenerateSchedules() {
|
|
3544
3230
|
logger.debug(`regenerateSchedules: tick for ${this.brain.brainbase.displayName}`);
|
|
@@ -3559,8 +3245,8 @@ class BaseChannel {
|
|
|
3559
3245
|
return;
|
|
3560
3246
|
}
|
|
3561
3247
|
const now = new Date;
|
|
3562
|
-
const
|
|
3563
|
-
const count = this.startConversationCounters.get(
|
|
3248
|
+
const dateKey = formatDateKey(now);
|
|
3249
|
+
const count = this.startConversationCounters.get(dateKey) ?? 0;
|
|
3564
3250
|
const countThreshold = this.brain.brainbase.startConversationCountThreshold;
|
|
3565
3251
|
if (count >= countThreshold)
|
|
3566
3252
|
return;
|
|
@@ -3573,7 +3259,7 @@ class BaseChannel {
|
|
|
3573
3259
|
});
|
|
3574
3260
|
if (replies.length === 0)
|
|
3575
3261
|
return;
|
|
3576
|
-
this.startConversationCounters.set(
|
|
3262
|
+
this.startConversationCounters.set(dateKey, count + 1);
|
|
3577
3263
|
this.startConversationTimeout = true;
|
|
3578
3264
|
setTimeout(() => {
|
|
3579
3265
|
this.startConversationTimeout = false;
|
|
@@ -3602,7 +3288,7 @@ class BaseChannel {
|
|
|
3602
3288
|
}, callback);
|
|
3603
3289
|
}
|
|
3604
3290
|
getRegisteredCrons() {
|
|
3605
|
-
return scheduledJobs.filter((c) => c.name && c.name.startsWith(this.resolveCronPrefix()));
|
|
3291
|
+
return scheduledJobs.filter((c) => c.name && c.name.startsWith(this.resolveCronPrefix())).map((c) => c.name);
|
|
3606
3292
|
}
|
|
3607
3293
|
pauseCron(key) {
|
|
3608
3294
|
const job = scheduledJobs.find((c) => c.name === this.resolveCronName(key));
|
|
@@ -3693,13 +3379,6 @@ class BaseChannel {
|
|
|
3693
3379
|
this.isChattingDebounce = null;
|
|
3694
3380
|
}, IS_CHATTING_DEBOUNCE_MS);
|
|
3695
3381
|
}
|
|
3696
|
-
async initAvailability() {
|
|
3697
|
-
const current = await this.brain.getAvailability();
|
|
3698
|
-
this.previousAvailability = current.status;
|
|
3699
|
-
logger.debug(`initAvailability: ${current.status}`);
|
|
3700
|
-
await this.setAvailability(current.status);
|
|
3701
|
-
this.ensureAvailabilityWatcher();
|
|
3702
|
-
}
|
|
3703
3382
|
ensureAvailabilityWatcher() {
|
|
3704
3383
|
if (this.isCronStarted(AVAILABILITY_WATCHER_KEY))
|
|
3705
3384
|
return;
|
|
@@ -3709,9 +3388,6 @@ class BaseChannel {
|
|
|
3709
3388
|
const prev = this.previousAvailability;
|
|
3710
3389
|
this.previousAvailability = current.status;
|
|
3711
3390
|
logger.debug(`availabilityWatcher: ${prev ?? "(initial)"} → ${current.status}`);
|
|
3712
|
-
if (prev !== current.status) {
|
|
3713
|
-
await this.setAvailability(current.status);
|
|
3714
|
-
}
|
|
3715
3391
|
if (prev !== null && prev !== "online" && current.status === "online") {
|
|
3716
3392
|
await this.flushDeferred();
|
|
3717
3393
|
}
|
|
@@ -3760,88 +3436,6 @@ class BaseChannel {
|
|
|
3760
3436
|
static all() {
|
|
3761
3437
|
return Array.from(BaseChannel.activeChannels.values());
|
|
3762
3438
|
}
|
|
3763
|
-
static forceDo(brainId, action) {
|
|
3764
|
-
const channel = BaseChannel.activeChannels.get(brainId);
|
|
3765
|
-
if (!channel) {
|
|
3766
|
-
return {
|
|
3767
|
-
ok: false,
|
|
3768
|
-
error: `no active channel for brain "${brainId}" (is it activated and daemon running?)`
|
|
3769
|
-
};
|
|
3770
|
-
}
|
|
3771
|
-
const displayName = channel.brain.brainbase.displayName;
|
|
3772
|
-
logger.info(`do ${action}: queued for "${displayName}" (${brainId})`);
|
|
3773
|
-
(async () => {
|
|
3774
|
-
try {
|
|
3775
|
-
if (action === "generateSchedule") {
|
|
3776
|
-
await channel.regenerateSchedules();
|
|
3777
|
-
} else {
|
|
3778
|
-
await channel.runSleepMemory(true);
|
|
3779
|
-
}
|
|
3780
|
-
logger.success(`do ${action}: done for "${displayName}" (${brainId})`);
|
|
3781
|
-
} catch (error) {
|
|
3782
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
3783
|
-
logger.error(`do ${action}: failed for "${displayName}" (${brainId}): ${reason}`);
|
|
3784
|
-
}
|
|
3785
|
-
})();
|
|
3786
|
-
return { ok: true, displayName };
|
|
3787
|
-
}
|
|
3788
|
-
static async view(brainId, thing) {
|
|
3789
|
-
const channel = BaseChannel.activeChannels.get(brainId);
|
|
3790
|
-
if (!channel) {
|
|
3791
|
-
return {
|
|
3792
|
-
ok: false,
|
|
3793
|
-
error: `no active channel for brain "${brainId}" (is it activated and daemon running?)`
|
|
3794
|
-
};
|
|
3795
|
-
}
|
|
3796
|
-
const displayName = channel.brain.brainbase.displayName;
|
|
3797
|
-
logger.debug(`view ${thing}: "${displayName}" (${brainId})`);
|
|
3798
|
-
return {
|
|
3799
|
-
ok: true,
|
|
3800
|
-
displayName,
|
|
3801
|
-
value: await channel.readView(thing)
|
|
3802
|
-
};
|
|
3803
|
-
}
|
|
3804
|
-
async readView(thing) {
|
|
3805
|
-
const now = new Date;
|
|
3806
|
-
switch (thing) {
|
|
3807
|
-
case "daily-schedule": {
|
|
3808
|
-
const key = formatDateKey(now);
|
|
3809
|
-
const stored = await this.brain.memory.get(`daily-schedule:${key}`);
|
|
3810
|
-
if (!stored)
|
|
3811
|
-
return null;
|
|
3812
|
-
try {
|
|
3813
|
-
return { key, schedule: JSON.parse(stored.content) };
|
|
3814
|
-
} catch {
|
|
3815
|
-
return { key, raw: stored.content };
|
|
3816
|
-
}
|
|
3817
|
-
}
|
|
3818
|
-
case "monthly-schedule": {
|
|
3819
|
-
const key = formatMonthKey(now);
|
|
3820
|
-
const stored = await this.brain.memory.get(`monthly-schedule:${key}`);
|
|
3821
|
-
if (!stored)
|
|
3822
|
-
return null;
|
|
3823
|
-
try {
|
|
3824
|
-
return { key, schedule: JSON.parse(stored.content) };
|
|
3825
|
-
} catch {
|
|
3826
|
-
return { key, raw: stored.content };
|
|
3827
|
-
}
|
|
3828
|
-
}
|
|
3829
|
-
case "sending-queue":
|
|
3830
|
-
return this.isSendingQueue.map((m) => ({
|
|
3831
|
-
sender: m.sender,
|
|
3832
|
-
time: m.time.toISOString(),
|
|
3833
|
-
content: m.content
|
|
3834
|
-
}));
|
|
3835
|
-
case "deferred-queue":
|
|
3836
|
-
return this.deferredQueue.map((m) => ({
|
|
3837
|
-
sender: m.sender,
|
|
3838
|
-
time: m.time.toISOString(),
|
|
3839
|
-
content: m.content
|
|
3840
|
-
}));
|
|
3841
|
-
case "today-availability":
|
|
3842
|
-
return await this.brain.getTodayScheduledAvailability(now);
|
|
3843
|
-
}
|
|
3844
|
-
}
|
|
3845
3439
|
static async shutdownAll() {
|
|
3846
3440
|
await Promise.all(BaseChannel.all().map((c) => c.shutdown()));
|
|
3847
3441
|
}
|
|
@@ -3854,8 +3448,8 @@ class BaseChannel {
|
|
|
3854
3448
|
logger.debug(`shutdown: done`);
|
|
3855
3449
|
}
|
|
3856
3450
|
stopOwnCrons() {
|
|
3857
|
-
for (const
|
|
3858
|
-
|
|
3451
|
+
for (const key of this.getRegisteredCrons()) {
|
|
3452
|
+
this.removeCron(key);
|
|
3859
3453
|
}
|
|
3860
3454
|
}
|
|
3861
3455
|
clearTimers() {
|
|
@@ -3965,7 +3559,6 @@ class DiscordChannel extends BaseChannel {
|
|
|
3965
3559
|
logger.debug(`DiscordClientReady: resolving configured channel ${channelId}`);
|
|
3966
3560
|
this.resolveConfiguredChannel(channelId);
|
|
3967
3561
|
}
|
|
3968
|
-
this.initAvailability();
|
|
3969
3562
|
});
|
|
3970
3563
|
this.client.on(Events.MessageCreate, (msg) => {
|
|
3971
3564
|
if (msg.author.bot)
|
|
@@ -4134,7 +3727,6 @@ class TelegramChannel extends BaseChannel {
|
|
|
4134
3727
|
this.registerActive();
|
|
4135
3728
|
this.bot.onStart(({ info }) => {
|
|
4136
3729
|
logger.success(`Telegram ready as @${info.username}`);
|
|
4137
|
-
this.initAvailability();
|
|
4138
3730
|
});
|
|
4139
3731
|
this.bot.on("message", (ctx) => {
|
|
4140
3732
|
if (ctx.from?.isBot())
|
|
@@ -4227,8 +3819,8 @@ class TelegramChannel extends BaseChannel {
|
|
|
4227
3819
|
|
|
4228
3820
|
// src/utils/daemonClient.ts
|
|
4229
3821
|
import { connect } from "node:net";
|
|
4230
|
-
import { join as
|
|
4231
|
-
var DAEMON_SOCKET_PATH =
|
|
3822
|
+
import { join as join3 } from "node:path";
|
|
3823
|
+
var DAEMON_SOCKET_PATH = join3(config.brainboxRoot, "daemon.sock");
|
|
4232
3824
|
async function sendToDaemon(payload) {
|
|
4233
3825
|
logger.debug(`sendToDaemon: command="${payload.command}" args=${JSON.stringify(payload.args) ?? "null"}`);
|
|
4234
3826
|
let reply;
|
|
@@ -4290,7 +3882,6 @@ function exchangeOnce(payload) {
|
|
|
4290
3882
|
// src/commands/daemon.ts
|
|
4291
3883
|
import { createServer } from "node:net";
|
|
4292
3884
|
import { chmodSync, unlinkSync } from "node:fs";
|
|
4293
|
-
import { join as join5 } from "node:path";
|
|
4294
3885
|
|
|
4295
3886
|
// src/commands/daemon/commands.ts
|
|
4296
3887
|
var log8 = logger.child("daemon-cmd");
|
|
@@ -4351,63 +3942,6 @@ defineCommand({
|
|
|
4351
3942
|
}
|
|
4352
3943
|
});
|
|
4353
3944
|
|
|
4354
|
-
// src/commands/daemon/doCommand.ts
|
|
4355
|
-
defineCommand({
|
|
4356
|
-
name: "do",
|
|
4357
|
-
handler: async (args) => {
|
|
4358
|
-
const action = args?.action;
|
|
4359
|
-
const brainId = args?.brainId;
|
|
4360
|
-
logger.debug(`do handler: action="${action}" brainId="${brainId}"`);
|
|
4361
|
-
if (typeof action !== "string" || !DO_ACTIONS.includes(action)) {
|
|
4362
|
-
return {
|
|
4363
|
-
ok: false,
|
|
4364
|
-
error: `invalid action (expected one of: ${DO_ACTIONS.join(", ")})`
|
|
4365
|
-
};
|
|
4366
|
-
}
|
|
4367
|
-
if (typeof brainId !== "string" || brainId.trim().length === 0) {
|
|
4368
|
-
return { ok: false, error: "missing brainId" };
|
|
4369
|
-
}
|
|
4370
|
-
const result = BaseChannel.forceDo(brainId.trim(), action);
|
|
4371
|
-
if (!result.ok)
|
|
4372
|
-
return result;
|
|
4373
|
-
return {
|
|
4374
|
-
ok: true,
|
|
4375
|
-
result: { action, brainId: brainId.trim(), displayName: result.displayName }
|
|
4376
|
-
};
|
|
4377
|
-
}
|
|
4378
|
-
});
|
|
4379
|
-
|
|
4380
|
-
// src/commands/daemon/viewCommand.ts
|
|
4381
|
-
defineCommand({
|
|
4382
|
-
name: "view",
|
|
4383
|
-
handler: async (args) => {
|
|
4384
|
-
const thing = args?.thing;
|
|
4385
|
-
const brainId = args?.brainId;
|
|
4386
|
-
logger.debug(`view handler: thing="${thing}" brainId="${brainId}"`);
|
|
4387
|
-
if (typeof thing !== "string" || !VIEW_THINGS.includes(thing)) {
|
|
4388
|
-
return {
|
|
4389
|
-
ok: false,
|
|
4390
|
-
error: `invalid thing (expected one of: ${VIEW_THINGS.join(", ")})`
|
|
4391
|
-
};
|
|
4392
|
-
}
|
|
4393
|
-
if (typeof brainId !== "string" || brainId.trim().length === 0) {
|
|
4394
|
-
return { ok: false, error: "missing brainId" };
|
|
4395
|
-
}
|
|
4396
|
-
const result = await BaseChannel.view(brainId.trim(), thing);
|
|
4397
|
-
if (!result.ok)
|
|
4398
|
-
return result;
|
|
4399
|
-
return {
|
|
4400
|
-
ok: true,
|
|
4401
|
-
result: {
|
|
4402
|
-
thing,
|
|
4403
|
-
brainId: brainId.trim(),
|
|
4404
|
-
displayName: result.displayName,
|
|
4405
|
-
value: result.value
|
|
4406
|
-
}
|
|
4407
|
-
};
|
|
4408
|
-
}
|
|
4409
|
-
});
|
|
4410
|
-
|
|
4411
3945
|
// src/commands/daemon.ts
|
|
4412
3946
|
async function startChannels() {
|
|
4413
3947
|
const items = await brainManager.listAvailableBrain();
|
|
@@ -4442,10 +3976,7 @@ async function startChannels() {
|
|
|
4442
3976
|
return started;
|
|
4443
3977
|
}
|
|
4444
3978
|
async function daemon() {
|
|
4445
|
-
|
|
4446
|
-
logger.configure({ logDir });
|
|
4447
|
-
configureLlmLog(config.debug ? join5(logDir, "llm") : undefined);
|
|
4448
|
-
logger.debug(`daemon: boot (debug=${config.debug}, logDir=${logDir}, llmLog=${config.debug})`);
|
|
3979
|
+
logger.debug(`daemon: boot`);
|
|
4449
3980
|
const started = await startChannels();
|
|
4450
3981
|
if (started === 0) {
|
|
4451
3982
|
logger.info("No activated brains with channels. Daemon idling.");
|
|
@@ -4585,8 +4116,7 @@ async function deactivateBrain(brainId) {
|
|
|
4585
4116
|
async function createBrain(displayName, seed, options) {
|
|
4586
4117
|
logger.debug(`createBrain: name="${displayName}" seed length=${seed.length} schedule=${options.schedule}`);
|
|
4587
4118
|
const result = await Brain.create(displayName, seed);
|
|
4588
|
-
if (
|
|
4589
|
-
logger.error(`Failed to create brain "${displayName}": ${result.error}`);
|
|
4119
|
+
if (!result) {
|
|
4590
4120
|
process.exitCode = 1;
|
|
4591
4121
|
return;
|
|
4592
4122
|
}
|
|
@@ -4612,33 +4142,6 @@ async function removeBrain(brainId) {
|
|
|
4612
4142
|
}
|
|
4613
4143
|
logger.success(`Removed brain "${brain.displayName}" (${chalk2.cyan(brainId)})`);
|
|
4614
4144
|
}
|
|
4615
|
-
async function doAction(action, brainId) {
|
|
4616
|
-
if (!DO_ACTIONS.includes(action)) {
|
|
4617
|
-
logger.error(`Unknown action "${action}". Expected one of: ${DO_ACTIONS.join(", ")}`);
|
|
4618
|
-
process.exit(1);
|
|
4619
|
-
}
|
|
4620
|
-
logger.debug(`do: action=${action} brainId=${brainId}`);
|
|
4621
|
-
const response = await sendToDaemon({
|
|
4622
|
-
command: "do",
|
|
4623
|
-
args: { action, brainId }
|
|
4624
|
-
});
|
|
4625
|
-
const name = response.result?.displayName ?? brainId;
|
|
4626
|
-
logger.success(`Successfully sent ${action} for "${name}" (${brainId}).`);
|
|
4627
|
-
}
|
|
4628
|
-
async function viewThing(thing, brainId) {
|
|
4629
|
-
if (!VIEW_THINGS.includes(thing)) {
|
|
4630
|
-
logger.error(`Unknown thing "${thing}". Expected one of: ${VIEW_THINGS.join(", ")}`);
|
|
4631
|
-
process.exit(1);
|
|
4632
|
-
}
|
|
4633
|
-
logger.debug(`view: thing=${thing} brainId=${brainId}`);
|
|
4634
|
-
const response = await sendToDaemon({
|
|
4635
|
-
command: "view",
|
|
4636
|
-
args: { thing, brainId }
|
|
4637
|
-
});
|
|
4638
|
-
const name = response.result?.displayName ?? brainId;
|
|
4639
|
-
logger.info(`${thing} — "${name}" (${brainId})`);
|
|
4640
|
-
console.log(JSON.stringify(response.result?.value ?? null, null, 2));
|
|
4641
|
-
}
|
|
4642
4145
|
function register3(program) {
|
|
4643
4146
|
const cmd = registerCommand(program, {
|
|
4644
4147
|
name: "brain",
|
|
@@ -4649,8 +4152,6 @@ function register3(program) {
|
|
|
4649
4152
|
cmd.command("remove <brainId>").description("Remove a brain and its memory").action(removeBrain);
|
|
4650
4153
|
cmd.command("activate <brainId>").description("Activate a brain").action(activateBrain);
|
|
4651
4154
|
cmd.command("deactivate <brainId>").description("Deactivate a brain").action(deactivateBrain);
|
|
4652
|
-
cmd.command("do <action> <brainId>").description(`Force-run a daemon job (${DO_ACTIONS.join(" | ")}) for a live brain`).action(doAction);
|
|
4653
|
-
cmd.command("view <thing> <brainId>").description(`Inspect a live brain value (${VIEW_THINGS.join(" | ")})`).action(viewThing);
|
|
4654
4155
|
return cmd;
|
|
4655
4156
|
}
|
|
4656
4157
|
|
|
@@ -4772,9 +4273,6 @@ function RawInput({
|
|
|
4772
4273
|
onSubmit
|
|
4773
4274
|
}) {
|
|
4774
4275
|
const [value, setValue] = useState(initialValue);
|
|
4775
|
-
useEffect(() => {
|
|
4776
|
-
setValue(initialValue);
|
|
4777
|
-
}, [prompt, initialValue]);
|
|
4778
4276
|
useInput((input, key) => {
|
|
4779
4277
|
if (key.return) {
|
|
4780
4278
|
onSubmit(value);
|
|
@@ -5470,19 +4968,6 @@ function Select(props) {
|
|
|
5470
4968
|
|
|
5471
4969
|
// src/commands/onboard.tsx
|
|
5472
4970
|
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
5473
|
-
function ok(msg) {
|
|
5474
|
-
console.log(chalk3.green(`✔ ${msg}`));
|
|
5475
|
-
}
|
|
5476
|
-
function info(msg) {
|
|
5477
|
-
console.log(msg);
|
|
5478
|
-
}
|
|
5479
|
-
function show(active, node, status) {
|
|
5480
|
-
active.current.unmount();
|
|
5481
|
-
console.clear();
|
|
5482
|
-
if (status)
|
|
5483
|
-
ok(status);
|
|
5484
|
-
active.current = render3(node);
|
|
5485
|
-
}
|
|
5486
4971
|
function ProviderApp({
|
|
5487
4972
|
providers,
|
|
5488
4973
|
onDone
|
|
@@ -5554,6 +5039,7 @@ function ProviderApp({
|
|
|
5554
5039
|
const extras = PROVIDER_EXTRA_FIELDS[stage.provider] ?? [];
|
|
5555
5040
|
if (extras.length === 0) {
|
|
5556
5041
|
setProviderAuth(stage.provider, { apiKey });
|
|
5042
|
+
logger.success(`Saved ${stage.provider} to auth.yaml`);
|
|
5557
5043
|
onDone({ provider: stage.provider });
|
|
5558
5044
|
return;
|
|
5559
5045
|
}
|
|
@@ -5574,6 +5060,13 @@ function ProviderApp({
|
|
|
5574
5060
|
}, undefined, true, undefined, this);
|
|
5575
5061
|
}
|
|
5576
5062
|
const nextField = stage.fields[0];
|
|
5063
|
+
if (!nextField) {
|
|
5064
|
+
setProviderAuth(stage.provider, stage.values);
|
|
5065
|
+
logger.success(`Saved ${stage.provider} to auth.yaml`);
|
|
5066
|
+
return /* @__PURE__ */ jsxDEV5(Text5, {
|
|
5067
|
+
children: "Continuing…"
|
|
5068
|
+
}, undefined, false, undefined, this);
|
|
5069
|
+
}
|
|
5577
5070
|
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
5578
5071
|
flexDirection: "column",
|
|
5579
5072
|
children: [
|
|
@@ -5599,11 +5092,6 @@ function ProviderApp({
|
|
|
5599
5092
|
const values = { ...stage.values };
|
|
5600
5093
|
if (value)
|
|
5601
5094
|
values[nextField] = value;
|
|
5602
|
-
if (remaining.length === 0) {
|
|
5603
|
-
setProviderAuth(stage.provider, values);
|
|
5604
|
-
onDone({ provider: stage.provider });
|
|
5605
|
-
return;
|
|
5606
|
-
}
|
|
5607
5095
|
setStage({
|
|
5608
5096
|
kind: "extras",
|
|
5609
5097
|
provider: stage.provider,
|
|
@@ -5632,7 +5120,7 @@ function ModelApp2({
|
|
|
5632
5120
|
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5633
5121
|
dimColor: true,
|
|
5634
5122
|
children: [
|
|
5635
|
-
"
|
|
5123
|
+
"e.g. ",
|
|
5636
5124
|
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5637
5125
|
color: "cyan",
|
|
5638
5126
|
children: [
|
|
@@ -5640,7 +5128,7 @@ function ModelApp2({
|
|
|
5640
5128
|
"/"
|
|
5641
5129
|
]
|
|
5642
5130
|
}, undefined, true, undefined, this),
|
|
5643
|
-
"model — fine-tune later with ",
|
|
5131
|
+
"model-name — fine-tune later with ",
|
|
5644
5132
|
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5645
5133
|
color: "cyan",
|
|
5646
5134
|
children: "brainbox model"
|
|
@@ -5655,15 +5143,14 @@ function ModelApp2({
|
|
|
5655
5143
|
setError("Model cannot be empty");
|
|
5656
5144
|
return;
|
|
5657
5145
|
}
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
if (full === prefix) {
|
|
5661
|
-
setError("Model cannot be empty");
|
|
5146
|
+
if (!value.startsWith(`${provider}/`)) {
|
|
5147
|
+
setError(`Must start with "${provider}/"`);
|
|
5662
5148
|
return;
|
|
5663
5149
|
}
|
|
5664
|
-
setModelSlot("identity",
|
|
5665
|
-
setModelSlot("conversation",
|
|
5666
|
-
|
|
5150
|
+
setModelSlot("identity", value);
|
|
5151
|
+
setModelSlot("conversation", value);
|
|
5152
|
+
logger.success(`Set identity + conversation model to ${value}`);
|
|
5153
|
+
onDone();
|
|
5667
5154
|
}
|
|
5668
5155
|
}, undefined, false, undefined, this),
|
|
5669
5156
|
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
@@ -5699,6 +5186,7 @@ function SuperMemoryApp({
|
|
|
5699
5186
|
return;
|
|
5700
5187
|
}
|
|
5701
5188
|
setSupermemoryKey(key);
|
|
5189
|
+
logger.success("Saved supermemory key to brainbox.yaml");
|
|
5702
5190
|
onDone();
|
|
5703
5191
|
}
|
|
5704
5192
|
}, undefined, false, undefined, this),
|
|
@@ -5714,7 +5202,6 @@ function BrainApp({
|
|
|
5714
5202
|
}) {
|
|
5715
5203
|
const [stage, setStage] = useState5({ kind: "name" });
|
|
5716
5204
|
const [error, setError] = useState5(null);
|
|
5717
|
-
const [busy, setBusy] = useState5(false);
|
|
5718
5205
|
if (stage.kind === "name") {
|
|
5719
5206
|
return /* @__PURE__ */ jsxDEV5(Box4, {
|
|
5720
5207
|
flexDirection: "column",
|
|
@@ -5766,14 +5253,12 @@ function BrainApp({
|
|
|
5766
5253
|
dimColor: true,
|
|
5767
5254
|
children: "One sentence about who they are. The model will expand it."
|
|
5768
5255
|
}, undefined, false, undefined, this),
|
|
5769
|
-
|
|
5770
|
-
dimColor: true,
|
|
5771
|
-
children: "Creating brain… (This can take few minutes depending on the model's response speed)"
|
|
5772
|
-
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(TextInput, {
|
|
5256
|
+
/* @__PURE__ */ jsxDEV5(TextInput, {
|
|
5773
5257
|
prompt: "seed> ",
|
|
5774
|
-
onSubmit: (raw) => {
|
|
5258
|
+
onSubmit: async (raw) => {
|
|
5775
5259
|
const seed = raw.trim();
|
|
5776
5260
|
if (seed === "skip") {
|
|
5261
|
+
logger.info("Skipped brain creation.");
|
|
5777
5262
|
onDone({ brainId: "", displayName: stage.displayName });
|
|
5778
5263
|
return;
|
|
5779
5264
|
}
|
|
@@ -5781,19 +5266,13 @@ function BrainApp({
|
|
|
5781
5266
|
setError("Seed cannot be empty (or type 'skip')");
|
|
5782
5267
|
return;
|
|
5783
5268
|
}
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
}
|
|
5792
|
-
onDone({
|
|
5793
|
-
brainId: result.brainId,
|
|
5794
|
-
displayName: stage.displayName
|
|
5795
|
-
});
|
|
5796
|
-
});
|
|
5269
|
+
const result = await Brain.create(stage.displayName, seed);
|
|
5270
|
+
if (!result) {
|
|
5271
|
+
setError("Brain creation failed (check logs above, or type 'skip')");
|
|
5272
|
+
return;
|
|
5273
|
+
}
|
|
5274
|
+
logger.success(`Created brain "${stage.displayName}" (${chalk3.cyan(result.brainId)})`);
|
|
5275
|
+
onDone({ brainId: result.brainId, displayName: stage.displayName });
|
|
5797
5276
|
}
|
|
5798
5277
|
}, undefined, false, undefined, this),
|
|
5799
5278
|
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
@@ -5834,7 +5313,8 @@ function ChannelApp({
|
|
|
5834
5313
|
items: ["discord", "telegram", "skip"],
|
|
5835
5314
|
onSelect: (v) => {
|
|
5836
5315
|
if (v === "skip") {
|
|
5837
|
-
|
|
5316
|
+
logger.info("Skipped channel setup.");
|
|
5317
|
+
onDone();
|
|
5838
5318
|
return;
|
|
5839
5319
|
}
|
|
5840
5320
|
setError(null);
|
|
@@ -5893,41 +5373,37 @@ function ChannelApp({
|
|
|
5893
5373
|
}, undefined, true, undefined, this),
|
|
5894
5374
|
/* @__PURE__ */ jsxDEV5(TextInput, {
|
|
5895
5375
|
prompt: `${stage.kind_ === "discord" ? "channelId" : "chatId"}> `,
|
|
5896
|
-
onSubmit: (raw) => {
|
|
5376
|
+
onSubmit: async (raw) => {
|
|
5897
5377
|
const target = raw.trim();
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5378
|
+
const existing = await brainManager.loadBrain(brainId);
|
|
5379
|
+
if (!existing) {
|
|
5380
|
+
setError(`Brain ${brainId} no longer exists`);
|
|
5381
|
+
return;
|
|
5382
|
+
}
|
|
5383
|
+
let updated;
|
|
5384
|
+
if (stage.kind_ === "discord") {
|
|
5385
|
+
updated = {
|
|
5386
|
+
...existing,
|
|
5387
|
+
channel: "discord",
|
|
5388
|
+
discord: { token: stage.token, channelId: target || undefined },
|
|
5389
|
+
activated: true
|
|
5390
|
+
};
|
|
5391
|
+
} else {
|
|
5392
|
+
const chatId = target ? Number(target) : undefined;
|
|
5393
|
+
if (target && Number.isNaN(chatId)) {
|
|
5394
|
+
setError("chatId must be a number");
|
|
5902
5395
|
return;
|
|
5903
5396
|
}
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
};
|
|
5915
|
-
} else {
|
|
5916
|
-
const chatId = target ? Number(target) : undefined;
|
|
5917
|
-
if (target && Number.isNaN(chatId)) {
|
|
5918
|
-
setError("chatId must be a number");
|
|
5919
|
-
return;
|
|
5920
|
-
}
|
|
5921
|
-
updated = {
|
|
5922
|
-
...existing,
|
|
5923
|
-
channel: "telegram",
|
|
5924
|
-
telegram: { token: stage.token, chatId },
|
|
5925
|
-
activated: true
|
|
5926
|
-
};
|
|
5927
|
-
}
|
|
5928
|
-
await brainManager.saveBrain(brainId, updated);
|
|
5929
|
-
onDone(`Bound ${displayName} → ${stage.kind_}${target ? ` (${target})` : " (pairing mode)"}`);
|
|
5930
|
-
})();
|
|
5397
|
+
updated = {
|
|
5398
|
+
...existing,
|
|
5399
|
+
channel: "telegram",
|
|
5400
|
+
telegram: { token: stage.token, chatId },
|
|
5401
|
+
activated: true
|
|
5402
|
+
};
|
|
5403
|
+
}
|
|
5404
|
+
await brainManager.saveBrain(brainId, updated);
|
|
5405
|
+
logger.success(`Bound ${displayName} → ${stage.kind_}${target ? ` (${target})` : " (pairing mode)"}`);
|
|
5406
|
+
onDone();
|
|
5931
5407
|
}
|
|
5932
5408
|
}, undefined, false, undefined, this),
|
|
5933
5409
|
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
@@ -5938,52 +5414,46 @@ function ChannelApp({
|
|
|
5938
5414
|
}, undefined, true, undefined, this);
|
|
5939
5415
|
}
|
|
5940
5416
|
async function runOnboard() {
|
|
5941
|
-
|
|
5942
|
-
info(`Welcome — let's get ${chalk3.bold("brainbox")} ready.`);
|
|
5417
|
+
logger.info(`Welcome — let's get ${chalk3.bold("brainbox")} ready.`);
|
|
5943
5418
|
const providers = listProviderNames().slice().sort();
|
|
5944
5419
|
const { promise, resolve: resolve2 } = Promise.withResolvers();
|
|
5945
|
-
|
|
5946
|
-
active.current = render3(/* @__PURE__ */ jsxDEV5(ProviderApp, {
|
|
5420
|
+
let active = render3(/* @__PURE__ */ jsxDEV5(ProviderApp, {
|
|
5947
5421
|
providers,
|
|
5948
5422
|
onDone: (p) => {
|
|
5949
|
-
|
|
5423
|
+
active.unmount();
|
|
5424
|
+
active = render3(/* @__PURE__ */ jsxDEV5(ModelApp2, {
|
|
5950
5425
|
provider: p.provider,
|
|
5951
|
-
onDone: (
|
|
5952
|
-
|
|
5426
|
+
onDone: () => {
|
|
5427
|
+
active.unmount();
|
|
5428
|
+
active = render3(/* @__PURE__ */ jsxDEV5(SuperMemoryApp, {
|
|
5953
5429
|
onDone: () => {
|
|
5954
|
-
|
|
5430
|
+
active.unmount();
|
|
5431
|
+
active = render3(/* @__PURE__ */ jsxDEV5(BrainApp, {
|
|
5955
5432
|
onDone: (b) => {
|
|
5433
|
+
active.unmount();
|
|
5956
5434
|
if (!b.brainId) {
|
|
5957
|
-
active.current.unmount();
|
|
5958
|
-
console.clear();
|
|
5959
|
-
info("Skipped brain creation.");
|
|
5960
5435
|
resolve2();
|
|
5961
5436
|
return;
|
|
5962
5437
|
}
|
|
5963
|
-
|
|
5438
|
+
active = render3(/* @__PURE__ */ jsxDEV5(ChannelApp, {
|
|
5964
5439
|
brainId: b.brainId,
|
|
5965
5440
|
displayName: b.displayName,
|
|
5966
|
-
onDone: (
|
|
5967
|
-
active.
|
|
5968
|
-
console.clear();
|
|
5969
|
-
if (status.startsWith("Skipped"))
|
|
5970
|
-
info(status);
|
|
5971
|
-
else
|
|
5972
|
-
ok(status);
|
|
5441
|
+
onDone: () => {
|
|
5442
|
+
active.unmount();
|
|
5973
5443
|
resolve2();
|
|
5974
5444
|
}
|
|
5975
|
-
}, undefined, false, undefined, this)
|
|
5445
|
+
}, undefined, false, undefined, this));
|
|
5976
5446
|
}
|
|
5977
|
-
}, undefined, false, undefined, this)
|
|
5447
|
+
}, undefined, false, undefined, this));
|
|
5978
5448
|
}
|
|
5979
|
-
}, undefined, false, undefined, this)
|
|
5449
|
+
}, undefined, false, undefined, this));
|
|
5980
5450
|
}
|
|
5981
|
-
}, undefined, false, undefined, this)
|
|
5451
|
+
}, undefined, false, undefined, this));
|
|
5982
5452
|
}
|
|
5983
5453
|
}, undefined, false, undefined, this));
|
|
5984
5454
|
await promise;
|
|
5985
|
-
|
|
5986
|
-
info(`Run ${chalk3.cyan("brainbox daemon")} to bring it online.`);
|
|
5455
|
+
logger.success("Onboarding complete.");
|
|
5456
|
+
logger.info(`Run ${chalk3.cyan("brainbox daemon")} to bring it online.`);
|
|
5987
5457
|
}
|
|
5988
5458
|
function register8(program) {
|
|
5989
5459
|
return registerCommand(program, {
|
|
@@ -5995,12 +5465,12 @@ function register8(program) {
|
|
|
5995
5465
|
|
|
5996
5466
|
// src/index.ts
|
|
5997
5467
|
var __filename3 = fileURLToPath2(import.meta.url);
|
|
5998
|
-
var __dirname3 =
|
|
5468
|
+
var __dirname3 = dirname4(__filename3);
|
|
5999
5469
|
logger.configure({ level: config.debug ? "debug" : "info" });
|
|
6000
5470
|
logger.debug(`brainbox starting (debug=${config.debug}, root=${config.brainboxRoot})`);
|
|
6001
5471
|
function getVersion() {
|
|
6002
5472
|
try {
|
|
6003
|
-
const pkgPath =
|
|
5473
|
+
const pkgPath = join4(__dirname3, "..", "package.json");
|
|
6004
5474
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
6005
5475
|
return pkg.version ?? "0.0.0";
|
|
6006
5476
|
} catch {
|