@ztimson/ai-utils 0.5.4 → 0.5.6
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 +16 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +215 -208
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,37 +1,43 @@
|
|
|
1
1
|
import * as S from "node:os";
|
|
2
|
-
import { objectMap as k, JSONAttemptParse as y, findByProp as x, JSONSanitize as b, clean as T, Http as M, consoleInterceptor as q, fn as P, ASet as
|
|
3
|
-
import { Anthropic as
|
|
4
|
-
import { OpenAI as
|
|
2
|
+
import { objectMap as k, JSONAttemptParse as y, findByProp as x, JSONSanitize as b, clean as T, Http as M, consoleInterceptor as q, fn as P, ASet as $ } from "@ztimson/utils";
|
|
3
|
+
import { Anthropic as A } from "@anthropic-ai/sdk";
|
|
4
|
+
import { OpenAI as E } from "openai";
|
|
5
5
|
import { Worker as v } from "worker_threads";
|
|
6
6
|
import { fileURLToPath as O } from "url";
|
|
7
7
|
import { join as R, dirname as U } from "path";
|
|
8
8
|
import { spawn as W } from "node:child_process";
|
|
9
9
|
import w from "node:fs/promises";
|
|
10
10
|
import L from "node:path";
|
|
11
|
-
import { createWorker as
|
|
11
|
+
import { createWorker as N } from "tesseract.js";
|
|
12
12
|
import "./embedder.mjs";
|
|
13
|
-
import * as
|
|
14
|
-
import { $ as
|
|
13
|
+
import * as C from "cheerio";
|
|
14
|
+
import { $ as I, $Sync as D } from "@ztimson/node-utils";
|
|
15
15
|
class j {
|
|
16
16
|
}
|
|
17
17
|
class J extends j {
|
|
18
18
|
constructor(t, e, s) {
|
|
19
|
-
super(), this.ai = t, this.apiToken = e, this.model = s, this.client = new
|
|
19
|
+
super(), this.ai = t, this.apiToken = e, this.model = s, this.client = new A({ apiKey: e });
|
|
20
20
|
}
|
|
21
21
|
client;
|
|
22
22
|
toStandard(t) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
c[r.is_error ? "error" : "content"] = r.content;
|
|
30
|
-
}), t[s].content = t[s].content.filter((r) => r.type == "text").map((r) => r.text).join(`
|
|
23
|
+
const e = Date.now(), s = [];
|
|
24
|
+
for (let o of t)
|
|
25
|
+
if (typeof o.content == "string")
|
|
26
|
+
s.push({ timestamp: e, ...o });
|
|
27
|
+
else {
|
|
28
|
+
const n = o.content?.filter((r) => r.type == "text").map((r) => r.text).join(`
|
|
31
29
|
|
|
32
|
-
`)
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
`);
|
|
31
|
+
n && s.push({ timestamp: e, role: o.role, content: n }), o.content.forEach((r) => {
|
|
32
|
+
if (r.type == "tool_use")
|
|
33
|
+
s.push({ timestamp: e, role: "tool", id: r.id, name: r.name, args: r.input, content: void 0 });
|
|
34
|
+
else if (r.type == "tool_result") {
|
|
35
|
+
const i = s.findLast((c) => c.id == r.tool_use_id);
|
|
36
|
+
i && (i[r.is_error ? "error" : "content"] = r.content);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return s;
|
|
35
41
|
}
|
|
36
42
|
fromStandard(t) {
|
|
37
43
|
for (let e = 0; e < t.length; e++)
|
|
@@ -48,78 +54,78 @@ class J extends j {
|
|
|
48
54
|
}
|
|
49
55
|
ask(t, e = {}) {
|
|
50
56
|
const s = new AbortController();
|
|
51
|
-
return Object.assign(new Promise(async (
|
|
52
|
-
|
|
57
|
+
return Object.assign(new Promise(async (o) => {
|
|
58
|
+
let n = this.fromStandard([...e.history || [], { role: "user", content: t, timestamp: Date.now() }]);
|
|
59
|
+
const r = e.tools || this.ai.options.llm?.tools || [], i = {
|
|
53
60
|
model: e.model || this.model,
|
|
54
61
|
max_tokens: e.max_tokens || this.ai.options.llm?.max_tokens || 4096,
|
|
55
62
|
system: e.system || this.ai.options.llm?.system || "",
|
|
56
63
|
temperature: e.temperature || this.ai.options.llm?.temperature || 0.7,
|
|
57
|
-
tools:
|
|
58
|
-
name:
|
|
59
|
-
description:
|
|
64
|
+
tools: r.map((m) => ({
|
|
65
|
+
name: m.name,
|
|
66
|
+
description: m.description,
|
|
60
67
|
input_schema: {
|
|
61
68
|
type: "object",
|
|
62
|
-
properties:
|
|
63
|
-
required:
|
|
69
|
+
properties: m.args ? k(m.args, (l, d) => ({ ...d, required: void 0 })) : {},
|
|
70
|
+
required: m.args ? Object.entries(m.args).filter((l) => l[1].required).map((l) => l[0]) : []
|
|
64
71
|
},
|
|
65
72
|
fn: void 0
|
|
66
73
|
})),
|
|
67
|
-
messages:
|
|
74
|
+
messages: n,
|
|
68
75
|
stream: !!e.stream
|
|
69
76
|
};
|
|
70
|
-
let
|
|
77
|
+
let c, a = !0;
|
|
71
78
|
do {
|
|
72
|
-
if (
|
|
73
|
-
throw
|
|
79
|
+
if (c = await this.client.messages.create(i).catch((l) => {
|
|
80
|
+
throw l.message += `
|
|
74
81
|
|
|
75
82
|
Messages:
|
|
76
|
-
${JSON.stringify(
|
|
83
|
+
${JSON.stringify(n, null, 2)}`, l;
|
|
77
84
|
}), e.stream) {
|
|
78
|
-
|
|
85
|
+
a ? a = !1 : e.stream({ text: `
|
|
79
86
|
|
|
80
|
-
` }),
|
|
81
|
-
for await (const
|
|
87
|
+
` }), c.content = [];
|
|
88
|
+
for await (const l of c) {
|
|
82
89
|
if (s.signal.aborted) break;
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
else if (
|
|
86
|
-
if (
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
} else
|
|
90
|
-
else if (
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
} else if (
|
|
90
|
+
if (l.type === "content_block_start")
|
|
91
|
+
l.content_block.type === "text" ? c.content.push({ type: "text", text: "" }) : l.content_block.type === "tool_use" && c.content.push({ type: "tool_use", id: l.content_block.id, name: l.content_block.name, input: "" });
|
|
92
|
+
else if (l.type === "content_block_delta")
|
|
93
|
+
if (l.delta.type === "text_delta") {
|
|
94
|
+
const d = l.delta.text;
|
|
95
|
+
c.content.at(-1).text += d, e.stream({ text: d });
|
|
96
|
+
} else l.delta.type === "input_json_delta" && (c.content.at(-1).input += l.delta.partial_json);
|
|
97
|
+
else if (l.type === "content_block_stop") {
|
|
98
|
+
const d = c.content.at(-1);
|
|
99
|
+
d.input != null && (d.input = d.input ? y(d.input, {}) : {});
|
|
100
|
+
} else if (l.type === "message_stop")
|
|
94
101
|
break;
|
|
95
102
|
}
|
|
96
103
|
}
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
if (e.stream && e.stream({ tool:
|
|
104
|
+
const m = c.content.filter((l) => l.type === "tool_use");
|
|
105
|
+
if (m.length && !s.signal.aborted) {
|
|
106
|
+
n.push({ role: "assistant", content: c.content });
|
|
107
|
+
const l = await Promise.all(m.map(async (d) => {
|
|
108
|
+
const p = r.find(x("name", d.name));
|
|
109
|
+
if (e.stream && e.stream({ tool: d.name }), !p) return { tool_use_id: d.id, is_error: !0, content: "Tool not found" };
|
|
103
110
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return { type: "tool_result", tool_use_id: u.id, is_error: !0, content: f?.message || f?.toString() || "Unknown" };
|
|
111
|
+
const u = await p.fn(d.input, e?.stream, this.ai);
|
|
112
|
+
return { type: "tool_result", tool_use_id: d.id, content: b(u) };
|
|
113
|
+
} catch (u) {
|
|
114
|
+
return { type: "tool_result", tool_use_id: d.id, is_error: !0, content: u?.message || u?.toString() || "Unknown" };
|
|
109
115
|
}
|
|
110
116
|
}));
|
|
111
|
-
|
|
117
|
+
n.push({ role: "user", content: l }), i.messages = n;
|
|
112
118
|
}
|
|
113
|
-
} while (!s.signal.aborted &&
|
|
114
|
-
|
|
119
|
+
} while (!s.signal.aborted && c.content.some((m) => m.type === "tool_use"));
|
|
120
|
+
n.push({ role: "assistant", content: c.content.filter((m) => m.type == "text").map((m) => m.text).join(`
|
|
115
121
|
|
|
116
|
-
`) }), this.toStandard(
|
|
122
|
+
`) }), n = this.toStandard(n), e.stream && e.stream({ done: !0 }), e.history && e.history.splice(0, e.history.length, ...n), o(n.at(-1)?.content);
|
|
117
123
|
}), { abort: () => s.abort() });
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
126
|
class _ extends j {
|
|
121
|
-
constructor(t, e, s,
|
|
122
|
-
super(), this.ai = t, this.host = e, this.token = s, this.model =
|
|
127
|
+
constructor(t, e, s, o) {
|
|
128
|
+
super(), this.ai = t, this.host = e, this.token = s, this.model = o, this.client = new E(T({
|
|
123
129
|
baseURL: e,
|
|
124
130
|
apiKey: s
|
|
125
131
|
}));
|
|
@@ -129,17 +135,17 @@ class _ extends j {
|
|
|
129
135
|
for (let e = 0; e < t.length; e++) {
|
|
130
136
|
const s = t[e];
|
|
131
137
|
if (s.role === "assistant" && s.tool_calls) {
|
|
132
|
-
const
|
|
138
|
+
const o = s.tool_calls.map((n) => ({
|
|
133
139
|
role: "tool",
|
|
134
|
-
id:
|
|
135
|
-
name:
|
|
136
|
-
args: y(
|
|
140
|
+
id: n.id,
|
|
141
|
+
name: n.function.name,
|
|
142
|
+
args: y(n.function.arguments, {}),
|
|
137
143
|
timestamp: s.timestamp
|
|
138
144
|
}));
|
|
139
|
-
t.splice(e, 1, ...
|
|
145
|
+
t.splice(e, 1, ...o), e += o.length - 1;
|
|
140
146
|
} else if (s.role === "tool" && s.content) {
|
|
141
|
-
const
|
|
142
|
-
|
|
147
|
+
const o = t.find((n) => s.tool_call_id == n.id);
|
|
148
|
+
o && (s.content.includes('"error":') ? o.error = s.content : o.content = s.content), t.splice(e, 1), e--;
|
|
143
149
|
}
|
|
144
150
|
t[e]?.timestamp || (t[e].timestamp = Date.now());
|
|
145
151
|
}
|
|
@@ -160,76 +166,77 @@ class _ extends j {
|
|
|
160
166
|
content: s.error || s.content
|
|
161
167
|
});
|
|
162
168
|
else {
|
|
163
|
-
const { timestamp:
|
|
164
|
-
e.push(
|
|
169
|
+
const { timestamp: o, ...n } = s;
|
|
170
|
+
e.push(n);
|
|
165
171
|
}
|
|
166
172
|
return e;
|
|
167
173
|
}, []);
|
|
168
174
|
}
|
|
169
175
|
ask(t, e = {}) {
|
|
170
176
|
const s = new AbortController();
|
|
171
|
-
return Object.assign(new Promise(async (
|
|
177
|
+
return Object.assign(new Promise(async (o, n) => {
|
|
172
178
|
e.system && e.history?.[0]?.role != "system" && e.history?.splice(0, 0, { role: "system", content: e.system, timestamp: Date.now() });
|
|
173
|
-
|
|
179
|
+
let r = this.fromStandard([...e.history || [], { role: "user", content: t, timestamp: Date.now() }]);
|
|
180
|
+
const i = e.tools || this.ai.options.llm?.tools || [], c = {
|
|
174
181
|
model: e.model || this.model,
|
|
175
|
-
messages:
|
|
182
|
+
messages: r,
|
|
176
183
|
stream: !!e.stream,
|
|
177
184
|
max_tokens: e.max_tokens || this.ai.options.llm?.max_tokens || 4096,
|
|
178
185
|
temperature: e.temperature || this.ai.options.llm?.temperature || 0.7,
|
|
179
|
-
tools:
|
|
186
|
+
tools: i.map((l) => ({
|
|
180
187
|
type: "function",
|
|
181
188
|
function: {
|
|
182
189
|
name: l.name,
|
|
183
190
|
description: l.description,
|
|
184
191
|
parameters: {
|
|
185
192
|
type: "object",
|
|
186
|
-
properties: l.args ? k(l.args, (
|
|
187
|
-
required: l.args ? Object.entries(l.args).filter((
|
|
193
|
+
properties: l.args ? k(l.args, (d, p) => ({ ...p, required: void 0 })) : {},
|
|
194
|
+
required: l.args ? Object.entries(l.args).filter((d) => d[1].required).map((d) => d[0]) : []
|
|
188
195
|
}
|
|
189
196
|
}
|
|
190
197
|
}))
|
|
191
198
|
};
|
|
192
|
-
let
|
|
199
|
+
let a, m = !0;
|
|
193
200
|
do {
|
|
194
|
-
if (
|
|
195
|
-
throw
|
|
201
|
+
if (a = await this.client.chat.completions.create(c).catch((d) => {
|
|
202
|
+
throw d.message += `
|
|
196
203
|
|
|
197
204
|
Messages:
|
|
198
|
-
${JSON.stringify(
|
|
205
|
+
${JSON.stringify(r, null, 2)}`, d;
|
|
199
206
|
}), e.stream) {
|
|
200
|
-
|
|
207
|
+
m ? m = !1 : e.stream({ text: `
|
|
201
208
|
|
|
202
|
-
` }),
|
|
203
|
-
for await (const
|
|
209
|
+
` }), a.choices = [{ message: { content: "", tool_calls: [] } }];
|
|
210
|
+
for await (const d of a) {
|
|
204
211
|
if (s.signal.aborted) break;
|
|
205
|
-
|
|
212
|
+
d.choices[0].delta.content && (a.choices[0].message.content += d.choices[0].delta.content, e.stream({ text: d.choices[0].delta.content })), d.choices[0].delta.tool_calls && (a.choices[0].message.tool_calls = d.choices[0].delta.tool_calls);
|
|
206
213
|
}
|
|
207
214
|
}
|
|
208
|
-
const l =
|
|
215
|
+
const l = a.choices[0].message.tool_calls || [];
|
|
209
216
|
if (l.length && !s.signal.aborted) {
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
if (e.stream && e.stream({ tool:
|
|
217
|
+
r.push(a.choices[0].message);
|
|
218
|
+
const d = await Promise.all(l.map(async (p) => {
|
|
219
|
+
const u = i?.find(x("name", p.function.name));
|
|
220
|
+
if (e.stream && e.stream({ tool: p.function.name }), !u) return { role: "tool", tool_call_id: p.id, content: '{"error": "Tool not found"}' };
|
|
214
221
|
try {
|
|
215
|
-
const f = y(
|
|
216
|
-
return { role: "tool", tool_call_id:
|
|
222
|
+
const f = y(p.function.arguments, {}), g = await u.fn(f, e.stream, this.ai);
|
|
223
|
+
return { role: "tool", tool_call_id: p.id, content: b(g) };
|
|
217
224
|
} catch (f) {
|
|
218
|
-
return { role: "tool", tool_call_id:
|
|
225
|
+
return { role: "tool", tool_call_id: p.id, content: b({ error: f?.message || f?.toString() || "Unknown" }) };
|
|
219
226
|
}
|
|
220
227
|
}));
|
|
221
|
-
|
|
228
|
+
r.push(...d), c.messages = r;
|
|
222
229
|
}
|
|
223
|
-
} while (!s.signal.aborted &&
|
|
224
|
-
|
|
230
|
+
} while (!s.signal.aborted && a.choices?.[0]?.message?.tool_calls?.length);
|
|
231
|
+
r.push({ role: "assistant", content: a.choices[0].message.content || "" }), r = this.toStandard(r), e.stream && e.stream({ done: !0 }), e.history && e.history.splice(0, e.history.length, ...r), o(r.at(-1)?.content);
|
|
225
232
|
}), { abort: () => s.abort() });
|
|
226
233
|
}
|
|
227
234
|
}
|
|
228
235
|
class H {
|
|
229
236
|
constructor(t) {
|
|
230
237
|
this.ai = t, this.embedWorker = new v(R(U(O(import.meta.url)), "embedder.js")), this.embedWorker.on("message", ({ id: e, embedding: s }) => {
|
|
231
|
-
const
|
|
232
|
-
|
|
238
|
+
const o = this.embedQueue.get(e);
|
|
239
|
+
o && (o.resolve(s), this.embedQueue.delete(e));
|
|
233
240
|
}), t.options.llm?.models && Object.entries(t.options.llm.models).forEach(([e, s]) => {
|
|
234
241
|
this.defaultModel || (this.defaultModel = e), s.proto == "anthropic" ? this.models[e] = new J(this.ai, s.token, e) : s.proto == "ollama" ? this.models[e] = new _(this.ai, s.host, "not-needed", e) : s.proto == "openai" && (this.models[e] = new _(this.ai, s.host || null, s.token, e));
|
|
235
242
|
});
|
|
@@ -248,22 +255,22 @@ class H {
|
|
|
248
255
|
ask(t, e = {}) {
|
|
249
256
|
const s = e.model || this.defaultModel;
|
|
250
257
|
if (!this.models[s]) throw new Error(`Model does not exist: ${s}`);
|
|
251
|
-
let
|
|
258
|
+
let o = () => {
|
|
252
259
|
};
|
|
253
|
-
return Object.assign(new Promise(async (
|
|
260
|
+
return Object.assign(new Promise(async (n) => {
|
|
254
261
|
if (e.history || (e.history = []), e.memory) {
|
|
255
262
|
e.system = (e.system || "") + `
|
|
256
263
|
You have passive persistent memory never make any mention of your memory capabilities and what you can/cannot remember
|
|
257
264
|
`;
|
|
258
|
-
const
|
|
259
|
-
const [
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
const i = async (a, m, l = 50) => {
|
|
266
|
+
const [d, p] = await Promise.all([
|
|
267
|
+
m ? this.embedding(m) : Promise.resolve(null),
|
|
268
|
+
a ? this.embedding(a) : Promise.resolve(null)
|
|
262
269
|
]);
|
|
263
|
-
return (e.memory || []).map((
|
|
264
|
-
},
|
|
265
|
-
|
|
266
|
-
` +
|
|
270
|
+
return (e.memory || []).map((u) => ({ ...u, score: d ? this.cosineSimilarity(u.embeddings[0], d[0].embedding) : 1 })).filter((u) => u.score >= 0.8).map((u) => ({ ...u, score: p ? this.cosineSimilarity(u.embeddings[1], p[0].embedding) : u.score })).filter((u) => u.score >= 0.2).toSorted((u, f) => u.score - f.score).slice(0, l);
|
|
271
|
+
}, c = await i(t);
|
|
272
|
+
c.length && e.history.push({ role: "assistant", content: `Things I remembered:
|
|
273
|
+
` + c.map((a) => `${a.owner}: ${a.fact}`).join(`
|
|
267
274
|
`) }), e.tools = [...e.tools || [], {
|
|
268
275
|
name: "read_memory",
|
|
269
276
|
description: "Check your long-term memory for more information",
|
|
@@ -272,32 +279,32 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
272
279
|
query: { type: "string", description: "Search memory based on a query, can be used with or without subject argument" },
|
|
273
280
|
limit: { type: "number", description: "Result limit, default 5" }
|
|
274
281
|
},
|
|
275
|
-
fn: (
|
|
276
|
-
if (!
|
|
277
|
-
return a
|
|
282
|
+
fn: (a) => {
|
|
283
|
+
if (!a.subject && !a.query) throw new Error("Either a subject or query argument is required");
|
|
284
|
+
return i(a.query, a.subject, a.limit || 5);
|
|
278
285
|
}
|
|
279
286
|
}];
|
|
280
287
|
}
|
|
281
|
-
const
|
|
288
|
+
const r = await this.models[s].ask(t, e);
|
|
282
289
|
if (e.memory) {
|
|
283
|
-
const
|
|
284
|
-
|
|
290
|
+
const i = e.history?.findIndex((c) => c.role == "assistant" && c.content.startsWith("Things I remembered:"));
|
|
291
|
+
i != null && i >= 0 && e.history?.splice(i, 1);
|
|
285
292
|
}
|
|
286
293
|
if (e.compress || e.memory) {
|
|
287
|
-
let
|
|
294
|
+
let i = null;
|
|
288
295
|
if (e.compress)
|
|
289
|
-
|
|
296
|
+
i = await this.ai.language.compressHistory(e.history, e.compress.max, e.compress.min, e), e.history.splice(0, e.history.length, ...i.history);
|
|
290
297
|
else {
|
|
291
|
-
const
|
|
292
|
-
|
|
298
|
+
const c = e.history?.findLastIndex((a) => a.role == "user") ?? -1;
|
|
299
|
+
i = await this.ai.language.compressHistory(c != -1 ? e.history.slice(c) : e.history, 0, 0, e);
|
|
293
300
|
}
|
|
294
301
|
if (e.memory) {
|
|
295
|
-
const
|
|
296
|
-
e.memory.splice(0, e.memory.length, ...
|
|
302
|
+
const c = e.memory.filter((a) => !i.memory.some((m) => this.cosineSimilarity(a.embeddings[1], m.embeddings[1]) > 0.8)).concat(i.memory);
|
|
303
|
+
e.memory.splice(0, e.memory.length, ...c);
|
|
297
304
|
}
|
|
298
305
|
}
|
|
299
|
-
return
|
|
300
|
-
}), { abort:
|
|
306
|
+
return n(r);
|
|
307
|
+
}), { abort: o });
|
|
301
308
|
}
|
|
302
309
|
/**
|
|
303
310
|
* Compress chat history to reduce context size
|
|
@@ -307,22 +314,22 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
307
314
|
* @param {LLMRequest} options LLM options
|
|
308
315
|
* @returns {Promise<LLMMessage[]>} New chat history will summary at index 0
|
|
309
316
|
*/
|
|
310
|
-
async compressHistory(t, e, s,
|
|
317
|
+
async compressHistory(t, e, s, o) {
|
|
311
318
|
if (this.estimateTokens(t) < e) return { history: t, memory: [] };
|
|
312
|
-
let
|
|
313
|
-
for (let
|
|
314
|
-
if (
|
|
319
|
+
let n = 0, r = 0;
|
|
320
|
+
for (let u of t.toReversed())
|
|
321
|
+
if (r += this.estimateTokens(u.content), r < s) n++;
|
|
315
322
|
else break;
|
|
316
|
-
if (t.length <=
|
|
317
|
-
const
|
|
323
|
+
if (t.length <= n) return { history: t, memory: [] };
|
|
324
|
+
const i = t[0].role == "system" ? t[0] : null, c = n == 0 ? [] : t.slice(-n), a = (n == 0 ? t : t.slice(0, -n)).filter((u) => u.role === "assistant" || u.role === "user"), m = await this.json(`Create the smallest summary possible, no more than 500 tokens. Create a list of NEW facts (split by subject [pro]noun and fact) about what you learned from this conversation that you didn't already know or get from a tool call or system prompt. Focus only on new information about people, topics, or facts. Avoid generating facts about the AI. Match this format: {summary: string, facts: [[subject, fact]]}
|
|
318
325
|
|
|
319
|
-
${
|
|
326
|
+
${a.map((u) => `${u.role}: ${u.content}`).join(`
|
|
320
327
|
|
|
321
|
-
`)}`, { model:
|
|
322
|
-
const g = await Promise.all([this.embedding(
|
|
323
|
-
return { owner:
|
|
324
|
-
})),
|
|
325
|
-
return
|
|
328
|
+
`)}`, { model: o?.model, temperature: o?.temperature || 0.3 }), l = /* @__PURE__ */ new Date(), d = await Promise.all((m?.facts || [])?.map(async ([u, f]) => {
|
|
329
|
+
const g = await Promise.all([this.embedding(u), this.embedding(`${u}: ${f}`)]);
|
|
330
|
+
return { owner: u, fact: f, embeddings: [g[0][0].embedding, g[1][0].embedding], timestamp: l };
|
|
331
|
+
})), p = [{ role: "assistant", content: `Conversation Summary: ${m?.summary}`, timestamp: Date.now() }, ...c];
|
|
332
|
+
return i && p.splice(0, 0, i), { history: p, memory: d };
|
|
326
333
|
}
|
|
327
334
|
/**
|
|
328
335
|
* Compare the difference between embeddings (calculates the angle between two vectors)
|
|
@@ -332,11 +339,11 @@ ${n.map((h) => `${h.role}: ${h.content}`).join(`
|
|
|
332
339
|
*/
|
|
333
340
|
cosineSimilarity(t, e) {
|
|
334
341
|
if (t.length !== e.length) throw new Error("Vectors must be same length");
|
|
335
|
-
let s = 0,
|
|
336
|
-
for (let
|
|
337
|
-
s += t[
|
|
338
|
-
const
|
|
339
|
-
return
|
|
342
|
+
let s = 0, o = 0, n = 0;
|
|
343
|
+
for (let i = 0; i < t.length; i++)
|
|
344
|
+
s += t[i] * e[i], o += t[i] * t[i], n += e[i] * e[i];
|
|
345
|
+
const r = Math.sqrt(o) * Math.sqrt(n);
|
|
346
|
+
return r === 0 ? 0 : s / r;
|
|
340
347
|
}
|
|
341
348
|
/**
|
|
342
349
|
* Chunk text into parts for AI digestion
|
|
@@ -346,25 +353,25 @@ ${n.map((h) => `${h.role}: ${h.content}`).join(`
|
|
|
346
353
|
* @returns {string[]} Chunked strings
|
|
347
354
|
*/
|
|
348
355
|
chunk(t, e = 500, s = 50) {
|
|
349
|
-
const
|
|
350
|
-
const
|
|
351
|
-
return typeof l == "object" && !Array.isArray(l) ?
|
|
352
|
-
}) : [],
|
|
353
|
-
`)).flatMap((
|
|
354
|
-
`]),
|
|
355
|
-
for (let
|
|
356
|
-
let
|
|
357
|
-
for (;
|
|
358
|
-
const
|
|
359
|
-
if (this.estimateTokens(
|
|
360
|
-
`)) > e &&
|
|
361
|
-
|
|
356
|
+
const o = (c, a = "") => c ? Object.entries(c).flatMap(([m, l]) => {
|
|
357
|
+
const d = a ? `${a}${isNaN(+m) ? `.${m}` : `[${m}]`}` : m;
|
|
358
|
+
return typeof l == "object" && !Array.isArray(l) ? o(l, d) : `${d}: ${Array.isArray(l) ? l.join(", ") : l}`;
|
|
359
|
+
}) : [], r = (typeof t == "object" ? o(t) : t.split(`
|
|
360
|
+
`)).flatMap((c) => [...c.split(/\s+/).filter(Boolean), `
|
|
361
|
+
`]), i = [];
|
|
362
|
+
for (let c = 0; c < r.length; ) {
|
|
363
|
+
let a = "", m = c;
|
|
364
|
+
for (; m < r.length; ) {
|
|
365
|
+
const d = a + (a ? " " : "") + r[m];
|
|
366
|
+
if (this.estimateTokens(d.replace(/\s*\n\s*/g, `
|
|
367
|
+
`)) > e && a) break;
|
|
368
|
+
a = d, m++;
|
|
362
369
|
}
|
|
363
|
-
const l =
|
|
370
|
+
const l = a.replace(/\s*\n\s*/g, `
|
|
364
371
|
`).trim();
|
|
365
|
-
l &&
|
|
372
|
+
l && i.push(l), c = Math.max(m - s, m === c ? c + 1 : m);
|
|
366
373
|
}
|
|
367
|
-
return
|
|
374
|
+
return i;
|
|
368
375
|
}
|
|
369
376
|
/**
|
|
370
377
|
* Create a vector representation of a string
|
|
@@ -374,20 +381,20 @@ ${n.map((h) => `${h.role}: ${h.content}`).join(`
|
|
|
374
381
|
* @returns {Promise<Awaited<{index: number, embedding: number[], text: string, tokens: number}>[]>} Chunked embeddings
|
|
375
382
|
*/
|
|
376
383
|
embedding(t, e = 500, s = 50) {
|
|
377
|
-
const
|
|
378
|
-
const
|
|
379
|
-
this.embedQueue.set(
|
|
380
|
-
id:
|
|
381
|
-
text:
|
|
384
|
+
const o = (r) => new Promise((i, c) => {
|
|
385
|
+
const a = this.embedId++;
|
|
386
|
+
this.embedQueue.set(a, { resolve: i, reject: c }), this.embedWorker?.postMessage({
|
|
387
|
+
id: a,
|
|
388
|
+
text: r,
|
|
382
389
|
model: this.ai.options?.embedder || "bge-small-en-v1.5",
|
|
383
390
|
path: this.ai.options.path
|
|
384
391
|
});
|
|
385
|
-
}),
|
|
386
|
-
return Promise.all(
|
|
387
|
-
index:
|
|
388
|
-
embedding: await r
|
|
389
|
-
text:
|
|
390
|
-
tokens: this.estimateTokens(
|
|
392
|
+
}), n = this.chunk(t, e, s);
|
|
393
|
+
return Promise.all(n.map(async (r, i) => ({
|
|
394
|
+
index: i,
|
|
395
|
+
embedding: await o(r),
|
|
396
|
+
text: r,
|
|
397
|
+
tokens: this.estimateTokens(r)
|
|
391
398
|
})));
|
|
392
399
|
}
|
|
393
400
|
/**
|
|
@@ -407,8 +414,8 @@ ${n.map((h) => `${h.role}: ${h.content}`).join(`
|
|
|
407
414
|
*/
|
|
408
415
|
fuzzyMatch(t, ...e) {
|
|
409
416
|
if (e.length < 2) throw new Error("Requires at least 2 strings to compare");
|
|
410
|
-
const s = (
|
|
411
|
-
return { avg:
|
|
417
|
+
const s = (r, i = 10) => r.toLowerCase().split("").map((c, a) => c.charCodeAt(0) * (a + 1) % i / i).slice(0, i), o = s(t), n = e.map((r) => s(r)).map((r) => this.cosineSimilarity(o, r));
|
|
418
|
+
return { avg: n.reduce((r, i) => r + i, 0) / n.length, max: Math.max(...n), similarities: n };
|
|
412
419
|
}
|
|
413
420
|
/**
|
|
414
421
|
* Ask a question with JSON response
|
|
@@ -419,8 +426,8 @@ ${n.map((h) => `${h.role}: ${h.content}`).join(`
|
|
|
419
426
|
async json(t, e) {
|
|
420
427
|
let s = await this.ask(t, { system: "Respond using a JSON blob matching any provided examples", ...e });
|
|
421
428
|
if (!s) return {};
|
|
422
|
-
const
|
|
423
|
-
return y(
|
|
429
|
+
const o = /```(?:.+)?\s*([\s\S]*?)```/.exec(s), n = o ? o[1].trim() : s;
|
|
430
|
+
return y(n, {});
|
|
424
431
|
}
|
|
425
432
|
/**
|
|
426
433
|
* Create a summary of some text
|
|
@@ -443,15 +450,15 @@ class z {
|
|
|
443
450
|
if (!this.ai.options.whisper?.binary) throw new Error("Whisper not configured");
|
|
444
451
|
let s = () => {
|
|
445
452
|
};
|
|
446
|
-
const
|
|
447
|
-
const
|
|
448
|
-
let
|
|
449
|
-
const
|
|
450
|
-
s = () =>
|
|
451
|
-
|
|
453
|
+
const o = new Promise(async (n, r) => {
|
|
454
|
+
const i = await this.downloadAsrModel(e);
|
|
455
|
+
let c = "";
|
|
456
|
+
const a = W(this.ai.options.whisper?.binary, ["-nt", "-np", "-m", i, "-f", t], { stdio: ["ignore", "pipe", "ignore"] });
|
|
457
|
+
s = () => a.kill("SIGTERM"), a.on("error", (m) => r(m)), a.stdout.on("data", (m) => c += m.toString()), a.on("close", (m) => {
|
|
458
|
+
m === 0 ? n(c.trim() || null) : r(new Error(`Exit code ${m}`));
|
|
452
459
|
});
|
|
453
460
|
});
|
|
454
|
-
return Object.assign(
|
|
461
|
+
return Object.assign(o, { abort: s });
|
|
455
462
|
}
|
|
456
463
|
async downloadAsrModel(t = this.whisperModel) {
|
|
457
464
|
if (!this.ai.options.whisper?.binary) throw new Error("Whisper not configured");
|
|
@@ -471,10 +478,10 @@ class F {
|
|
|
471
478
|
*/
|
|
472
479
|
ocr(t) {
|
|
473
480
|
let e;
|
|
474
|
-
const s = new Promise(async (
|
|
475
|
-
e = await
|
|
476
|
-
const { data:
|
|
477
|
-
await e.terminate(),
|
|
481
|
+
const s = new Promise(async (o) => {
|
|
482
|
+
e = await N(this.ai.options.tesseract?.model || "eng", 2, { cachePath: this.ai.options.path });
|
|
483
|
+
const { data: n } = await e.recognize(t);
|
|
484
|
+
await e.terminate(), o(n.text.trim() || null);
|
|
478
485
|
});
|
|
479
486
|
return Object.assign(s, { abort: () => e?.terminate() });
|
|
480
487
|
}
|
|
@@ -494,7 +501,7 @@ const B = {
|
|
|
494
501
|
name: "cli",
|
|
495
502
|
description: "Use the command line interface, returns any output",
|
|
496
503
|
args: { command: { type: "string", description: "Command to run", required: !0 } },
|
|
497
|
-
fn: (
|
|
504
|
+
fn: (h) => I`${h.command}`
|
|
498
505
|
}, ce = {
|
|
499
506
|
name: "get_datetime",
|
|
500
507
|
description: "Get current UTC date / time",
|
|
@@ -507,15 +514,15 @@ const B = {
|
|
|
507
514
|
language: { type: "string", description: "Execution language", enum: ["cli", "node", "python"], required: !0 },
|
|
508
515
|
code: { type: "string", description: "Code to execute", required: !0 }
|
|
509
516
|
},
|
|
510
|
-
fn: async (
|
|
517
|
+
fn: async (h, t, e) => {
|
|
511
518
|
try {
|
|
512
|
-
switch (
|
|
519
|
+
switch (h.type) {
|
|
513
520
|
case "bash":
|
|
514
|
-
return await B.fn({ command:
|
|
521
|
+
return await B.fn({ command: h.code }, t, e);
|
|
515
522
|
case "node":
|
|
516
|
-
return await G.fn({ code:
|
|
523
|
+
return await G.fn({ code: h.code }, t, e);
|
|
517
524
|
case "python":
|
|
518
|
-
return await Q.fn({ code:
|
|
525
|
+
return await Q.fn({ code: h.code }, t, e);
|
|
519
526
|
}
|
|
520
527
|
} catch (s) {
|
|
521
528
|
return { error: s?.message || s.toString() };
|
|
@@ -530,15 +537,15 @@ const B = {
|
|
|
530
537
|
headers: { type: "object", description: "HTTP headers to send", default: {} },
|
|
531
538
|
body: { type: "object", description: "HTTP body to send" }
|
|
532
539
|
},
|
|
533
|
-
fn: (
|
|
540
|
+
fn: (h) => new M({ url: h.url, headers: h.headers }).request({ method: h.method || "GET", body: h.body })
|
|
534
541
|
}, G = {
|
|
535
542
|
name: "exec_javascript",
|
|
536
543
|
description: "Execute commonjs javascript",
|
|
537
544
|
args: {
|
|
538
545
|
code: { type: "string", description: "CommonJS javascript", required: !0 }
|
|
539
546
|
},
|
|
540
|
-
fn: async (
|
|
541
|
-
const t = q(null), e = await P({ console: t },
|
|
547
|
+
fn: async (h) => {
|
|
548
|
+
const t = q(null), e = await P({ console: t }, h.code, !0).catch((s) => t.output.error.push(s));
|
|
542
549
|
return { ...t.output, return: e, stdout: void 0, stderr: void 0 };
|
|
543
550
|
}
|
|
544
551
|
}, Q = {
|
|
@@ -547,7 +554,7 @@ const B = {
|
|
|
547
554
|
args: {
|
|
548
555
|
code: { type: "string", description: "CommonJS javascript", required: !0 }
|
|
549
556
|
},
|
|
550
|
-
fn: async (
|
|
557
|
+
fn: async (h) => ({ result: D`python -c "${h.code}"` })
|
|
551
558
|
}, de = {
|
|
552
559
|
name: "read_webpage",
|
|
553
560
|
description: "Extract clean, structured content from a webpage. Use after web_search to read specific URLs",
|
|
@@ -555,25 +562,25 @@ const B = {
|
|
|
555
562
|
url: { type: "string", description: "URL to extract content from", required: !0 },
|
|
556
563
|
focus: { type: "string", description: 'Optional: What aspect to focus on (e.g., "pricing", "features", "contact info")' }
|
|
557
564
|
},
|
|
558
|
-
fn: async (
|
|
559
|
-
const t = await fetch(
|
|
560
|
-
throw new Error(`Failed to fetch: ${
|
|
561
|
-
}), e =
|
|
565
|
+
fn: async (h) => {
|
|
566
|
+
const t = await fetch(h.url, { headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" } }).then((r) => r.text()).catch((r) => {
|
|
567
|
+
throw new Error(`Failed to fetch: ${r.message}`);
|
|
568
|
+
}), e = C.load(t);
|
|
562
569
|
e('script, style, nav, footer, header, aside, iframe, noscript, [role="navigation"], [role="banner"], .ad, .ads, .cookie, .popup').remove();
|
|
563
570
|
const s = {
|
|
564
571
|
title: e('meta[property="og:title"]').attr("content") || e("title").text() || "",
|
|
565
572
|
description: e('meta[name="description"]').attr("content") || e('meta[property="og:description"]').attr("content") || ""
|
|
566
573
|
};
|
|
567
|
-
let
|
|
568
|
-
const
|
|
569
|
-
for (const
|
|
570
|
-
const
|
|
571
|
-
if (
|
|
572
|
-
|
|
574
|
+
let o = "";
|
|
575
|
+
const n = ["article", "main", '[role="main"]', ".content", ".post", ".entry", "body"];
|
|
576
|
+
for (const r of n) {
|
|
577
|
+
const i = e(r).first();
|
|
578
|
+
if (i.length && i.text().trim().length > 200) {
|
|
579
|
+
o = i.text();
|
|
573
580
|
break;
|
|
574
581
|
}
|
|
575
582
|
}
|
|
576
|
-
return
|
|
583
|
+
return o || (o = e("body").text()), o = o.replace(/\s+/g, " ").trim().slice(0, 8e3), { url: h.url, title: s.title.trim(), description: s.description.trim(), content: o, focus: h.focus };
|
|
577
584
|
}
|
|
578
585
|
}, ue = {
|
|
579
586
|
name: "web_search",
|
|
@@ -582,17 +589,17 @@ const B = {
|
|
|
582
589
|
query: { type: "string", description: "Search string", required: !0 },
|
|
583
590
|
length: { type: "string", description: "Number of results to return", default: 5 }
|
|
584
591
|
},
|
|
585
|
-
fn: async (
|
|
586
|
-
const t = await fetch(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(
|
|
592
|
+
fn: async (h) => {
|
|
593
|
+
const t = await fetch(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(h.query)}`, {
|
|
587
594
|
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Accept-Language": "en-US,en;q=0.9" }
|
|
588
|
-
}).then((
|
|
595
|
+
}).then((n) => n.text());
|
|
589
596
|
let e, s = /<a .*?href="(.+?)".+?<\/a>/g;
|
|
590
|
-
const
|
|
597
|
+
const o = new $();
|
|
591
598
|
for (; (e = s.exec(t)) !== null; ) {
|
|
592
|
-
let
|
|
593
|
-
if (
|
|
599
|
+
let n = /uddg=(.+)&?/.exec(decodeURIComponent(e[1]))?.[1];
|
|
600
|
+
if (n && (n = decodeURIComponent(n)), n && o.add(n), o.size >= (h.length || 5)) break;
|
|
594
601
|
}
|
|
595
|
-
return
|
|
602
|
+
return o;
|
|
596
603
|
}
|
|
597
604
|
};
|
|
598
605
|
export {
|