@ztimson/ai-utils 0.5.5 → 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 -207
- 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,77 +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
|
-
const
|
|
105
|
-
return { type: "tool_result", tool_use_id:
|
|
106
|
-
} catch (
|
|
107
|
-
return { type: "tool_result", tool_use_id:
|
|
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" };
|
|
108
115
|
}
|
|
109
116
|
}));
|
|
110
|
-
|
|
117
|
+
n.push({ role: "user", content: l }), i.messages = n;
|
|
111
118
|
}
|
|
112
|
-
} while (!s.signal.aborted &&
|
|
113
|
-
|
|
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(`
|
|
114
121
|
|
|
115
|
-
`) }), 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);
|
|
116
123
|
}), { abort: () => s.abort() });
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
126
|
class _ extends j {
|
|
120
|
-
constructor(t, e, s,
|
|
121
|
-
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({
|
|
122
129
|
baseURL: e,
|
|
123
130
|
apiKey: s
|
|
124
131
|
}));
|
|
@@ -128,17 +135,17 @@ class _ extends j {
|
|
|
128
135
|
for (let e = 0; e < t.length; e++) {
|
|
129
136
|
const s = t[e];
|
|
130
137
|
if (s.role === "assistant" && s.tool_calls) {
|
|
131
|
-
const
|
|
138
|
+
const o = s.tool_calls.map((n) => ({
|
|
132
139
|
role: "tool",
|
|
133
|
-
id:
|
|
134
|
-
name:
|
|
135
|
-
args: y(
|
|
140
|
+
id: n.id,
|
|
141
|
+
name: n.function.name,
|
|
142
|
+
args: y(n.function.arguments, {}),
|
|
136
143
|
timestamp: s.timestamp
|
|
137
144
|
}));
|
|
138
|
-
t.splice(e, 1, ...
|
|
145
|
+
t.splice(e, 1, ...o), e += o.length - 1;
|
|
139
146
|
} else if (s.role === "tool" && s.content) {
|
|
140
|
-
const
|
|
141
|
-
|
|
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--;
|
|
142
149
|
}
|
|
143
150
|
t[e]?.timestamp || (t[e].timestamp = Date.now());
|
|
144
151
|
}
|
|
@@ -159,76 +166,77 @@ class _ extends j {
|
|
|
159
166
|
content: s.error || s.content
|
|
160
167
|
});
|
|
161
168
|
else {
|
|
162
|
-
const { timestamp:
|
|
163
|
-
e.push(
|
|
169
|
+
const { timestamp: o, ...n } = s;
|
|
170
|
+
e.push(n);
|
|
164
171
|
}
|
|
165
172
|
return e;
|
|
166
173
|
}, []);
|
|
167
174
|
}
|
|
168
175
|
ask(t, e = {}) {
|
|
169
176
|
const s = new AbortController();
|
|
170
|
-
return Object.assign(new Promise(async (
|
|
177
|
+
return Object.assign(new Promise(async (o, n) => {
|
|
171
178
|
e.system && e.history?.[0]?.role != "system" && e.history?.splice(0, 0, { role: "system", content: e.system, timestamp: Date.now() });
|
|
172
|
-
|
|
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 = {
|
|
173
181
|
model: e.model || this.model,
|
|
174
|
-
messages:
|
|
182
|
+
messages: r,
|
|
175
183
|
stream: !!e.stream,
|
|
176
184
|
max_tokens: e.max_tokens || this.ai.options.llm?.max_tokens || 4096,
|
|
177
185
|
temperature: e.temperature || this.ai.options.llm?.temperature || 0.7,
|
|
178
|
-
tools:
|
|
186
|
+
tools: i.map((l) => ({
|
|
179
187
|
type: "function",
|
|
180
188
|
function: {
|
|
181
189
|
name: l.name,
|
|
182
190
|
description: l.description,
|
|
183
191
|
parameters: {
|
|
184
192
|
type: "object",
|
|
185
|
-
properties: l.args ? k(l.args, (
|
|
186
|
-
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]) : []
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
197
|
}))
|
|
190
198
|
};
|
|
191
|
-
let
|
|
199
|
+
let a, m = !0;
|
|
192
200
|
do {
|
|
193
|
-
if (
|
|
194
|
-
throw
|
|
201
|
+
if (a = await this.client.chat.completions.create(c).catch((d) => {
|
|
202
|
+
throw d.message += `
|
|
195
203
|
|
|
196
204
|
Messages:
|
|
197
|
-
${JSON.stringify(
|
|
205
|
+
${JSON.stringify(r, null, 2)}`, d;
|
|
198
206
|
}), e.stream) {
|
|
199
|
-
|
|
207
|
+
m ? m = !1 : e.stream({ text: `
|
|
200
208
|
|
|
201
|
-
` }),
|
|
202
|
-
for await (const
|
|
209
|
+
` }), a.choices = [{ message: { content: "", tool_calls: [] } }];
|
|
210
|
+
for await (const d of a) {
|
|
203
211
|
if (s.signal.aborted) break;
|
|
204
|
-
|
|
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);
|
|
205
213
|
}
|
|
206
214
|
}
|
|
207
|
-
const l =
|
|
215
|
+
const l = a.choices[0].message.tool_calls || [];
|
|
208
216
|
if (l.length && !s.signal.aborted) {
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
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"}' };
|
|
213
221
|
try {
|
|
214
|
-
const f = y(
|
|
215
|
-
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) };
|
|
216
224
|
} catch (f) {
|
|
217
|
-
return { role: "tool", tool_call_id:
|
|
225
|
+
return { role: "tool", tool_call_id: p.id, content: b({ error: f?.message || f?.toString() || "Unknown" }) };
|
|
218
226
|
}
|
|
219
227
|
}));
|
|
220
|
-
|
|
228
|
+
r.push(...d), c.messages = r;
|
|
221
229
|
}
|
|
222
|
-
} while (!s.signal.aborted &&
|
|
223
|
-
|
|
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);
|
|
224
232
|
}), { abort: () => s.abort() });
|
|
225
233
|
}
|
|
226
234
|
}
|
|
227
235
|
class H {
|
|
228
236
|
constructor(t) {
|
|
229
237
|
this.ai = t, this.embedWorker = new v(R(U(O(import.meta.url)), "embedder.js")), this.embedWorker.on("message", ({ id: e, embedding: s }) => {
|
|
230
|
-
const
|
|
231
|
-
|
|
238
|
+
const o = this.embedQueue.get(e);
|
|
239
|
+
o && (o.resolve(s), this.embedQueue.delete(e));
|
|
232
240
|
}), t.options.llm?.models && Object.entries(t.options.llm.models).forEach(([e, s]) => {
|
|
233
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));
|
|
234
242
|
});
|
|
@@ -247,22 +255,22 @@ class H {
|
|
|
247
255
|
ask(t, e = {}) {
|
|
248
256
|
const s = e.model || this.defaultModel;
|
|
249
257
|
if (!this.models[s]) throw new Error(`Model does not exist: ${s}`);
|
|
250
|
-
let
|
|
258
|
+
let o = () => {
|
|
251
259
|
};
|
|
252
|
-
return Object.assign(new Promise(async (
|
|
260
|
+
return Object.assign(new Promise(async (n) => {
|
|
253
261
|
if (e.history || (e.history = []), e.memory) {
|
|
254
262
|
e.system = (e.system || "") + `
|
|
255
263
|
You have passive persistent memory never make any mention of your memory capabilities and what you can/cannot remember
|
|
256
264
|
`;
|
|
257
|
-
const
|
|
258
|
-
const [
|
|
259
|
-
|
|
260
|
-
|
|
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)
|
|
261
269
|
]);
|
|
262
|
-
return (e.memory || []).map((
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
` +
|
|
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(`
|
|
266
274
|
`) }), e.tools = [...e.tools || [], {
|
|
267
275
|
name: "read_memory",
|
|
268
276
|
description: "Check your long-term memory for more information",
|
|
@@ -271,32 +279,32 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
271
279
|
query: { type: "string", description: "Search memory based on a query, can be used with or without subject argument" },
|
|
272
280
|
limit: { type: "number", description: "Result limit, default 5" }
|
|
273
281
|
},
|
|
274
|
-
fn: (
|
|
275
|
-
if (!
|
|
276
|
-
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);
|
|
277
285
|
}
|
|
278
286
|
}];
|
|
279
287
|
}
|
|
280
|
-
const
|
|
288
|
+
const r = await this.models[s].ask(t, e);
|
|
281
289
|
if (e.memory) {
|
|
282
|
-
const
|
|
283
|
-
|
|
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);
|
|
284
292
|
}
|
|
285
293
|
if (e.compress || e.memory) {
|
|
286
|
-
let
|
|
294
|
+
let i = null;
|
|
287
295
|
if (e.compress)
|
|
288
|
-
|
|
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);
|
|
289
297
|
else {
|
|
290
|
-
const
|
|
291
|
-
|
|
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);
|
|
292
300
|
}
|
|
293
301
|
if (e.memory) {
|
|
294
|
-
const
|
|
295
|
-
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);
|
|
296
304
|
}
|
|
297
305
|
}
|
|
298
|
-
return
|
|
299
|
-
}), { abort:
|
|
306
|
+
return n(r);
|
|
307
|
+
}), { abort: o });
|
|
300
308
|
}
|
|
301
309
|
/**
|
|
302
310
|
* Compress chat history to reduce context size
|
|
@@ -306,22 +314,22 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
306
314
|
* @param {LLMRequest} options LLM options
|
|
307
315
|
* @returns {Promise<LLMMessage[]>} New chat history will summary at index 0
|
|
308
316
|
*/
|
|
309
|
-
async compressHistory(t, e, s,
|
|
317
|
+
async compressHistory(t, e, s, o) {
|
|
310
318
|
if (this.estimateTokens(t) < e) return { history: t, memory: [] };
|
|
311
|
-
let
|
|
312
|
-
for (let
|
|
313
|
-
if (
|
|
319
|
+
let n = 0, r = 0;
|
|
320
|
+
for (let u of t.toReversed())
|
|
321
|
+
if (r += this.estimateTokens(u.content), r < s) n++;
|
|
314
322
|
else break;
|
|
315
|
-
if (t.length <=
|
|
316
|
-
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]]}
|
|
317
325
|
|
|
318
|
-
${
|
|
326
|
+
${a.map((u) => `${u.role}: ${u.content}`).join(`
|
|
319
327
|
|
|
320
|
-
`)}`, { model:
|
|
321
|
-
const g = await Promise.all([this.embedding(
|
|
322
|
-
return { owner:
|
|
323
|
-
})),
|
|
324
|
-
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 };
|
|
325
333
|
}
|
|
326
334
|
/**
|
|
327
335
|
* Compare the difference between embeddings (calculates the angle between two vectors)
|
|
@@ -331,11 +339,11 @@ ${n.map((h) => `${h.role}: ${h.content}`).join(`
|
|
|
331
339
|
*/
|
|
332
340
|
cosineSimilarity(t, e) {
|
|
333
341
|
if (t.length !== e.length) throw new Error("Vectors must be same length");
|
|
334
|
-
let s = 0,
|
|
335
|
-
for (let
|
|
336
|
-
s += t[
|
|
337
|
-
const
|
|
338
|
-
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;
|
|
339
347
|
}
|
|
340
348
|
/**
|
|
341
349
|
* Chunk text into parts for AI digestion
|
|
@@ -345,25 +353,25 @@ ${n.map((h) => `${h.role}: ${h.content}`).join(`
|
|
|
345
353
|
* @returns {string[]} Chunked strings
|
|
346
354
|
*/
|
|
347
355
|
chunk(t, e = 500, s = 50) {
|
|
348
|
-
const
|
|
349
|
-
const
|
|
350
|
-
return typeof l == "object" && !Array.isArray(l) ?
|
|
351
|
-
}) : [],
|
|
352
|
-
`)).flatMap((
|
|
353
|
-
`]),
|
|
354
|
-
for (let
|
|
355
|
-
let
|
|
356
|
-
for (;
|
|
357
|
-
const
|
|
358
|
-
if (this.estimateTokens(
|
|
359
|
-
`)) > e &&
|
|
360
|
-
|
|
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++;
|
|
361
369
|
}
|
|
362
|
-
const l =
|
|
370
|
+
const l = a.replace(/\s*\n\s*/g, `
|
|
363
371
|
`).trim();
|
|
364
|
-
l &&
|
|
372
|
+
l && i.push(l), c = Math.max(m - s, m === c ? c + 1 : m);
|
|
365
373
|
}
|
|
366
|
-
return
|
|
374
|
+
return i;
|
|
367
375
|
}
|
|
368
376
|
/**
|
|
369
377
|
* Create a vector representation of a string
|
|
@@ -373,20 +381,20 @@ ${n.map((h) => `${h.role}: ${h.content}`).join(`
|
|
|
373
381
|
* @returns {Promise<Awaited<{index: number, embedding: number[], text: string, tokens: number}>[]>} Chunked embeddings
|
|
374
382
|
*/
|
|
375
383
|
embedding(t, e = 500, s = 50) {
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
this.embedQueue.set(
|
|
379
|
-
id:
|
|
380
|
-
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,
|
|
381
389
|
model: this.ai.options?.embedder || "bge-small-en-v1.5",
|
|
382
390
|
path: this.ai.options.path
|
|
383
391
|
});
|
|
384
|
-
}),
|
|
385
|
-
return Promise.all(
|
|
386
|
-
index:
|
|
387
|
-
embedding: await r
|
|
388
|
-
text:
|
|
389
|
-
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)
|
|
390
398
|
})));
|
|
391
399
|
}
|
|
392
400
|
/**
|
|
@@ -406,8 +414,8 @@ ${n.map((h) => `${h.role}: ${h.content}`).join(`
|
|
|
406
414
|
*/
|
|
407
415
|
fuzzyMatch(t, ...e) {
|
|
408
416
|
if (e.length < 2) throw new Error("Requires at least 2 strings to compare");
|
|
409
|
-
const s = (
|
|
410
|
-
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 };
|
|
411
419
|
}
|
|
412
420
|
/**
|
|
413
421
|
* Ask a question with JSON response
|
|
@@ -418,8 +426,8 @@ ${n.map((h) => `${h.role}: ${h.content}`).join(`
|
|
|
418
426
|
async json(t, e) {
|
|
419
427
|
let s = await this.ask(t, { system: "Respond using a JSON blob matching any provided examples", ...e });
|
|
420
428
|
if (!s) return {};
|
|
421
|
-
const
|
|
422
|
-
return y(
|
|
429
|
+
const o = /```(?:.+)?\s*([\s\S]*?)```/.exec(s), n = o ? o[1].trim() : s;
|
|
430
|
+
return y(n, {});
|
|
423
431
|
}
|
|
424
432
|
/**
|
|
425
433
|
* Create a summary of some text
|
|
@@ -442,15 +450,15 @@ class z {
|
|
|
442
450
|
if (!this.ai.options.whisper?.binary) throw new Error("Whisper not configured");
|
|
443
451
|
let s = () => {
|
|
444
452
|
};
|
|
445
|
-
const
|
|
446
|
-
const
|
|
447
|
-
let
|
|
448
|
-
const
|
|
449
|
-
s = () =>
|
|
450
|
-
|
|
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}`));
|
|
451
459
|
});
|
|
452
460
|
});
|
|
453
|
-
return Object.assign(
|
|
461
|
+
return Object.assign(o, { abort: s });
|
|
454
462
|
}
|
|
455
463
|
async downloadAsrModel(t = this.whisperModel) {
|
|
456
464
|
if (!this.ai.options.whisper?.binary) throw new Error("Whisper not configured");
|
|
@@ -470,10 +478,10 @@ class F {
|
|
|
470
478
|
*/
|
|
471
479
|
ocr(t) {
|
|
472
480
|
let e;
|
|
473
|
-
const s = new Promise(async (
|
|
474
|
-
e = await
|
|
475
|
-
const { data:
|
|
476
|
-
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);
|
|
477
485
|
});
|
|
478
486
|
return Object.assign(s, { abort: () => e?.terminate() });
|
|
479
487
|
}
|
|
@@ -493,7 +501,7 @@ const B = {
|
|
|
493
501
|
name: "cli",
|
|
494
502
|
description: "Use the command line interface, returns any output",
|
|
495
503
|
args: { command: { type: "string", description: "Command to run", required: !0 } },
|
|
496
|
-
fn: (
|
|
504
|
+
fn: (h) => I`${h.command}`
|
|
497
505
|
}, ce = {
|
|
498
506
|
name: "get_datetime",
|
|
499
507
|
description: "Get current UTC date / time",
|
|
@@ -506,15 +514,15 @@ const B = {
|
|
|
506
514
|
language: { type: "string", description: "Execution language", enum: ["cli", "node", "python"], required: !0 },
|
|
507
515
|
code: { type: "string", description: "Code to execute", required: !0 }
|
|
508
516
|
},
|
|
509
|
-
fn: async (
|
|
517
|
+
fn: async (h, t, e) => {
|
|
510
518
|
try {
|
|
511
|
-
switch (
|
|
519
|
+
switch (h.type) {
|
|
512
520
|
case "bash":
|
|
513
|
-
return await B.fn({ command:
|
|
521
|
+
return await B.fn({ command: h.code }, t, e);
|
|
514
522
|
case "node":
|
|
515
|
-
return await G.fn({ code:
|
|
523
|
+
return await G.fn({ code: h.code }, t, e);
|
|
516
524
|
case "python":
|
|
517
|
-
return await Q.fn({ code:
|
|
525
|
+
return await Q.fn({ code: h.code }, t, e);
|
|
518
526
|
}
|
|
519
527
|
} catch (s) {
|
|
520
528
|
return { error: s?.message || s.toString() };
|
|
@@ -529,15 +537,15 @@ const B = {
|
|
|
529
537
|
headers: { type: "object", description: "HTTP headers to send", default: {} },
|
|
530
538
|
body: { type: "object", description: "HTTP body to send" }
|
|
531
539
|
},
|
|
532
|
-
fn: (
|
|
540
|
+
fn: (h) => new M({ url: h.url, headers: h.headers }).request({ method: h.method || "GET", body: h.body })
|
|
533
541
|
}, G = {
|
|
534
542
|
name: "exec_javascript",
|
|
535
543
|
description: "Execute commonjs javascript",
|
|
536
544
|
args: {
|
|
537
545
|
code: { type: "string", description: "CommonJS javascript", required: !0 }
|
|
538
546
|
},
|
|
539
|
-
fn: async (
|
|
540
|
-
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));
|
|
541
549
|
return { ...t.output, return: e, stdout: void 0, stderr: void 0 };
|
|
542
550
|
}
|
|
543
551
|
}, Q = {
|
|
@@ -546,7 +554,7 @@ const B = {
|
|
|
546
554
|
args: {
|
|
547
555
|
code: { type: "string", description: "CommonJS javascript", required: !0 }
|
|
548
556
|
},
|
|
549
|
-
fn: async (
|
|
557
|
+
fn: async (h) => ({ result: D`python -c "${h.code}"` })
|
|
550
558
|
}, de = {
|
|
551
559
|
name: "read_webpage",
|
|
552
560
|
description: "Extract clean, structured content from a webpage. Use after web_search to read specific URLs",
|
|
@@ -554,25 +562,25 @@ const B = {
|
|
|
554
562
|
url: { type: "string", description: "URL to extract content from", required: !0 },
|
|
555
563
|
focus: { type: "string", description: 'Optional: What aspect to focus on (e.g., "pricing", "features", "contact info")' }
|
|
556
564
|
},
|
|
557
|
-
fn: async (
|
|
558
|
-
const t = await fetch(
|
|
559
|
-
throw new Error(`Failed to fetch: ${
|
|
560
|
-
}), 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);
|
|
561
569
|
e('script, style, nav, footer, header, aside, iframe, noscript, [role="navigation"], [role="banner"], .ad, .ads, .cookie, .popup').remove();
|
|
562
570
|
const s = {
|
|
563
571
|
title: e('meta[property="og:title"]').attr("content") || e("title").text() || "",
|
|
564
572
|
description: e('meta[name="description"]').attr("content") || e('meta[property="og:description"]').attr("content") || ""
|
|
565
573
|
};
|
|
566
|
-
let
|
|
567
|
-
const
|
|
568
|
-
for (const
|
|
569
|
-
const
|
|
570
|
-
if (
|
|
571
|
-
|
|
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();
|
|
572
580
|
break;
|
|
573
581
|
}
|
|
574
582
|
}
|
|
575
|
-
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 };
|
|
576
584
|
}
|
|
577
585
|
}, ue = {
|
|
578
586
|
name: "web_search",
|
|
@@ -581,17 +589,17 @@ const B = {
|
|
|
581
589
|
query: { type: "string", description: "Search string", required: !0 },
|
|
582
590
|
length: { type: "string", description: "Number of results to return", default: 5 }
|
|
583
591
|
},
|
|
584
|
-
fn: async (
|
|
585
|
-
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)}`, {
|
|
586
594
|
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Accept-Language": "en-US,en;q=0.9" }
|
|
587
|
-
}).then((
|
|
595
|
+
}).then((n) => n.text());
|
|
588
596
|
let e, s = /<a .*?href="(.+?)".+?<\/a>/g;
|
|
589
|
-
const
|
|
597
|
+
const o = new $();
|
|
590
598
|
for (; (e = s.exec(t)) !== null; ) {
|
|
591
|
-
let
|
|
592
|
-
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;
|
|
593
601
|
}
|
|
594
|
-
return
|
|
602
|
+
return o;
|
|
595
603
|
}
|
|
596
604
|
};
|
|
597
605
|
export {
|