@ztimson/ai-utils 0.4.1 → 0.5.2

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