@p-sw/brainbox 0.1.2-alpha.13 → 0.1.2-alpha.3
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 +426 -926
- 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)
|
|
@@ -597,42 +443,6 @@ function readAuthString(auth, key, envName) {
|
|
|
597
443
|
}
|
|
598
444
|
|
|
599
445
|
class LLMExecutor {
|
|
600
|
-
async chatWithToolExecution(model, options) {
|
|
601
|
-
const messages = options.messages.slice();
|
|
602
|
-
const maxSteps = options.maxSteps ?? 20;
|
|
603
|
-
let last;
|
|
604
|
-
for (let step = 0;step < maxSteps; step += 1) {
|
|
605
|
-
log2.debug(`chatWithToolExecution: step ${step + 1}/${maxSteps} msgs=${messages.length}`);
|
|
606
|
-
last = await this.chatWithTools(model, {
|
|
607
|
-
instruction: options.instruction,
|
|
608
|
-
messages,
|
|
609
|
-
tools: options.tools,
|
|
610
|
-
caller: options.caller,
|
|
611
|
-
reasoningEffort: options.reasoningEffort,
|
|
612
|
-
parallelToolCalls: options.parallelToolCalls,
|
|
613
|
-
toolChoice: options.toolChoice
|
|
614
|
-
});
|
|
615
|
-
const toolCalls = last.message.toolCalls ?? [];
|
|
616
|
-
log2.debug(`chatWithToolExecution: step ${step + 1} toolCalls=${toolCalls.length}`);
|
|
617
|
-
if (toolCalls.length === 0)
|
|
618
|
-
return last;
|
|
619
|
-
messages.push({
|
|
620
|
-
role: "assistant",
|
|
621
|
-
content: last.message.content,
|
|
622
|
-
toolCalls
|
|
623
|
-
});
|
|
624
|
-
for (const call of toolCalls) {
|
|
625
|
-
const content = await options.executeTool(call);
|
|
626
|
-
messages.push({
|
|
627
|
-
role: "tool",
|
|
628
|
-
toolCallId: call.id,
|
|
629
|
-
content
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
log2.warn(`chatWithToolExecution: reached maxSteps (${maxSteps}) without final reply`);
|
|
634
|
-
return last;
|
|
635
|
-
}
|
|
636
446
|
static providers = [];
|
|
637
447
|
static registerProvider(p) {
|
|
638
448
|
LLMExecutor.providers.push(p);
|
|
@@ -699,19 +509,6 @@ class LLMExecutor {
|
|
|
699
509
|
};
|
|
700
510
|
}
|
|
701
511
|
}
|
|
702
|
-
function resolveLlmCaller(options, fallback = "llm") {
|
|
703
|
-
return options.caller ?? options.jsonSchemaName ?? fallback;
|
|
704
|
-
}
|
|
705
|
-
function logLlmWire(caller, request, response) {
|
|
706
|
-
if (!isLlmLogEnabled())
|
|
707
|
-
return;
|
|
708
|
-
writeLlmExchange(caller, `======== REQUEST ========
|
|
709
|
-
${request}
|
|
710
|
-
|
|
711
|
-
======== RESPONSE ========
|
|
712
|
-
${response}
|
|
713
|
-
`);
|
|
714
|
-
}
|
|
715
512
|
function listProviderNames() {
|
|
716
513
|
return LLMExecutor.listProviderNames();
|
|
717
514
|
}
|
|
@@ -749,7 +546,7 @@ function fromOrChoice(choice) {
|
|
|
749
546
|
}));
|
|
750
547
|
return {
|
|
751
548
|
message: {
|
|
752
|
-
content: typeof msg.content === "string" ?
|
|
549
|
+
content: typeof msg.content === "string" ? msg.content : undefined,
|
|
753
550
|
toolCalls
|
|
754
551
|
}
|
|
755
552
|
};
|
|
@@ -780,54 +577,53 @@ class OpenRouterExecutor extends LLMExecutor {
|
|
|
780
577
|
async call(model, options) {
|
|
781
578
|
const jsonMode = "jsonSchemaName" in options;
|
|
782
579
|
log3.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
const
|
|
805
|
-
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;
|
|
806
602
|
if (!content) {
|
|
807
603
|
log3.debug(`call: empty content in choice 0`);
|
|
808
604
|
throw new Error("Empty response from model");
|
|
809
605
|
}
|
|
810
606
|
log3.debug(`call: response ${content.length} chars`);
|
|
811
|
-
return jsonMode ?
|
|
607
|
+
return jsonMode ? JSON.parse(content) : content;
|
|
812
608
|
}
|
|
813
609
|
async chatWithTools(model, options) {
|
|
814
610
|
log3.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
+
});
|
|
831
627
|
const choice = result.choices[0];
|
|
832
628
|
if (!choice) {
|
|
833
629
|
log3.debug(`chatWithTools: no choice in response`);
|
|
@@ -867,7 +663,7 @@ function fromChoice(choice) {
|
|
|
867
663
|
}));
|
|
868
664
|
return {
|
|
869
665
|
message: {
|
|
870
|
-
content: typeof msg.content === "string" ?
|
|
666
|
+
content: typeof msg.content === "string" ? msg.content : undefined,
|
|
871
667
|
toolCalls
|
|
872
668
|
}
|
|
873
669
|
};
|
|
@@ -902,7 +698,6 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
902
698
|
chatPath;
|
|
903
699
|
noBearerPrefix;
|
|
904
700
|
reasoningEffortInQuery;
|
|
905
|
-
supportsResponseFormat;
|
|
906
701
|
constructor(opts) {
|
|
907
702
|
super();
|
|
908
703
|
this.providerName = opts.providerName;
|
|
@@ -912,7 +707,6 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
912
707
|
this.chatPath = opts.chatPath ?? "/chat/completions";
|
|
913
708
|
this.noBearerPrefix = opts.noBearerPrefix ?? false;
|
|
914
709
|
this.reasoningEffortInQuery = opts.reasoningEffortInQuery ?? false;
|
|
915
|
-
this.supportsResponseFormat = opts.supportsResponseFormat ?? true;
|
|
916
710
|
this.models = {
|
|
917
711
|
conversation: opts.conversationModel,
|
|
918
712
|
identity: opts.identityModel
|
|
@@ -943,7 +737,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
943
737
|
}
|
|
944
738
|
return url.toString();
|
|
945
739
|
}
|
|
946
|
-
async sendRequest(body, reasoningEffort
|
|
740
|
+
async sendRequest(body, reasoningEffort) {
|
|
947
741
|
const modelName = body["model"];
|
|
948
742
|
const modelStr = typeof modelName === "string" ? modelName : "";
|
|
949
743
|
const url = this.buildRequestUrl(modelStr, reasoningEffort);
|
|
@@ -953,40 +747,24 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
953
747
|
...authHeader,
|
|
954
748
|
...this.defaultHeaders
|
|
955
749
|
};
|
|
956
|
-
const requestRaw = JSON.stringify(body);
|
|
957
750
|
const res = await fetch(url, {
|
|
958
751
|
method: "POST",
|
|
959
752
|
headers,
|
|
960
|
-
body:
|
|
753
|
+
body: JSON.stringify(body)
|
|
961
754
|
});
|
|
962
|
-
const responseRaw = await res.text().catch(() => "");
|
|
963
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
964
755
|
if (!res.ok) {
|
|
965
|
-
|
|
756
|
+
const text = await res.text().catch(() => "");
|
|
757
|
+
log4.error(`${this.providerName}: HTTP ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
966
758
|
throw new Error(`${this.providerName} request failed: ${res.status} ${res.statusText}`);
|
|
967
759
|
}
|
|
968
|
-
|
|
969
|
-
try {
|
|
970
|
-
data = JSON.parse(responseRaw);
|
|
971
|
-
} catch {
|
|
972
|
-
throw new Error(`${this.providerName}: invalid JSON response`);
|
|
973
|
-
}
|
|
760
|
+
const data = await res.json();
|
|
974
761
|
if (data.error) {
|
|
975
762
|
log4.error(`${this.providerName}: API error ${data.error.type ?? ""} ${data.error.message ?? ""}`);
|
|
976
763
|
throw new Error(`${this.providerName} API error: ${data.error.message ?? "unknown"}`);
|
|
977
764
|
}
|
|
978
|
-
const baseCode = data.base_resp?.status_code;
|
|
979
|
-
if (typeof baseCode === "number" && baseCode !== 0) {
|
|
980
|
-
const msg = data.base_resp?.status_msg?.trim() || `status_code ${baseCode}`;
|
|
981
|
-
log4.error(`${this.providerName}: base_resp ${baseCode} ${msg}`);
|
|
982
|
-
throw new Error(`${this.providerName} API error: ${msg}`);
|
|
983
|
-
}
|
|
984
765
|
return data;
|
|
985
766
|
}
|
|
986
767
|
async call(model, options) {
|
|
987
|
-
if ("jsonSchemaName" in options && !this.supportsResponseFormat) {
|
|
988
|
-
return this.callJsonViaTool(model, options);
|
|
989
|
-
}
|
|
990
768
|
const jsonMode = "jsonSchemaName" in options;
|
|
991
769
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
992
770
|
log4.debug(`call: provider=${this.providerName} model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
@@ -999,31 +777,14 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
999
777
|
responseFormat: jsonMode ? buildResponseFormat(options.jsonSchemaName, options.jsonSchema) : undefined,
|
|
1000
778
|
reasoningEffort: reasoning
|
|
1001
779
|
});
|
|
1002
|
-
const data = await this.sendRequest(body, options.reasoningEffort
|
|
1003
|
-
const
|
|
1004
|
-
const raw = choice?.message?.content;
|
|
1005
|
-
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;
|
|
1006
782
|
if (!content) {
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
log4.debug(`call: empty content in choice 0 finish_reason=${finish} reasoning_len=${reasoningLen} rawType=${raw === null ? "null" : typeof raw}`);
|
|
1010
|
-
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");
|
|
1011
785
|
}
|
|
1012
786
|
log4.debug(`call: response ${content.length} chars`);
|
|
1013
|
-
return jsonMode ?
|
|
1014
|
-
}
|
|
1015
|
-
async callJsonViaTool(model, options) {
|
|
1016
|
-
const { toolName, tool, instruction } = buildStructuredJsonRequest(options);
|
|
1017
|
-
log4.debug(`callJsonViaTool: provider=${this.providerName} model=${model} tool=${toolName}`);
|
|
1018
|
-
const choice = await this.chatWithTools(model, {
|
|
1019
|
-
caller: options.caller ?? options.jsonSchemaName,
|
|
1020
|
-
instruction,
|
|
1021
|
-
messages: [{ role: "user", content: options.message }],
|
|
1022
|
-
tools: [tool],
|
|
1023
|
-
reasoningEffort: "none",
|
|
1024
|
-
parallelToolCalls: false
|
|
1025
|
-
});
|
|
1026
|
-
return parseStructuredJsonResult(choice, toolName);
|
|
787
|
+
return jsonMode ? JSON.parse(content) : content;
|
|
1027
788
|
}
|
|
1028
789
|
async chatWithTools(model, options) {
|
|
1029
790
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
@@ -1038,7 +799,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
1038
799
|
parallelToolCalls: options.parallelToolCalls ?? false,
|
|
1039
800
|
reasoningEffort: reasoning
|
|
1040
801
|
});
|
|
1041
|
-
const data = await this.sendRequest(body, options.reasoningEffort
|
|
802
|
+
const data = await this.sendRequest(body, options.reasoningEffort);
|
|
1042
803
|
const choice = data.choices?.[0];
|
|
1043
804
|
if (!choice) {
|
|
1044
805
|
log4.debug(`chatWithTools: no choice in response`);
|
|
@@ -1338,40 +1099,15 @@ class ZenMuxExecutor extends OpenAICompatibleExecutor {
|
|
|
1338
1099
|
}
|
|
1339
1100
|
|
|
1340
1101
|
// src/provider/providers/minimax.ts
|
|
1341
|
-
class
|
|
1342
|
-
buildBody(opts) {
|
|
1343
|
-
const jsonMode = opts.responseFormat !== undefined;
|
|
1344
|
-
const body = super.buildBody(opts);
|
|
1345
|
-
delete body["reasoning_effort"];
|
|
1346
|
-
delete body["response_format"];
|
|
1347
|
-
delete body["parallel_tool_calls"];
|
|
1348
|
-
body["reasoning_split"] = true;
|
|
1349
|
-
body["max_completion_tokens"] = 16384;
|
|
1350
|
-
const wantThink = !jsonMode && opts.reasoningEffort !== undefined && opts.reasoningEffort !== "none";
|
|
1351
|
-
body["thinking"] = wantThink ? { type: "adaptive" } : { type: "disabled" };
|
|
1352
|
-
return body;
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
function minimaxOpts(providerName, baseURL, opts) {
|
|
1356
|
-
return {
|
|
1357
|
-
providerName,
|
|
1358
|
-
baseURL,
|
|
1359
|
-
apiKey: opts.apiKey,
|
|
1360
|
-
conversationModel: opts.conversationModel,
|
|
1361
|
-
identityModel: opts.identityModel,
|
|
1362
|
-
supportsResponseFormat: false
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
class MiniMaxExecutor extends MiniMaxBase {
|
|
1367
|
-
constructor(opts) {
|
|
1368
|
-
super(minimaxOpts("minimax", "https://api.minimax.io/v1", opts));
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
class MiniMaxCnExecutor extends MiniMaxBase {
|
|
1102
|
+
class MiniMaxExecutor extends OpenAICompatibleExecutor {
|
|
1373
1103
|
constructor(opts) {
|
|
1374
|
-
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
|
+
});
|
|
1375
1111
|
}
|
|
1376
1112
|
}
|
|
1377
1113
|
|
|
@@ -1435,8 +1171,7 @@ class LmStudioExecutor extends OpenAICompatibleExecutor {
|
|
|
1435
1171
|
baseURL: "http://127.0.0.1:1234/v1",
|
|
1436
1172
|
apiKey: opts.apiKey || "lm-studio",
|
|
1437
1173
|
conversationModel: opts.conversationModel,
|
|
1438
|
-
identityModel: opts.identityModel
|
|
1439
|
-
supportsResponseFormat: false
|
|
1174
|
+
identityModel: opts.identityModel
|
|
1440
1175
|
});
|
|
1441
1176
|
}
|
|
1442
1177
|
}
|
|
@@ -1449,8 +1184,7 @@ class OllamaExecutor extends OpenAICompatibleExecutor {
|
|
|
1449
1184
|
baseURL: "http://127.0.0.1:11434/v1",
|
|
1450
1185
|
apiKey: opts.apiKey || "ollama",
|
|
1451
1186
|
conversationModel: opts.conversationModel,
|
|
1452
|
-
identityModel: opts.identityModel
|
|
1453
|
-
supportsResponseFormat: false
|
|
1187
|
+
identityModel: opts.identityModel
|
|
1454
1188
|
});
|
|
1455
1189
|
}
|
|
1456
1190
|
}
|
|
@@ -1476,8 +1210,7 @@ class LlamaCppExecutor extends OpenAICompatibleExecutor {
|
|
|
1476
1210
|
baseURL: "http://127.0.0.1:8080/v1",
|
|
1477
1211
|
apiKey: opts.apiKey || "llama.cpp",
|
|
1478
1212
|
conversationModel: opts.conversationModel,
|
|
1479
|
-
identityModel: opts.identityModel
|
|
1480
|
-
supportsResponseFormat: false
|
|
1213
|
+
identityModel: opts.identityModel
|
|
1481
1214
|
});
|
|
1482
1215
|
}
|
|
1483
1216
|
}
|
|
@@ -1527,17 +1260,7 @@ class CloudflareGatewayExecutor extends OpenAICompatibleExecutor {
|
|
|
1527
1260
|
// src/provider/providers/cloudflare_workers.ts
|
|
1528
1261
|
function toCloudflareMessage(m) {
|
|
1529
1262
|
if (m.role === "assistant") {
|
|
1530
|
-
return {
|
|
1531
|
-
role: "assistant",
|
|
1532
|
-
content: m.content ?? "",
|
|
1533
|
-
tool_calls: m.toolCalls?.map((c) => {
|
|
1534
|
-
let args = c.function.arguments;
|
|
1535
|
-
try {
|
|
1536
|
-
args = JSON.parse(c.function.arguments);
|
|
1537
|
-
} catch {}
|
|
1538
|
-
return { id: c.id, name: c.function.name, arguments: args };
|
|
1539
|
-
})
|
|
1540
|
-
};
|
|
1263
|
+
return { role: "assistant", content: m.content ?? "" };
|
|
1541
1264
|
}
|
|
1542
1265
|
if (m.role === "tool") {
|
|
1543
1266
|
return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
|
|
@@ -1560,23 +1283,21 @@ class CloudflareWorkersExecutor extends LLMExecutor {
|
|
|
1560
1283
|
const accountId = readAuthString(opts.auth, "accountId", "CLOUDFLARE_ACCOUNT_ID");
|
|
1561
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";
|
|
1562
1285
|
}
|
|
1563
|
-
async run(model, body
|
|
1286
|
+
async run(model, body) {
|
|
1564
1287
|
const url = `${this.baseURL}/${encodeURIComponent(model)}`;
|
|
1565
|
-
const requestRaw = JSON.stringify(body);
|
|
1566
1288
|
const res = await fetch(url, {
|
|
1567
1289
|
method: "POST",
|
|
1568
1290
|
headers: {
|
|
1569
1291
|
Authorization: `Bearer ${this.apiKey}`,
|
|
1570
1292
|
"Content-Type": "application/json"
|
|
1571
1293
|
},
|
|
1572
|
-
body:
|
|
1294
|
+
body: JSON.stringify(body)
|
|
1573
1295
|
});
|
|
1574
|
-
const responseRaw = await res.text().catch(() => "");
|
|
1575
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
1576
1296
|
if (!res.ok) {
|
|
1577
|
-
|
|
1297
|
+
const text = await res.text().catch(() => "");
|
|
1298
|
+
throw new Error(`cloudflare-workers request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
1578
1299
|
}
|
|
1579
|
-
return
|
|
1300
|
+
return await res.json();
|
|
1580
1301
|
}
|
|
1581
1302
|
async call(model, options) {
|
|
1582
1303
|
const jsonMode = "jsonSchemaName" in options;
|
|
@@ -1597,15 +1318,15 @@ class CloudflareWorkersExecutor extends LLMExecutor {
|
|
|
1597
1318
|
if (reasoning !== "none") {
|
|
1598
1319
|
body["reasoning_effort"] = reasoning;
|
|
1599
1320
|
}
|
|
1600
|
-
const data = await this.run(model, body
|
|
1321
|
+
const data = await this.run(model, body);
|
|
1601
1322
|
if (data.errors && data.errors.length > 0) {
|
|
1602
1323
|
throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
|
|
1603
1324
|
}
|
|
1604
|
-
const content =
|
|
1325
|
+
const content = data.result?.response ?? "";
|
|
1605
1326
|
if (!content) {
|
|
1606
1327
|
throw new Error("Empty response from model");
|
|
1607
1328
|
}
|
|
1608
|
-
return jsonMode ?
|
|
1329
|
+
return jsonMode ? JSON.parse(content) : content;
|
|
1609
1330
|
}
|
|
1610
1331
|
async chatWithTools(model, options) {
|
|
1611
1332
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
@@ -1626,11 +1347,11 @@ class CloudflareWorkersExecutor extends LLMExecutor {
|
|
|
1626
1347
|
if (reasoning !== "none") {
|
|
1627
1348
|
body["reasoning_effort"] = reasoning;
|
|
1628
1349
|
}
|
|
1629
|
-
const data = await this.run(model, body
|
|
1350
|
+
const data = await this.run(model, body);
|
|
1630
1351
|
if (data.errors && data.errors.length > 0) {
|
|
1631
1352
|
throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
|
|
1632
1353
|
}
|
|
1633
|
-
const content =
|
|
1354
|
+
const content = data.result?.response ?? "";
|
|
1634
1355
|
const toolCalls = data.result?.tool_calls?.map((c, idx) => ({
|
|
1635
1356
|
id: `call_${idx}`,
|
|
1636
1357
|
function: {
|
|
@@ -1668,8 +1389,7 @@ class AzureOpenAIExecutor extends OpenAICompatibleExecutor {
|
|
|
1668
1389
|
super({
|
|
1669
1390
|
providerName: "azure-openai",
|
|
1670
1391
|
baseURL: `https://${resource || "__resource__"}.openai.azure.com/openai/deployments`,
|
|
1671
|
-
apiKey:
|
|
1672
|
-
defaultHeaders: { "api-key": opts.apiKey },
|
|
1392
|
+
apiKey: opts.apiKey,
|
|
1673
1393
|
conversationModel: opts.conversationModel,
|
|
1674
1394
|
identityModel: opts.identityModel
|
|
1675
1395
|
});
|
|
@@ -1689,8 +1409,7 @@ class AzureCognitiveExecutor extends OpenAICompatibleExecutor {
|
|
|
1689
1409
|
super({
|
|
1690
1410
|
providerName: "azure-cognitive",
|
|
1691
1411
|
baseURL: `https://${resource || "__resource__"}.cognitiveservices.azure.com/openai/deployments`,
|
|
1692
|
-
apiKey:
|
|
1693
|
-
defaultHeaders: { "api-key": opts.apiKey },
|
|
1412
|
+
apiKey: opts.apiKey,
|
|
1694
1413
|
conversationModel: opts.conversationModel,
|
|
1695
1414
|
identityModel: opts.identityModel
|
|
1696
1415
|
});
|
|
@@ -1716,17 +1435,17 @@ var AnthropicAuthSchema = z4.object({
|
|
|
1716
1435
|
apiVersion: z4.string().optional()
|
|
1717
1436
|
}).loose();
|
|
1718
1437
|
function toAnthropicMessages(messages) {
|
|
1719
|
-
let
|
|
1720
|
-
const
|
|
1438
|
+
let system;
|
|
1439
|
+
const msgs = [];
|
|
1721
1440
|
for (const m of messages) {
|
|
1722
1441
|
if (m.role === "system") {
|
|
1723
|
-
|
|
1442
|
+
system = (system ? system + `
|
|
1724
1443
|
|
|
1725
1444
|
` : "") + m.content;
|
|
1726
1445
|
continue;
|
|
1727
1446
|
}
|
|
1728
1447
|
if (m.role === "user") {
|
|
1729
|
-
|
|
1448
|
+
msgs.push({ role: "user", content: m.content });
|
|
1730
1449
|
continue;
|
|
1731
1450
|
}
|
|
1732
1451
|
if (m.role === "assistant") {
|
|
@@ -1749,11 +1468,11 @@ function toAnthropicMessages(messages) {
|
|
|
1749
1468
|
});
|
|
1750
1469
|
}
|
|
1751
1470
|
}
|
|
1752
|
-
|
|
1471
|
+
msgs.push({ role: "assistant", content: blocks });
|
|
1753
1472
|
continue;
|
|
1754
1473
|
}
|
|
1755
1474
|
if (m.role === "tool") {
|
|
1756
|
-
|
|
1475
|
+
msgs.push({
|
|
1757
1476
|
role: "user",
|
|
1758
1477
|
content: [
|
|
1759
1478
|
{
|
|
@@ -1765,7 +1484,7 @@ function toAnthropicMessages(messages) {
|
|
|
1765
1484
|
});
|
|
1766
1485
|
}
|
|
1767
1486
|
}
|
|
1768
|
-
return { system
|
|
1487
|
+
return { system, msgs };
|
|
1769
1488
|
}
|
|
1770
1489
|
function toAnthropicTool(t) {
|
|
1771
1490
|
return {
|
|
@@ -1793,8 +1512,7 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1793
1512
|
this.baseURL = (extra.baseURL ?? readAuthString(opts.auth, "baseURL", "ANTHROPIC_BASE_URL") ?? "https://api.anthropic.com").replace(/\/v1\/?$/, "");
|
|
1794
1513
|
this.apiVersion = extra.apiVersion ?? "2023-06-01";
|
|
1795
1514
|
}
|
|
1796
|
-
async send(body
|
|
1797
|
-
const requestRaw = JSON.stringify(body);
|
|
1515
|
+
async send(body) {
|
|
1798
1516
|
const res = await fetch(`${this.baseURL}/v1/messages`, {
|
|
1799
1517
|
method: "POST",
|
|
1800
1518
|
headers: {
|
|
@@ -1802,93 +1520,65 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1802
1520
|
"anthropic-version": this.apiVersion,
|
|
1803
1521
|
"Content-Type": "application/json"
|
|
1804
1522
|
},
|
|
1805
|
-
body:
|
|
1523
|
+
body: JSON.stringify(body)
|
|
1806
1524
|
});
|
|
1807
|
-
const responseRaw = await res.text().catch(() => "");
|
|
1808
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
1809
1525
|
if (!res.ok) {
|
|
1810
|
-
|
|
1526
|
+
const text = await res.text().catch(() => "");
|
|
1527
|
+
throw new Error(`anthropic request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
1811
1528
|
}
|
|
1812
|
-
return
|
|
1529
|
+
return await res.json();
|
|
1813
1530
|
}
|
|
1814
1531
|
async call(model, options) {
|
|
1815
|
-
|
|
1816
|
-
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
1817
|
-
instruction: options.instruction,
|
|
1818
|
-
jsonSchemaName: options.jsonSchemaName,
|
|
1819
|
-
jsonSchema: options.jsonSchema
|
|
1820
|
-
});
|
|
1821
|
-
const choice = await this.chatWithTools(model, {
|
|
1822
|
-
caller: options.caller ?? options.jsonSchemaName,
|
|
1823
|
-
instruction,
|
|
1824
|
-
messages: [{ role: "user", content: options.message }],
|
|
1825
|
-
tools: [tool],
|
|
1826
|
-
reasoningEffort: "none",
|
|
1827
|
-
toolChoice: { type: "tool", name: toolName }
|
|
1828
|
-
});
|
|
1829
|
-
return parseStructuredJsonResult(choice, toolName);
|
|
1830
|
-
}
|
|
1532
|
+
const jsonMode = "jsonSchemaName" in options;
|
|
1831
1533
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
1832
1534
|
const log5 = logger.child("llm:anthropic");
|
|
1833
|
-
log5.debug(`call: model=${model} jsonSchema
|
|
1834
|
-
const outputCap = 4096;
|
|
1535
|
+
log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
1835
1536
|
const body = {
|
|
1836
1537
|
model,
|
|
1837
|
-
max_tokens:
|
|
1538
|
+
max_tokens: 4096,
|
|
1838
1539
|
system: options.instruction,
|
|
1839
1540
|
messages: [{ role: "user", content: options.message }]
|
|
1840
1541
|
};
|
|
1841
1542
|
if (reasoning !== "none") {
|
|
1842
|
-
const budget = REASONING_BUDGET[reasoning];
|
|
1843
|
-
body["max_tokens"] = budget + outputCap;
|
|
1844
1543
|
body["thinking"] = {
|
|
1845
1544
|
type: "enabled",
|
|
1846
|
-
budget_tokens:
|
|
1545
|
+
budget_tokens: REASONING_BUDGET[reasoning]
|
|
1847
1546
|
};
|
|
1848
1547
|
}
|
|
1849
|
-
const data = await this.send(body
|
|
1548
|
+
const data = await this.send(body);
|
|
1850
1549
|
if (data.error) {
|
|
1851
1550
|
throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
|
|
1852
1551
|
}
|
|
1853
|
-
const text =
|
|
1552
|
+
const text = (data.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
1854
1553
|
if (!text) {
|
|
1855
1554
|
throw new Error("Empty response from model");
|
|
1856
1555
|
}
|
|
1857
|
-
return text;
|
|
1556
|
+
return jsonMode ? JSON.parse(text) : text;
|
|
1858
1557
|
}
|
|
1859
1558
|
async chatWithTools(model, options) {
|
|
1860
1559
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
1861
1560
|
const log5 = logger.child("llm:anthropic");
|
|
1862
1561
|
log5.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
|
|
1863
|
-
const { system
|
|
1864
|
-
const outputCap = 4096;
|
|
1562
|
+
const { system, msgs } = toAnthropicMessages(options.messages);
|
|
1865
1563
|
const body = {
|
|
1866
1564
|
model,
|
|
1867
|
-
max_tokens:
|
|
1868
|
-
system:
|
|
1869
|
-
messages:
|
|
1565
|
+
max_tokens: 4096,
|
|
1566
|
+
system: system ?? options.instruction,
|
|
1567
|
+
messages: msgs,
|
|
1870
1568
|
tools: options.tools.map(toAnthropicTool)
|
|
1871
1569
|
};
|
|
1872
|
-
if (options.toolChoice) {
|
|
1873
|
-
body["tool_choice"] = {
|
|
1874
|
-
type: "tool",
|
|
1875
|
-
name: options.toolChoice.name
|
|
1876
|
-
};
|
|
1877
|
-
}
|
|
1878
1570
|
if (reasoning !== "none") {
|
|
1879
|
-
const budget = REASONING_BUDGET[reasoning];
|
|
1880
|
-
body["max_tokens"] = budget + outputCap;
|
|
1881
1571
|
body["thinking"] = {
|
|
1882
1572
|
type: "enabled",
|
|
1883
|
-
budget_tokens:
|
|
1573
|
+
budget_tokens: REASONING_BUDGET[reasoning]
|
|
1884
1574
|
};
|
|
1885
1575
|
}
|
|
1886
|
-
const data = await this.send(body
|
|
1576
|
+
const data = await this.send(body);
|
|
1887
1577
|
if (data.error) {
|
|
1888
1578
|
throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
|
|
1889
1579
|
}
|
|
1890
1580
|
const blocks = data.content ?? [];
|
|
1891
|
-
const text =
|
|
1581
|
+
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
1892
1582
|
const toolCalls = blocks.filter((b) => b.type === "tool_use").map((b) => ({
|
|
1893
1583
|
id: b.id,
|
|
1894
1584
|
function: {
|
|
@@ -1902,11 +1592,11 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1902
1592
|
|
|
1903
1593
|
// src/provider/providers/bedrock.ts
|
|
1904
1594
|
import { createHmac, createHash } from "node:crypto";
|
|
1905
|
-
var
|
|
1595
|
+
var pad2 = (n) => n.toString().padStart(2, "0");
|
|
1906
1596
|
function amzDate(now) {
|
|
1907
1597
|
return {
|
|
1908
|
-
date: `${now.getUTCFullYear()}${
|
|
1909
|
-
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`
|
|
1910
1600
|
};
|
|
1911
1601
|
}
|
|
1912
1602
|
function signRequest(opts) {
|
|
@@ -1953,6 +1643,58 @@ function signRequest(opts) {
|
|
|
1953
1643
|
headers["X-Amz-Security-Token"] = opts.sessionToken;
|
|
1954
1644
|
return headers;
|
|
1955
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
|
+
}
|
|
1956
1698
|
function toAnthropicTool2(t) {
|
|
1957
1699
|
return {
|
|
1958
1700
|
name: t.name,
|
|
@@ -1962,7 +1704,7 @@ function toAnthropicTool2(t) {
|
|
|
1962
1704
|
}
|
|
1963
1705
|
function extractAnthropicContent(data) {
|
|
1964
1706
|
const content = data["content"] ?? [];
|
|
1965
|
-
const text =
|
|
1707
|
+
const text = content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("");
|
|
1966
1708
|
const toolCalls = content.filter((b) => b["type"] === "tool_use").map((b) => ({
|
|
1967
1709
|
id: b["id"],
|
|
1968
1710
|
function: {
|
|
@@ -1994,7 +1736,7 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1994
1736
|
this.sessionToken = readAuthString(opts.auth, "sessionToken", "AWS_SESSION_TOKEN");
|
|
1995
1737
|
this.baseURL = `https://bedrock-runtime.${this.region}.amazonaws.com`;
|
|
1996
1738
|
}
|
|
1997
|
-
async invoke(model, body
|
|
1739
|
+
async invoke(model, body) {
|
|
1998
1740
|
const bodyStr = JSON.stringify(body);
|
|
1999
1741
|
const path = `/model/${encodeURIComponent(model)}/invoke`;
|
|
2000
1742
|
const headers = signRequest({
|
|
@@ -2013,32 +1755,16 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
2013
1755
|
headers,
|
|
2014
1756
|
body: bodyStr
|
|
2015
1757
|
});
|
|
2016
|
-
const responseRaw = await res.text().catch(() => "");
|
|
2017
|
-
logLlmWire(caller, bodyStr, responseRaw);
|
|
2018
1758
|
if (!res.ok) {
|
|
2019
|
-
|
|
1759
|
+
const text = await res.text().catch(() => "");
|
|
1760
|
+
throw new Error(`bedrock request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
2020
1761
|
}
|
|
2021
|
-
return
|
|
1762
|
+
return await res.json();
|
|
2022
1763
|
}
|
|
2023
1764
|
async call(model, options) {
|
|
1765
|
+
const jsonMode = "jsonSchemaName" in options;
|
|
2024
1766
|
const log5 = logger.child("llm:bedrock");
|
|
2025
|
-
|
|
2026
|
-
log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
|
|
2027
|
-
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
2028
|
-
instruction: options.instruction,
|
|
2029
|
-
jsonSchemaName: options.jsonSchemaName,
|
|
2030
|
-
jsonSchema: options.jsonSchema
|
|
2031
|
-
});
|
|
2032
|
-
const choice = await this.chatWithTools(model, {
|
|
2033
|
-
caller: options.caller ?? options.jsonSchemaName,
|
|
2034
|
-
instruction,
|
|
2035
|
-
messages: [{ role: "user", content: options.message }],
|
|
2036
|
-
tools: [tool],
|
|
2037
|
-
toolChoice: { type: "tool", name: toolName }
|
|
2038
|
-
});
|
|
2039
|
-
return parseStructuredJsonResult(choice, toolName);
|
|
2040
|
-
}
|
|
2041
|
-
log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
|
|
1767
|
+
log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
2042
1768
|
if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
|
|
2043
1769
|
throw new Error(`bedrock provider currently only supports Anthropic models (got ${model})`);
|
|
2044
1770
|
}
|
|
@@ -2048,12 +1774,12 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
2048
1774
|
system: options.instruction,
|
|
2049
1775
|
messages: [{ role: "user", content: options.message }]
|
|
2050
1776
|
};
|
|
2051
|
-
const data = await this.invoke(model, body
|
|
1777
|
+
const data = await this.invoke(model, body);
|
|
2052
1778
|
const { text } = extractAnthropicContent(data);
|
|
2053
1779
|
if (!text) {
|
|
2054
1780
|
throw new Error("Empty response from model");
|
|
2055
1781
|
}
|
|
2056
|
-
return text;
|
|
1782
|
+
return jsonMode ? JSON.parse(text) : text;
|
|
2057
1783
|
}
|
|
2058
1784
|
async chatWithTools(model, options) {
|
|
2059
1785
|
const log5 = logger.child("llm:bedrock");
|
|
@@ -2061,6 +1787,7 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
2061
1787
|
if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
|
|
2062
1788
|
throw new Error(`bedrock provider currently only supports Anthropic models (got ${model})`);
|
|
2063
1789
|
}
|
|
1790
|
+
const { system, msgs } = toAnthropicMessages2(options.messages);
|
|
2064
1791
|
const body = {
|
|
2065
1792
|
anthropic_version: "bedrock-2023-05-31",
|
|
2066
1793
|
max_tokens: 4096,
|
|
@@ -2068,13 +1795,7 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
2068
1795
|
messages: msgs,
|
|
2069
1796
|
tools: options.tools.map(toAnthropicTool2)
|
|
2070
1797
|
};
|
|
2071
|
-
|
|
2072
|
-
body.tool_choice = {
|
|
2073
|
-
type: "tool",
|
|
2074
|
-
name: options.toolChoice.name
|
|
2075
|
-
};
|
|
2076
|
-
}
|
|
2077
|
-
const data = await this.invoke(model, body, resolveLlmCaller(options));
|
|
1798
|
+
const data = await this.invoke(model, body);
|
|
2078
1799
|
const { text, toolCalls } = extractAnthropicContent(data);
|
|
2079
1800
|
return { message: { content: text || undefined, toolCalls } };
|
|
2080
1801
|
}
|
|
@@ -2088,7 +1809,6 @@ var VertexAuthSchema = z5.object({
|
|
|
2088
1809
|
}).loose();
|
|
2089
1810
|
function toGeminiContents(messages) {
|
|
2090
1811
|
const out = [];
|
|
2091
|
-
const nameByCallId = new Map;
|
|
2092
1812
|
for (const m of messages) {
|
|
2093
1813
|
if (m.role === "system")
|
|
2094
1814
|
continue;
|
|
@@ -2102,7 +1822,6 @@ function toGeminiContents(messages) {
|
|
|
2102
1822
|
parts.push({ text: m.content });
|
|
2103
1823
|
if (m.toolCalls) {
|
|
2104
1824
|
for (const c of m.toolCalls) {
|
|
2105
|
-
nameByCallId.set(c.id, c.function.name);
|
|
2106
1825
|
let args = {};
|
|
2107
1826
|
try {
|
|
2108
1827
|
args = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
@@ -2124,26 +1843,21 @@ function toGeminiContents(messages) {
|
|
|
2124
1843
|
}
|
|
2125
1844
|
out.push({
|
|
2126
1845
|
role: "user",
|
|
2127
|
-
parts: [
|
|
2128
|
-
{
|
|
2129
|
-
functionResponse: {
|
|
2130
|
-
name: nameByCallId.get(m.toolCallId) ?? "",
|
|
2131
|
-
response
|
|
2132
|
-
}
|
|
2133
|
-
}
|
|
2134
|
-
]
|
|
1846
|
+
parts: [{ functionResponse: { name: "", response } }]
|
|
2135
1847
|
});
|
|
2136
1848
|
}
|
|
2137
1849
|
}
|
|
2138
1850
|
return out;
|
|
2139
1851
|
}
|
|
2140
|
-
function
|
|
1852
|
+
function toGeminiTool(t) {
|
|
2141
1853
|
return {
|
|
2142
|
-
functionDeclarations:
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
1854
|
+
functionDeclarations: [
|
|
1855
|
+
{
|
|
1856
|
+
name: t.name,
|
|
1857
|
+
description: t.description,
|
|
1858
|
+
parameters: t.parameters ?? { type: "object", properties: {} }
|
|
1859
|
+
}
|
|
1860
|
+
]
|
|
2147
1861
|
};
|
|
2148
1862
|
}
|
|
2149
1863
|
function extractFromGemini(data) {
|
|
@@ -2164,10 +1878,7 @@ function extractFromGemini(data) {
|
|
|
2164
1878
|
});
|
|
2165
1879
|
}
|
|
2166
1880
|
}
|
|
2167
|
-
return {
|
|
2168
|
-
text: stripThinkTags(text),
|
|
2169
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
2170
|
-
};
|
|
1881
|
+
return { text, toolCalls: toolCalls.length > 0 ? toolCalls : undefined };
|
|
2171
1882
|
}
|
|
2172
1883
|
|
|
2173
1884
|
class VertexExecutor extends LLMExecutor {
|
|
@@ -2190,23 +1901,21 @@ class VertexExecutor extends LLMExecutor {
|
|
|
2190
1901
|
this.region = extra.region ?? readAuthString(opts.auth, "region", "GOOGLE_CLOUD_REGION") ?? "us-central1";
|
|
2191
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";
|
|
2192
1903
|
}
|
|
2193
|
-
async generate(model, body
|
|
1904
|
+
async generate(model, body) {
|
|
2194
1905
|
const url = `${this.baseURL}/${encodeURIComponent(model)}:generateContent`;
|
|
2195
|
-
const requestRaw = JSON.stringify(body);
|
|
2196
1906
|
const res = await fetch(url, {
|
|
2197
1907
|
method: "POST",
|
|
2198
1908
|
headers: {
|
|
2199
1909
|
Authorization: `Bearer ${this.apiKey}`,
|
|
2200
1910
|
"Content-Type": "application/json"
|
|
2201
1911
|
},
|
|
2202
|
-
body:
|
|
1912
|
+
body: JSON.stringify(body)
|
|
2203
1913
|
});
|
|
2204
|
-
const responseRaw = await res.text().catch(() => "");
|
|
2205
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
2206
1914
|
if (!res.ok) {
|
|
2207
|
-
|
|
1915
|
+
const text = await res.text().catch(() => "");
|
|
1916
|
+
throw new Error(`vertex request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
2208
1917
|
}
|
|
2209
|
-
return
|
|
1918
|
+
return await res.json();
|
|
2210
1919
|
}
|
|
2211
1920
|
async call(model, options) {
|
|
2212
1921
|
const jsonMode = "jsonSchemaName" in options;
|
|
@@ -2227,7 +1936,7 @@ class VertexExecutor extends LLMExecutor {
|
|
|
2227
1936
|
responseSchema: options.jsonSchema
|
|
2228
1937
|
};
|
|
2229
1938
|
}
|
|
2230
|
-
const data = await this.generate(model, body
|
|
1939
|
+
const data = await this.generate(model, body);
|
|
2231
1940
|
if (data.error) {
|
|
2232
1941
|
throw new Error(`vertex API error: ${data.error.message ?? "unknown"}`);
|
|
2233
1942
|
}
|
|
@@ -2235,7 +1944,7 @@ class VertexExecutor extends LLMExecutor {
|
|
|
2235
1944
|
if (!text) {
|
|
2236
1945
|
throw new Error("Empty response from model");
|
|
2237
1946
|
}
|
|
2238
|
-
return jsonMode ?
|
|
1947
|
+
return jsonMode ? JSON.parse(text) : text;
|
|
2239
1948
|
}
|
|
2240
1949
|
async chatWithTools(model, options) {
|
|
2241
1950
|
const log5 = logger.child("llm:vertex");
|
|
@@ -2244,9 +1953,9 @@ class VertexExecutor extends LLMExecutor {
|
|
|
2244
1953
|
const body = {
|
|
2245
1954
|
contents,
|
|
2246
1955
|
systemInstruction: { parts: [{ text: options.instruction }] },
|
|
2247
|
-
tools: options.tools.length > 0 ? [
|
|
1956
|
+
tools: options.tools.length > 0 ? [toGeminiTool(options.tools[0])] : undefined
|
|
2248
1957
|
};
|
|
2249
|
-
const data = await this.generate(model, body
|
|
1958
|
+
const data = await this.generate(model, body);
|
|
2250
1959
|
if (data.error) {
|
|
2251
1960
|
throw new Error(`vertex API error: ${data.error.message ?? "unknown"}`);
|
|
2252
1961
|
}
|
|
@@ -2322,41 +2031,25 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2322
2031
|
const extra = parsed.success ? parsed.data : {};
|
|
2323
2032
|
this.baseURL = (extra.baseURL ?? readAuthString(opts.auth, "baseURL", "GITLAB_BASE_URL") ?? "https://gitlab.com/api/v4/ai/llm/proxy").replace(/\/+$/, "");
|
|
2324
2033
|
}
|
|
2325
|
-
async send(body
|
|
2326
|
-
const requestRaw = JSON.stringify(body);
|
|
2034
|
+
async send(body) {
|
|
2327
2035
|
const res = await fetch(this.baseURL, {
|
|
2328
2036
|
method: "POST",
|
|
2329
2037
|
headers: {
|
|
2330
2038
|
Authorization: `Bearer ${this.apiKey}`,
|
|
2331
2039
|
"Content-Type": "application/json"
|
|
2332
2040
|
},
|
|
2333
|
-
body:
|
|
2041
|
+
body: JSON.stringify(body)
|
|
2334
2042
|
});
|
|
2335
|
-
const responseRaw = await res.text().catch(() => "");
|
|
2336
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
2337
2043
|
if (!res.ok) {
|
|
2338
|
-
|
|
2044
|
+
const text = await res.text().catch(() => "");
|
|
2045
|
+
throw new Error(`gitlab-duo request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
2339
2046
|
}
|
|
2340
|
-
return
|
|
2047
|
+
return await res.json();
|
|
2341
2048
|
}
|
|
2342
2049
|
async call(model, options) {
|
|
2050
|
+
const jsonMode = "jsonSchemaName" in options;
|
|
2343
2051
|
const log5 = logger.child("llm:gitlab-duo");
|
|
2344
|
-
|
|
2345
|
-
log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
|
|
2346
|
-
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
2347
|
-
instruction: options.instruction,
|
|
2348
|
-
jsonSchemaName: options.jsonSchemaName,
|
|
2349
|
-
jsonSchema: options.jsonSchema
|
|
2350
|
-
});
|
|
2351
|
-
const choice = await this.chatWithTools(model, {
|
|
2352
|
-
caller: options.caller ?? options.jsonSchemaName,
|
|
2353
|
-
instruction,
|
|
2354
|
-
messages: [{ role: "user", content: options.message }],
|
|
2355
|
-
tools: [tool]
|
|
2356
|
-
});
|
|
2357
|
-
return parseStructuredJsonResult(choice, toolName);
|
|
2358
|
-
}
|
|
2359
|
-
log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
|
|
2052
|
+
log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
2360
2053
|
const body = {
|
|
2361
2054
|
model,
|
|
2362
2055
|
messages: [
|
|
@@ -2364,15 +2057,15 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2364
2057
|
{ role: "user", content: options.message }
|
|
2365
2058
|
]
|
|
2366
2059
|
};
|
|
2367
|
-
const data = await this.send(body
|
|
2060
|
+
const data = await this.send(body);
|
|
2368
2061
|
if (data.error) {
|
|
2369
2062
|
throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
|
|
2370
2063
|
}
|
|
2371
|
-
const content =
|
|
2064
|
+
const content = data.choices?.[0]?.message?.content ?? "";
|
|
2372
2065
|
if (!content) {
|
|
2373
2066
|
throw new Error("Empty response from model");
|
|
2374
2067
|
}
|
|
2375
|
-
return content;
|
|
2068
|
+
return jsonMode ? JSON.parse(content) : content;
|
|
2376
2069
|
}
|
|
2377
2070
|
async chatWithTools(model, options) {
|
|
2378
2071
|
const log5 = logger.child("llm:gitlab-duo");
|
|
@@ -2385,7 +2078,7 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2385
2078
|
],
|
|
2386
2079
|
tools: options.tools.map(toTool)
|
|
2387
2080
|
};
|
|
2388
|
-
const data = await this.send(body
|
|
2081
|
+
const data = await this.send(body);
|
|
2389
2082
|
if (data.error) {
|
|
2390
2083
|
throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
|
|
2391
2084
|
}
|
|
@@ -2396,7 +2089,7 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2396
2089
|
}));
|
|
2397
2090
|
return {
|
|
2398
2091
|
message: {
|
|
2399
|
-
content: choice?.message?.content
|
|
2092
|
+
content: choice?.message?.content ?? undefined,
|
|
2400
2093
|
toolCalls
|
|
2401
2094
|
}
|
|
2402
2095
|
};
|
|
@@ -2453,43 +2146,26 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2453
2146
|
const account = extra.account ?? readAuthString(opts.auth, "account", "SNOWFLAKE_ACCOUNT") ?? "";
|
|
2454
2147
|
this.baseURL = account ? `https://${account}.snowflakecomputing.com/api/v2/cortex/inference:complete` : "https://__account__.snowflakecomputing.com/api/v2/cortex/inference:complete";
|
|
2455
2148
|
}
|
|
2456
|
-
async send(body
|
|
2457
|
-
const requestRaw = JSON.stringify(body);
|
|
2149
|
+
async send(body) {
|
|
2458
2150
|
const res = await fetch(this.baseURL, {
|
|
2459
2151
|
method: "POST",
|
|
2460
2152
|
headers: {
|
|
2461
2153
|
Authorization: `Bearer ${this.apiKey}`,
|
|
2462
2154
|
"Content-Type": "application/json"
|
|
2463
2155
|
},
|
|
2464
|
-
body:
|
|
2156
|
+
body: JSON.stringify(body)
|
|
2465
2157
|
});
|
|
2466
|
-
const responseRaw = await res.text().catch(() => "");
|
|
2467
|
-
logLlmWire(caller, requestRaw, responseRaw);
|
|
2468
2158
|
if (!res.ok) {
|
|
2469
|
-
|
|
2159
|
+
const text = await res.text().catch(() => "");
|
|
2160
|
+
throw new Error(`snowflake-cortex request failed: ${res.status} ${res.statusText} body=${text.slice(0, 500)}`);
|
|
2470
2161
|
}
|
|
2471
|
-
return
|
|
2162
|
+
return await res.json();
|
|
2472
2163
|
}
|
|
2473
2164
|
async call(model, options) {
|
|
2474
|
-
const
|
|
2475
|
-
if ("jsonSchemaName" in options) {
|
|
2476
|
-
log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
|
|
2477
|
-
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
2478
|
-
instruction: options.instruction,
|
|
2479
|
-
jsonSchemaName: options.jsonSchemaName,
|
|
2480
|
-
jsonSchema: options.jsonSchema
|
|
2481
|
-
});
|
|
2482
|
-
const choice = await this.chatWithTools(model, {
|
|
2483
|
-
caller: options.caller ?? options.jsonSchemaName,
|
|
2484
|
-
instruction,
|
|
2485
|
-
messages: [{ role: "user", content: options.message }],
|
|
2486
|
-
tools: [tool],
|
|
2487
|
-
reasoningEffort: "none"
|
|
2488
|
-
});
|
|
2489
|
-
return parseStructuredJsonResult(choice, toolName);
|
|
2490
|
-
}
|
|
2165
|
+
const jsonMode = "jsonSchemaName" in options;
|
|
2491
2166
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
2492
|
-
log5.
|
|
2167
|
+
const log5 = logger.child("llm:snowflake-cortex");
|
|
2168
|
+
log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
2493
2169
|
const body = {
|
|
2494
2170
|
model,
|
|
2495
2171
|
messages: [
|
|
@@ -2500,15 +2176,15 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2500
2176
|
if (reasoning !== "none") {
|
|
2501
2177
|
body["reasoning_effort"] = reasoning;
|
|
2502
2178
|
}
|
|
2503
|
-
const data = await this.send(body
|
|
2179
|
+
const data = await this.send(body);
|
|
2504
2180
|
if (data.error) {
|
|
2505
2181
|
throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
|
|
2506
2182
|
}
|
|
2507
|
-
const content =
|
|
2183
|
+
const content = data.choices?.[0]?.message?.content ?? data.message ?? "";
|
|
2508
2184
|
if (!content) {
|
|
2509
2185
|
throw new Error("Empty response from model");
|
|
2510
2186
|
}
|
|
2511
|
-
return content;
|
|
2187
|
+
return jsonMode ? JSON.parse(content) : content;
|
|
2512
2188
|
}
|
|
2513
2189
|
async chatWithTools(model, options) {
|
|
2514
2190
|
const log5 = logger.child("llm:snowflake-cortex");
|
|
@@ -2521,12 +2197,12 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2521
2197
|
],
|
|
2522
2198
|
tools: options.tools.map(toCortexTool)
|
|
2523
2199
|
};
|
|
2524
|
-
const data = await this.send(body
|
|
2200
|
+
const data = await this.send(body);
|
|
2525
2201
|
if (data.error) {
|
|
2526
2202
|
throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
|
|
2527
2203
|
}
|
|
2528
2204
|
const choice = data.choices?.[0];
|
|
2529
|
-
const content =
|
|
2205
|
+
const content = choice?.message?.content ?? data.message ?? "";
|
|
2530
2206
|
const toolCalls = choice?.message?.tool_calls?.map((c) => ({
|
|
2531
2207
|
id: c.id,
|
|
2532
2208
|
function: {
|
|
@@ -2566,7 +2242,6 @@ register("gmi", GmiExecutor);
|
|
|
2566
2242
|
register("zai", ZaiExecutor);
|
|
2567
2243
|
register("zenmux", ZenMuxExecutor);
|
|
2568
2244
|
register("minimax", MiniMaxExecutor);
|
|
2569
|
-
register("minimax-cn", MiniMaxCnExecutor);
|
|
2570
2245
|
register("ionet", IoNetExecutor);
|
|
2571
2246
|
register("baseten", BasetenExecutor);
|
|
2572
2247
|
register("cortecs", CortecsExecutor);
|
|
@@ -2600,14 +2275,14 @@ var llm = new Proxy({}, {
|
|
|
2600
2275
|
// src/provider/promptLoader.ts
|
|
2601
2276
|
import { existsSync as existsSync2 } from "fs";
|
|
2602
2277
|
import { readFile as readFile2 } from "fs/promises";
|
|
2603
|
-
import path, { dirname as
|
|
2278
|
+
import path, { dirname as dirname3 } from "path";
|
|
2604
2279
|
import { fileURLToPath } from "url";
|
|
2605
2280
|
var log5 = logger.child("prompt-loader");
|
|
2606
2281
|
function fileName(promptKey) {
|
|
2607
2282
|
return promptKey.toLowerCase() + ".md";
|
|
2608
2283
|
}
|
|
2609
2284
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
2610
|
-
var __dirname2 =
|
|
2285
|
+
var __dirname2 = dirname3(__filename2);
|
|
2611
2286
|
var PROMPTS_DIR = existsSync2(path.resolve(__dirname2, "prompts")) ? path.resolve(__dirname2, "prompts") : path.resolve(__dirname2, "../../prompts");
|
|
2612
2287
|
async function loadPrompt(promptKey) {
|
|
2613
2288
|
const filePath = path.join(PROMPTS_DIR, fileName(promptKey));
|
|
@@ -2732,14 +2407,14 @@ function translateMessageHistory(personaName, entries) {
|
|
|
2732
2407
|
}
|
|
2733
2408
|
|
|
2734
2409
|
// src/brain/schedule.ts
|
|
2735
|
-
function
|
|
2410
|
+
function pad22(n) {
|
|
2736
2411
|
return n < 10 ? `0${n}` : `${n}`;
|
|
2737
2412
|
}
|
|
2738
2413
|
function formatDateKey(d) {
|
|
2739
|
-
return `${d.getFullYear()}-${
|
|
2414
|
+
return `${d.getFullYear()}-${pad22(d.getMonth() + 1)}-${pad22(d.getDate())}`;
|
|
2740
2415
|
}
|
|
2741
2416
|
function formatMonthKey(d) {
|
|
2742
|
-
return `${d.getFullYear()}-${
|
|
2417
|
+
return `${d.getFullYear()}-${pad22(d.getMonth() + 1)}`;
|
|
2743
2418
|
}
|
|
2744
2419
|
|
|
2745
2420
|
// src/brain/memory.ts
|
|
@@ -2827,22 +2502,22 @@ class Brain {
|
|
|
2827
2502
|
db;
|
|
2828
2503
|
space;
|
|
2829
2504
|
brainbase;
|
|
2830
|
-
availabilityCache = new Map;
|
|
2831
2505
|
memory;
|
|
2832
|
-
|
|
2506
|
+
availabilityCache = new Map;
|
|
2507
|
+
constructor(db, space, brainbase, memory = new Memory(this.db, this.space)) {
|
|
2833
2508
|
this.db = db;
|
|
2834
2509
|
this.space = space;
|
|
2835
2510
|
this.brainbase = brainbase;
|
|
2836
|
-
this.memory = memory
|
|
2511
|
+
this.memory = memory;
|
|
2837
2512
|
log7.debug(`Brain constructed: id=${brainbase.brainId} name=${brainbase.displayName} space=${space.name}`);
|
|
2838
2513
|
}
|
|
2839
2514
|
async createDailySchedule(datetime) {
|
|
2840
|
-
const
|
|
2841
|
-
log7.debug(`createDailySchedule: starting for ${
|
|
2515
|
+
const dateKey = formatDateKey(datetime);
|
|
2516
|
+
log7.debug(`createDailySchedule: starting for ${dateKey}`);
|
|
2842
2517
|
try {
|
|
2843
|
-
const existing = await this.memory.get(`daily-schedule:${
|
|
2518
|
+
const existing = await this.memory.get(`daily-schedule:${dateKey}`);
|
|
2844
2519
|
if (existing) {
|
|
2845
|
-
log7.debug(`createDailySchedule: cache hit for ${
|
|
2520
|
+
log7.debug(`createDailySchedule: cache hit for ${dateKey}`);
|
|
2846
2521
|
try {
|
|
2847
2522
|
return JSON.parse(existing.content);
|
|
2848
2523
|
} catch (parseErr) {
|
|
@@ -2870,7 +2545,7 @@ class Brain {
|
|
|
2870
2545
|
}
|
|
2871
2546
|
const instruction = await loadPrompt("DAILY_SCHEDULE");
|
|
2872
2547
|
const promptMessage = [
|
|
2873
|
-
`Target date: ${
|
|
2548
|
+
`Target date: ${dateKey} (${datetime.toLocaleDateString("en-US", { weekday: "long" })})`,
|
|
2874
2549
|
`Personality: ${this.brainbase.baseSystemPrompt}`,
|
|
2875
2550
|
monthlySummary ? `Monthly summary for this day: ${monthlySummary}` : "(no monthly summary available for this date)",
|
|
2876
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)"}`,
|
|
@@ -2881,7 +2556,6 @@ class Brain {
|
|
|
2881
2556
|
`);
|
|
2882
2557
|
log7.debug(`createDailySchedule: calling identity model`);
|
|
2883
2558
|
const schedule = await llm.call(llm.models.identity, {
|
|
2884
|
-
caller: "daily-schedule",
|
|
2885
2559
|
instruction,
|
|
2886
2560
|
message: promptMessage,
|
|
2887
2561
|
jsonSchemaName: "daily-schedule",
|
|
@@ -2889,15 +2563,15 @@ class Brain {
|
|
|
2889
2563
|
});
|
|
2890
2564
|
log7.debug(`createDailySchedule: model returned ${schedule.items.length} slots`);
|
|
2891
2565
|
await this.memory.add({
|
|
2892
|
-
customId: `daily-schedule:${
|
|
2566
|
+
customId: `daily-schedule:${dateKey}`,
|
|
2893
2567
|
content: JSON.stringify(schedule),
|
|
2894
2568
|
metadata: {
|
|
2895
2569
|
kind: "schedule",
|
|
2896
2570
|
source: "createDailySchedule",
|
|
2897
|
-
date:
|
|
2571
|
+
date: dateKey
|
|
2898
2572
|
}
|
|
2899
2573
|
});
|
|
2900
|
-
log7.debug(`createDailySchedule: persisted ${
|
|
2574
|
+
log7.debug(`createDailySchedule: persisted ${dateKey}`);
|
|
2901
2575
|
return schedule;
|
|
2902
2576
|
} catch (error) {
|
|
2903
2577
|
let reason = error instanceof Error ? error.message + `(${error.name})` : String(error);
|
|
@@ -2909,21 +2583,17 @@ class Brain {
|
|
|
2909
2583
|
}
|
|
2910
2584
|
async regenerateSchedules() {
|
|
2911
2585
|
const today = new Date;
|
|
2912
|
-
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
|
2913
|
-
const monthly = await this.createMonthlySchedule(nextMonth);
|
|
2914
|
-
if (!monthly) {
|
|
2915
|
-
log7.debug(`regenerateSchedules: skip daily — monthly schedule generation failed`);
|
|
2916
|
-
return;
|
|
2917
|
-
}
|
|
2918
2586
|
const tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
|
|
2919
2587
|
await this.createDailySchedule(tomorrow);
|
|
2920
2588
|
await this.createDailySchedule(today);
|
|
2589
|
+
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
|
2590
|
+
await this.createMonthlySchedule(nextMonth);
|
|
2921
2591
|
}
|
|
2922
2592
|
async createMonthlySchedule(datetime) {
|
|
2923
2593
|
const year = datetime.getMonth() === 11 ? datetime.getFullYear() + 1 : datetime.getFullYear();
|
|
2924
2594
|
const month = (datetime.getMonth() + 1) % 12;
|
|
2925
2595
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
2926
|
-
const monthKey = `${year}-${
|
|
2596
|
+
const monthKey = `${year}-${pad22(month + 1)}`;
|
|
2927
2597
|
log7.debug(`createMonthlySchedule: starting for ${monthKey}`);
|
|
2928
2598
|
try {
|
|
2929
2599
|
const existing = await this.memory.get(`monthly-schedule:${monthKey}`);
|
|
@@ -2936,7 +2606,7 @@ class Brain {
|
|
|
2936
2606
|
}
|
|
2937
2607
|
}
|
|
2938
2608
|
const twoMonthsAgo = new Date(year, month - 2, 1);
|
|
2939
|
-
const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${
|
|
2609
|
+
const twoMonthsAgoKey = `${twoMonthsAgo.getFullYear()}-${pad22(twoMonthsAgo.getMonth() + 1)}`;
|
|
2940
2610
|
log7.debug(`createMonthlySchedule: gathering context (history, ${twoMonthsAgoKey})`);
|
|
2941
2611
|
const [history, twoMonthsAgoStored] = await Promise.all([
|
|
2942
2612
|
this.getHistoryFacts(),
|
|
@@ -2964,7 +2634,6 @@ class Brain {
|
|
|
2964
2634
|
`);
|
|
2965
2635
|
log7.debug(`createMonthlySchedule: calling identity model`);
|
|
2966
2636
|
const schedule = await llm.call(llm.models.identity, {
|
|
2967
|
-
caller: "monthly-schedule",
|
|
2968
2637
|
instruction,
|
|
2969
2638
|
message: promptMessage,
|
|
2970
2639
|
jsonSchemaName: "monthly-schedule",
|
|
@@ -2995,11 +2664,11 @@ class Brain {
|
|
|
2995
2664
|
}
|
|
2996
2665
|
log7.debug(`sleepMemory: starting, ${history.length} messages`);
|
|
2997
2666
|
try {
|
|
2998
|
-
const
|
|
2667
|
+
const dateKey = formatDateKey(datetime);
|
|
2999
2668
|
const instruction = await loadPrompt("MEMOIR");
|
|
3000
2669
|
const historyBlock = translateMessageHistory(this.brainbase.displayName, history);
|
|
3001
2670
|
const promptMessage = [
|
|
3002
|
-
`Date: ${
|
|
2671
|
+
`Date: ${dateKey}`,
|
|
3003
2672
|
`Personality: ${this.brainbase.baseSystemPrompt}`,
|
|
3004
2673
|
`Conversation log:`,
|
|
3005
2674
|
historyBlock
|
|
@@ -3008,21 +2677,20 @@ class Brain {
|
|
|
3008
2677
|
`);
|
|
3009
2678
|
log7.debug(`sleepMemory: calling identity model`);
|
|
3010
2679
|
const memoir = await llm.call(llm.models.identity, {
|
|
3011
|
-
caller: "sleep-memory",
|
|
3012
2680
|
instruction,
|
|
3013
2681
|
message: promptMessage
|
|
3014
2682
|
});
|
|
3015
2683
|
log7.debug(`sleepMemory: model returned ${memoir.length} chars`);
|
|
3016
2684
|
await this.memory.add({
|
|
3017
|
-
customId: `daily-journal:${
|
|
2685
|
+
customId: `daily-journal:${dateKey}`,
|
|
3018
2686
|
content: memoir,
|
|
3019
2687
|
metadata: {
|
|
3020
2688
|
kind: "daily-journal",
|
|
3021
2689
|
source: "sleepMemory",
|
|
3022
|
-
date:
|
|
2690
|
+
date: dateKey
|
|
3023
2691
|
}
|
|
3024
2692
|
});
|
|
3025
|
-
log7.debug(`sleepMemory: journal persisted for ${
|
|
2693
|
+
log7.debug(`sleepMemory: journal persisted for ${dateKey}`);
|
|
3026
2694
|
return memoir;
|
|
3027
2695
|
} catch (error) {
|
|
3028
2696
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -3031,22 +2699,22 @@ class Brain {
|
|
|
3031
2699
|
}
|
|
3032
2700
|
}
|
|
3033
2701
|
async getTodayScheduledAvailability(datetime) {
|
|
3034
|
-
const
|
|
2702
|
+
const dateKey = formatDateKey(datetime);
|
|
3035
2703
|
try {
|
|
3036
|
-
const cached = this.availabilityCache.get(
|
|
2704
|
+
const cached = this.availabilityCache.get(dateKey);
|
|
3037
2705
|
if (cached) {
|
|
3038
|
-
log7.debug(`getTodayScheduledAvailability: cache hit for ${
|
|
2706
|
+
log7.debug(`getTodayScheduledAvailability: cache hit for ${dateKey}`);
|
|
3039
2707
|
return cached;
|
|
3040
2708
|
}
|
|
3041
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
2709
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey}`);
|
|
3042
2710
|
if (!stored) {
|
|
3043
|
-
log7.debug(`getTodayScheduledAvailability: no schedule for ${
|
|
2711
|
+
log7.debug(`getTodayScheduledAvailability: no schedule for ${dateKey}`);
|
|
3044
2712
|
return null;
|
|
3045
2713
|
}
|
|
3046
2714
|
const dailySchedule = JSON.parse(stored.content);
|
|
3047
2715
|
const availability = await this.deriveAvailabilityFromSchedule(dailySchedule);
|
|
3048
|
-
this.availabilityCache.set(
|
|
3049
|
-
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}`);
|
|
3050
2718
|
return availability;
|
|
3051
2719
|
} catch (error) {
|
|
3052
2720
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -3057,7 +2725,7 @@ class Brain {
|
|
|
3057
2725
|
async getAvailability(datetime = new Date) {
|
|
3058
2726
|
const h = datetime.getHours();
|
|
3059
2727
|
const m = datetime.getMinutes();
|
|
3060
|
-
const hhmm = `${
|
|
2728
|
+
const hhmm = `${pad22(h)}:${pad22(m)}`;
|
|
3061
2729
|
const current = h * 60 + m;
|
|
3062
2730
|
const toMinutes = (s) => {
|
|
3063
2731
|
const [hh = 0, mm = 0] = s.split(":").map((x) => parseInt(x, 10));
|
|
@@ -3070,10 +2738,10 @@ class Brain {
|
|
|
3070
2738
|
return result;
|
|
3071
2739
|
}
|
|
3072
2740
|
async getCurrentAndAdjacentSlots(now) {
|
|
3073
|
-
const
|
|
3074
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
2741
|
+
const dateKey = formatDateKey(now);
|
|
2742
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey}`);
|
|
3075
2743
|
if (!stored) {
|
|
3076
|
-
log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${
|
|
2744
|
+
log7.debug(`getCurrentAndAdjacentSlots: no schedule for ${dateKey}`);
|
|
3077
2745
|
return [];
|
|
3078
2746
|
}
|
|
3079
2747
|
let schedule;
|
|
@@ -3106,7 +2774,6 @@ class Brain {
|
|
|
3106
2774
|
personality: this.brainbase.baseSystemPrompt
|
|
3107
2775
|
});
|
|
3108
2776
|
const result = await llm.call(llm.models.identity, {
|
|
3109
|
-
caller: "availability",
|
|
3110
2777
|
instruction,
|
|
3111
2778
|
message: promptMessage,
|
|
3112
2779
|
jsonSchemaName: "availability",
|
|
@@ -3177,46 +2844,74 @@ class Brain {
|
|
|
3177
2844
|
content: userPrompt
|
|
3178
2845
|
}
|
|
3179
2846
|
];
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
2847
|
+
for (let step = 0;step < maxSteps; step += 1) {
|
|
2848
|
+
log7.debug(`sendMessage: step ${step + 1}/${maxSteps} → model`);
|
|
2849
|
+
let choice;
|
|
2850
|
+
try {
|
|
2851
|
+
choice = await llm.chatWithTools(llm.models.conversation, {
|
|
2852
|
+
instruction: `${this.brainbase.baseSystemPrompt}
|
|
3184
2853
|
|
|
3185
2854
|
${instruction}`,
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
2855
|
+
messages,
|
|
2856
|
+
tools
|
|
2857
|
+
});
|
|
2858
|
+
} catch (error) {
|
|
2859
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2860
|
+
logger.error(`sendMessage: LLM call failed at step ${step}: ${reason}`);
|
|
2861
|
+
return replyMessages;
|
|
2862
|
+
}
|
|
2863
|
+
const assistantMessage = choice.message;
|
|
2864
|
+
const toolCalls = assistantMessage.toolCalls ?? [];
|
|
2865
|
+
const hasContent = typeof assistantMessage.content === "string" && assistantMessage.content.length > 0;
|
|
2866
|
+
log7.debug(`sendMessage: step ${step + 1} → toolCalls=${toolCalls.length} hasContent=${hasContent}`);
|
|
2867
|
+
if (toolCalls.length === 0) {
|
|
2868
|
+
log7.debug(`sendMessage: model returned no tool calls; finalising with ${replyMessages.length} replies`);
|
|
2869
|
+
return replyMessages;
|
|
2870
|
+
}
|
|
2871
|
+
messages.push(stripAssistantForHistory(assistantMessage));
|
|
2872
|
+
for (const call of toolCalls) {
|
|
2873
|
+
if (call.function.name === "addReplyMessage") {
|
|
2874
|
+
const content = parseAddReplyMessageArguments(call.function.arguments);
|
|
2875
|
+
if (content !== null) {
|
|
2876
|
+
log7.debug(`sendMessage: addReplyMessage[${replyMessages.length}] (${content.length} chars)`);
|
|
2877
|
+
send(content);
|
|
2878
|
+
replyMessages.push(content);
|
|
2879
|
+
} else {
|
|
3201
2880
|
log7.debug(`sendMessage: addReplyMessage rejected (invalid arguments: ${call.function.arguments})`);
|
|
3202
|
-
return JSON.stringify({ ok: false, error: "invalid arguments" });
|
|
3203
|
-
}
|
|
3204
|
-
if (call.function.name === "searchMemory") {
|
|
3205
|
-
log7.debug(`sendMessage: searchMemory tool call`);
|
|
3206
|
-
return this.executeSearchTool(call.function.arguments);
|
|
3207
2881
|
}
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
error:
|
|
2882
|
+
messages.push({
|
|
2883
|
+
role: "tool",
|
|
2884
|
+
toolCallId: call.id,
|
|
2885
|
+
content: content === null ? JSON.stringify({ ok: false, error: "invalid arguments" }) : JSON.stringify({ ok: true, index: replyMessages.length - 1 })
|
|
3212
2886
|
});
|
|
2887
|
+
continue;
|
|
3213
2888
|
}
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
2889
|
+
if (call.function.name === "searchMemory") {
|
|
2890
|
+
log7.debug(`sendMessage: searchMemory tool call`);
|
|
2891
|
+
const result = await this.executeSearchTool(call.function.arguments);
|
|
2892
|
+
messages.push({
|
|
2893
|
+
role: "tool",
|
|
2894
|
+
toolCallId: call.id,
|
|
2895
|
+
content: result
|
|
2896
|
+
});
|
|
2897
|
+
continue;
|
|
2898
|
+
}
|
|
2899
|
+
log7.debug(`sendMessage: unknown tool "${call.function.name}"`);
|
|
2900
|
+
messages.push({
|
|
2901
|
+
role: "tool",
|
|
2902
|
+
toolCallId: call.id,
|
|
2903
|
+
content: JSON.stringify({
|
|
2904
|
+
ok: false,
|
|
2905
|
+
error: `Unknown tool: ${call.function.name}`
|
|
2906
|
+
})
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
if (!hasContent && toolCalls.every((c) => c.function.name === "searchMemory")) {
|
|
2910
|
+
log7.debug(`sendMessage: step was pure searchMemory, looping back`);
|
|
2911
|
+
continue;
|
|
2912
|
+
}
|
|
3218
2913
|
}
|
|
3219
|
-
|
|
2914
|
+
logger.warn(`sendMessage: reached maxSteps (${maxSteps}) without final reply`);
|
|
3220
2915
|
return replyMessages;
|
|
3221
2916
|
}
|
|
3222
2917
|
async buildMemoryBlock() {
|
|
@@ -3225,11 +2920,11 @@ ${instruction}`,
|
|
|
3225
2920
|
${facts || "(none indexed)"}`;
|
|
3226
2921
|
}
|
|
3227
2922
|
async buildScheduleBlock(now) {
|
|
3228
|
-
const
|
|
2923
|
+
const dateKey = formatDateKey(now);
|
|
3229
2924
|
const currentSlots = await this.getCurrentAndAdjacentSlots(now);
|
|
3230
2925
|
const currentBlock = currentSlots.length > 0 ? `Currently (around ${now.toTimeString().slice(0, 5)}):
|
|
3231
2926
|
${currentSlots.map((s) => ` ${s.start}-${s.end} ${s.activity}${s.notes ? ` (${s.notes})` : ""}`).join(`
|
|
3232
|
-
`)}` : `Currently (${
|
|
2927
|
+
`)}` : `Currently (${dateKey} ${now.toTimeString().slice(0, 5)}): (no matching slot in today's schedule)`;
|
|
3233
2928
|
const days = [
|
|
3234
2929
|
{
|
|
3235
2930
|
label: "Yesterday",
|
|
@@ -3252,9 +2947,9 @@ ${currentBlock}
|
|
|
3252
2947
|
${blocks.join(`
|
|
3253
2948
|
`)}`;
|
|
3254
2949
|
}
|
|
3255
|
-
async getDailyScheduleSummary(
|
|
2950
|
+
async getDailyScheduleSummary(dateKey) {
|
|
3256
2951
|
try {
|
|
3257
|
-
const stored = await this.memory.get(`daily-schedule:${
|
|
2952
|
+
const stored = await this.memory.get(`daily-schedule:${dateKey}`);
|
|
3258
2953
|
if (!stored)
|
|
3259
2954
|
return null;
|
|
3260
2955
|
const schedule = JSON.parse(stored.content);
|
|
@@ -3325,7 +3020,6 @@ ${blocks.join(`
|
|
|
3325
3020
|
const personaInitInstruction = await loadPrompt("PERSONA_INIT");
|
|
3326
3021
|
log7.debug(`Brain.create: generating description`);
|
|
3327
3022
|
const description = await llm.call(llm.models.identity, {
|
|
3328
|
-
caller: "persona-init",
|
|
3329
3023
|
instruction: personaInitInstruction,
|
|
3330
3024
|
message: seed
|
|
3331
3025
|
});
|
|
@@ -3333,7 +3027,6 @@ ${blocks.join(`
|
|
|
3333
3027
|
const personaSystemInstruction = await loadPrompt("PERSONA_BASE_SYSTEM_PROMPT");
|
|
3334
3028
|
log7.debug(`Brain.create: generating base system prompt + dials`);
|
|
3335
3029
|
const generated = await llm.call(llm.models.identity, {
|
|
3336
|
-
caller: "base-system-prompt",
|
|
3337
3030
|
instruction: personaSystemInstruction,
|
|
3338
3031
|
message: description,
|
|
3339
3032
|
jsonSchemaName: "base-system-prompt",
|
|
@@ -3468,6 +3161,13 @@ function parseSearchArguments(json) {
|
|
|
3468
3161
|
}
|
|
3469
3162
|
return null;
|
|
3470
3163
|
}
|
|
3164
|
+
function stripAssistantForHistory(message) {
|
|
3165
|
+
return {
|
|
3166
|
+
role: "assistant",
|
|
3167
|
+
content: message.content,
|
|
3168
|
+
toolCalls: message.toolCalls
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3471
3171
|
|
|
3472
3172
|
// src/channel/discord.ts
|
|
3473
3173
|
import {
|
|
@@ -3487,18 +3187,10 @@ var SLEEP_MEMORY_CRON_KEY = "__sleep-memory__";
|
|
|
3487
3187
|
var SLEEP_MEMORY_CRON_PATTERN = "0 * * * *";
|
|
3488
3188
|
var START_CONVERSATION_CRON_KEY = "__start-conversation__";
|
|
3489
3189
|
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
|
-
];
|
|
3190
|
+
var DAILY_SCHEDULE_CRON_KEY = "__daily-schedule__";
|
|
3191
|
+
var DAILY_SCHEDULE_CRON_PATTERN = "0 0 * * *";
|
|
3192
|
+
var DAILY_SCHEDULE_NOON_CRON_KEY = "__daily-schedule-noon__";
|
|
3193
|
+
var DAILY_SCHEDULE_NOON_CRON_PATTERN = "0 12 * * *";
|
|
3502
3194
|
|
|
3503
3195
|
class BaseChannel {
|
|
3504
3196
|
brain;
|
|
@@ -3518,27 +3210,24 @@ class BaseChannel {
|
|
|
3518
3210
|
static activeChannels = new Map;
|
|
3519
3211
|
constructor(brain) {
|
|
3520
3212
|
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) {
|
|
3213
|
+
this.registerCron(SLEEP_MEMORY_CRON_KEY, SLEEP_MEMORY_CRON_PATTERN, async () => {
|
|
3214
|
+
const dateKey = formatDateKey(new Date);
|
|
3529
3215
|
const availability = await this.brain.getAvailability();
|
|
3530
3216
|
if (availability.status !== "offline") {
|
|
3531
3217
|
logger.debug(`sleepMemory cron: skip — availability=${availability.status}`);
|
|
3532
3218
|
return;
|
|
3533
3219
|
}
|
|
3534
|
-
const existing = await this.brain.memory.get(`daily-journal:${
|
|
3220
|
+
const existing = await this.brain.memory.get(`daily-journal:${dateKey}`);
|
|
3535
3221
|
if (existing) {
|
|
3536
|
-
logger.debug(`sleepMemory cron: skip — journal for ${
|
|
3222
|
+
logger.debug(`sleepMemory cron: skip — journal for ${dateKey} exists`);
|
|
3537
3223
|
return;
|
|
3538
3224
|
}
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3225
|
+
const history = await this.getMessageHistoryBetween(new Date(Date.now() - 24 * 60 * 60 * 1000), new Date);
|
|
3226
|
+
await this.brain.sleepMemory(new Date, history);
|
|
3227
|
+
});
|
|
3228
|
+
this.registerCron(DAILY_SCHEDULE_CRON_KEY, DAILY_SCHEDULE_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3229
|
+
this.registerCron(DAILY_SCHEDULE_NOON_CRON_KEY, DAILY_SCHEDULE_NOON_CRON_PATTERN, () => this.regenerateSchedules());
|
|
3230
|
+
this.registerCron(START_CONVERSATION_CRON_KEY, START_CONVERSATION_CRON_PATTERN, () => this.runStartConversation());
|
|
3542
3231
|
}
|
|
3543
3232
|
async regenerateSchedules() {
|
|
3544
3233
|
logger.debug(`regenerateSchedules: tick for ${this.brain.brainbase.displayName}`);
|
|
@@ -3559,8 +3248,8 @@ class BaseChannel {
|
|
|
3559
3248
|
return;
|
|
3560
3249
|
}
|
|
3561
3250
|
const now = new Date;
|
|
3562
|
-
const
|
|
3563
|
-
const count = this.startConversationCounters.get(
|
|
3251
|
+
const dateKey = formatDateKey(now);
|
|
3252
|
+
const count = this.startConversationCounters.get(dateKey) ?? 0;
|
|
3564
3253
|
const countThreshold = this.brain.brainbase.startConversationCountThreshold;
|
|
3565
3254
|
if (count >= countThreshold)
|
|
3566
3255
|
return;
|
|
@@ -3573,7 +3262,7 @@ class BaseChannel {
|
|
|
3573
3262
|
});
|
|
3574
3263
|
if (replies.length === 0)
|
|
3575
3264
|
return;
|
|
3576
|
-
this.startConversationCounters.set(
|
|
3265
|
+
this.startConversationCounters.set(dateKey, count + 1);
|
|
3577
3266
|
this.startConversationTimeout = true;
|
|
3578
3267
|
setTimeout(() => {
|
|
3579
3268
|
this.startConversationTimeout = false;
|
|
@@ -3602,7 +3291,7 @@ class BaseChannel {
|
|
|
3602
3291
|
}, callback);
|
|
3603
3292
|
}
|
|
3604
3293
|
getRegisteredCrons() {
|
|
3605
|
-
return scheduledJobs.filter((c) => c.name && c.name.startsWith(this.resolveCronPrefix()));
|
|
3294
|
+
return scheduledJobs.filter((c) => c.name && c.name.startsWith(this.resolveCronPrefix())).map((c) => c.name);
|
|
3606
3295
|
}
|
|
3607
3296
|
pauseCron(key) {
|
|
3608
3297
|
const job = scheduledJobs.find((c) => c.name === this.resolveCronName(key));
|
|
@@ -3693,13 +3382,6 @@ class BaseChannel {
|
|
|
3693
3382
|
this.isChattingDebounce = null;
|
|
3694
3383
|
}, IS_CHATTING_DEBOUNCE_MS);
|
|
3695
3384
|
}
|
|
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
3385
|
ensureAvailabilityWatcher() {
|
|
3704
3386
|
if (this.isCronStarted(AVAILABILITY_WATCHER_KEY))
|
|
3705
3387
|
return;
|
|
@@ -3709,9 +3391,6 @@ class BaseChannel {
|
|
|
3709
3391
|
const prev = this.previousAvailability;
|
|
3710
3392
|
this.previousAvailability = current.status;
|
|
3711
3393
|
logger.debug(`availabilityWatcher: ${prev ?? "(initial)"} → ${current.status}`);
|
|
3712
|
-
if (prev !== current.status) {
|
|
3713
|
-
await this.setAvailability(current.status);
|
|
3714
|
-
}
|
|
3715
3394
|
if (prev !== null && prev !== "online" && current.status === "online") {
|
|
3716
3395
|
await this.flushDeferred();
|
|
3717
3396
|
}
|
|
@@ -3760,88 +3439,6 @@ class BaseChannel {
|
|
|
3760
3439
|
static all() {
|
|
3761
3440
|
return Array.from(BaseChannel.activeChannels.values());
|
|
3762
3441
|
}
|
|
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
3442
|
static async shutdownAll() {
|
|
3846
3443
|
await Promise.all(BaseChannel.all().map((c) => c.shutdown()));
|
|
3847
3444
|
}
|
|
@@ -3854,8 +3451,8 @@ class BaseChannel {
|
|
|
3854
3451
|
logger.debug(`shutdown: done`);
|
|
3855
3452
|
}
|
|
3856
3453
|
stopOwnCrons() {
|
|
3857
|
-
for (const
|
|
3858
|
-
|
|
3454
|
+
for (const key of this.getRegisteredCrons()) {
|
|
3455
|
+
this.removeCron(key);
|
|
3859
3456
|
}
|
|
3860
3457
|
}
|
|
3861
3458
|
clearTimers() {
|
|
@@ -3965,7 +3562,6 @@ class DiscordChannel extends BaseChannel {
|
|
|
3965
3562
|
logger.debug(`DiscordClientReady: resolving configured channel ${channelId}`);
|
|
3966
3563
|
this.resolveConfiguredChannel(channelId);
|
|
3967
3564
|
}
|
|
3968
|
-
this.initAvailability();
|
|
3969
3565
|
});
|
|
3970
3566
|
this.client.on(Events.MessageCreate, (msg) => {
|
|
3971
3567
|
if (msg.author.bot)
|
|
@@ -4134,7 +3730,6 @@ class TelegramChannel extends BaseChannel {
|
|
|
4134
3730
|
this.registerActive();
|
|
4135
3731
|
this.bot.onStart(({ info }) => {
|
|
4136
3732
|
logger.success(`Telegram ready as @${info.username}`);
|
|
4137
|
-
this.initAvailability();
|
|
4138
3733
|
});
|
|
4139
3734
|
this.bot.on("message", (ctx) => {
|
|
4140
3735
|
if (ctx.from?.isBot())
|
|
@@ -4227,8 +3822,8 @@ class TelegramChannel extends BaseChannel {
|
|
|
4227
3822
|
|
|
4228
3823
|
// src/utils/daemonClient.ts
|
|
4229
3824
|
import { connect } from "node:net";
|
|
4230
|
-
import { join as
|
|
4231
|
-
var DAEMON_SOCKET_PATH =
|
|
3825
|
+
import { join as join3 } from "node:path";
|
|
3826
|
+
var DAEMON_SOCKET_PATH = join3(config.brainboxRoot, "daemon.sock");
|
|
4232
3827
|
async function sendToDaemon(payload) {
|
|
4233
3828
|
logger.debug(`sendToDaemon: command="${payload.command}" args=${JSON.stringify(payload.args) ?? "null"}`);
|
|
4234
3829
|
let reply;
|
|
@@ -4290,7 +3885,6 @@ function exchangeOnce(payload) {
|
|
|
4290
3885
|
// src/commands/daemon.ts
|
|
4291
3886
|
import { createServer } from "node:net";
|
|
4292
3887
|
import { chmodSync, unlinkSync } from "node:fs";
|
|
4293
|
-
import { join as join5 } from "node:path";
|
|
4294
3888
|
|
|
4295
3889
|
// src/commands/daemon/commands.ts
|
|
4296
3890
|
var log8 = logger.child("daemon-cmd");
|
|
@@ -4351,63 +3945,6 @@ defineCommand({
|
|
|
4351
3945
|
}
|
|
4352
3946
|
});
|
|
4353
3947
|
|
|
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
3948
|
// src/commands/daemon.ts
|
|
4412
3949
|
async function startChannels() {
|
|
4413
3950
|
const items = await brainManager.listAvailableBrain();
|
|
@@ -4442,10 +3979,7 @@ async function startChannels() {
|
|
|
4442
3979
|
return started;
|
|
4443
3980
|
}
|
|
4444
3981
|
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})`);
|
|
3982
|
+
logger.debug(`daemon: boot`);
|
|
4449
3983
|
const started = await startChannels();
|
|
4450
3984
|
if (started === 0) {
|
|
4451
3985
|
logger.info("No activated brains with channels. Daemon idling.");
|
|
@@ -4612,33 +4146,6 @@ async function removeBrain(brainId) {
|
|
|
4612
4146
|
}
|
|
4613
4147
|
logger.success(`Removed brain "${brain.displayName}" (${chalk2.cyan(brainId)})`);
|
|
4614
4148
|
}
|
|
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
4149
|
function register3(program) {
|
|
4643
4150
|
const cmd = registerCommand(program, {
|
|
4644
4151
|
name: "brain",
|
|
@@ -4649,8 +4156,6 @@ function register3(program) {
|
|
|
4649
4156
|
cmd.command("remove <brainId>").description("Remove a brain and its memory").action(removeBrain);
|
|
4650
4157
|
cmd.command("activate <brainId>").description("Activate a brain").action(activateBrain);
|
|
4651
4158
|
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
4159
|
return cmd;
|
|
4655
4160
|
}
|
|
4656
4161
|
|
|
@@ -4772,9 +4277,6 @@ function RawInput({
|
|
|
4772
4277
|
onSubmit
|
|
4773
4278
|
}) {
|
|
4774
4279
|
const [value, setValue] = useState(initialValue);
|
|
4775
|
-
useEffect(() => {
|
|
4776
|
-
setValue(initialValue);
|
|
4777
|
-
}, [prompt, initialValue]);
|
|
4778
4280
|
useInput((input, key) => {
|
|
4779
4281
|
if (key.return) {
|
|
4780
4282
|
onSubmit(value);
|
|
@@ -5632,7 +5134,7 @@ function ModelApp2({
|
|
|
5632
5134
|
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5633
5135
|
dimColor: true,
|
|
5634
5136
|
children: [
|
|
5635
|
-
"
|
|
5137
|
+
"e.g. ",
|
|
5636
5138
|
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5637
5139
|
color: "cyan",
|
|
5638
5140
|
children: [
|
|
@@ -5640,7 +5142,7 @@ function ModelApp2({
|
|
|
5640
5142
|
"/"
|
|
5641
5143
|
]
|
|
5642
5144
|
}, undefined, true, undefined, this),
|
|
5643
|
-
"model — fine-tune later with ",
|
|
5145
|
+
"model-name — fine-tune later with ",
|
|
5644
5146
|
/* @__PURE__ */ jsxDEV5(Text5, {
|
|
5645
5147
|
color: "cyan",
|
|
5646
5148
|
children: "brainbox model"
|
|
@@ -5655,15 +5157,13 @@ function ModelApp2({
|
|
|
5655
5157
|
setError("Model cannot be empty");
|
|
5656
5158
|
return;
|
|
5657
5159
|
}
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
if (full === prefix) {
|
|
5661
|
-
setError("Model cannot be empty");
|
|
5160
|
+
if (!value.startsWith(`${provider}/`)) {
|
|
5161
|
+
setError(`Must start with "${provider}/"`);
|
|
5662
5162
|
return;
|
|
5663
5163
|
}
|
|
5664
|
-
setModelSlot("identity",
|
|
5665
|
-
setModelSlot("conversation",
|
|
5666
|
-
onDone(
|
|
5164
|
+
setModelSlot("identity", value);
|
|
5165
|
+
setModelSlot("conversation", value);
|
|
5166
|
+
onDone(value);
|
|
5667
5167
|
}
|
|
5668
5168
|
}, undefined, false, undefined, this),
|
|
5669
5169
|
error && /* @__PURE__ */ jsxDEV5(Text5, {
|
|
@@ -5768,7 +5268,7 @@ function BrainApp({
|
|
|
5768
5268
|
}, undefined, false, undefined, this),
|
|
5769
5269
|
busy ? /* @__PURE__ */ jsxDEV5(Text5, {
|
|
5770
5270
|
dimColor: true,
|
|
5771
|
-
children: "Creating brain…
|
|
5271
|
+
children: "Creating brain…"
|
|
5772
5272
|
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(TextInput, {
|
|
5773
5273
|
prompt: "seed> ",
|
|
5774
5274
|
onSubmit: (raw) => {
|
|
@@ -5995,12 +5495,12 @@ function register8(program) {
|
|
|
5995
5495
|
|
|
5996
5496
|
// src/index.ts
|
|
5997
5497
|
var __filename3 = fileURLToPath2(import.meta.url);
|
|
5998
|
-
var __dirname3 =
|
|
5498
|
+
var __dirname3 = dirname4(__filename3);
|
|
5999
5499
|
logger.configure({ level: config.debug ? "debug" : "info" });
|
|
6000
5500
|
logger.debug(`brainbox starting (debug=${config.debug}, root=${config.brainboxRoot})`);
|
|
6001
5501
|
function getVersion() {
|
|
6002
5502
|
try {
|
|
6003
|
-
const pkgPath =
|
|
5503
|
+
const pkgPath = join4(__dirname3, "..", "package.json");
|
|
6004
5504
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
6005
5505
|
return pkg.version ?? "0.0.0";
|
|
6006
5506
|
} catch {
|