@ztimson/ai-utils 0.8.10 → 0.8.11
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 +25 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +327 -279
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
import * as O from "node:os";
|
|
2
|
-
import { tmpdir as
|
|
3
|
-
import { objectMap as
|
|
4
|
-
import { Anthropic as
|
|
5
|
-
import { OpenAI as
|
|
6
|
-
import { fileURLToPath as
|
|
7
|
-
import { join as I, dirname as
|
|
8
|
-
import { spawn as
|
|
2
|
+
import { tmpdir as U } from "node:os";
|
|
3
|
+
import { objectMap as z, JSONAttemptParse as _, findByProp as R, JSONSanitize as S, clean as W, Http as C, consoleInterceptor as N, fn as J, ASet as F } from "@ztimson/utils";
|
|
4
|
+
import { Anthropic as B } from "@anthropic-ai/sdk";
|
|
5
|
+
import { OpenAI as D } from "openai";
|
|
6
|
+
import { fileURLToPath as H } from "url";
|
|
7
|
+
import { join as I, dirname as G } from "path";
|
|
8
|
+
import { spawn as w, execSync as K } from "node:child_process";
|
|
9
9
|
import { mkdtempSync as V } from "node:fs";
|
|
10
|
-
import
|
|
10
|
+
import b from "node:fs/promises";
|
|
11
11
|
import * as M from "node:path";
|
|
12
|
-
import
|
|
12
|
+
import q, { join as A } from "node:path";
|
|
13
13
|
import { createWorker as Y } from "tesseract.js";
|
|
14
14
|
import * as Z from "cheerio";
|
|
15
15
|
import { $Sync as j } from "@ztimson/node-utils";
|
|
16
|
-
class
|
|
16
|
+
class L {
|
|
17
17
|
}
|
|
18
|
-
class Q extends
|
|
18
|
+
class Q extends L {
|
|
19
19
|
constructor(r, e, t) {
|
|
20
|
-
super(), this.ai = r, this.apiToken = e, this.model = t, this.client = new
|
|
20
|
+
super(), this.ai = r, this.apiToken = e, this.model = t, this.client = new B({ apiKey: e });
|
|
21
21
|
}
|
|
22
22
|
client;
|
|
23
23
|
toStandard(r) {
|
|
24
24
|
const e = Date.now(), t = [];
|
|
25
|
-
for (let
|
|
26
|
-
if (typeof
|
|
27
|
-
t.push({ timestamp: e, ...
|
|
25
|
+
for (let i of r)
|
|
26
|
+
if (typeof i.content == "string")
|
|
27
|
+
t.push({ timestamp: e, ...i });
|
|
28
28
|
else {
|
|
29
|
-
const
|
|
29
|
+
const o = i.content?.filter((n) => n.type == "text").map((n) => n.text).join(`
|
|
30
30
|
|
|
31
31
|
`);
|
|
32
|
-
|
|
32
|
+
o && t.push({ timestamp: e, role: i.role, content: o }), i.content.forEach((n) => {
|
|
33
33
|
if (n.type == "tool_use")
|
|
34
34
|
t.push({ timestamp: e, role: "tool", id: n.id, name: n.name, args: n.input, content: void 0 });
|
|
35
35
|
else if (n.type == "tool_result") {
|
|
36
|
-
const
|
|
37
|
-
|
|
36
|
+
const u = t.findLast((l) => l.id == n.tool_use_id);
|
|
37
|
+
u && (u[n.is_error ? "error" : "content"] = n.content);
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
}
|
|
@@ -55,9 +55,9 @@ class Q extends z {
|
|
|
55
55
|
}
|
|
56
56
|
ask(r, e = {}) {
|
|
57
57
|
const t = new AbortController();
|
|
58
|
-
return Object.assign(new Promise(async (
|
|
59
|
-
let
|
|
60
|
-
const n = e.tools || this.ai.options.llm?.tools || [],
|
|
58
|
+
return Object.assign(new Promise(async (i) => {
|
|
59
|
+
let o = this.fromStandard([...e.history || [], { role: "user", content: r, timestamp: Date.now() }]);
|
|
60
|
+
const n = e.tools || this.ai.options.llm?.tools || [], u = {
|
|
61
61
|
model: e.model || this.model,
|
|
62
62
|
max_tokens: e.max_tokens || this.ai.options.llm?.max_tokens || 4096,
|
|
63
63
|
system: e.system || this.ai.options.llm?.system || "",
|
|
@@ -67,66 +67,66 @@ class Q extends z {
|
|
|
67
67
|
description: m.description,
|
|
68
68
|
input_schema: {
|
|
69
69
|
type: "object",
|
|
70
|
-
properties: m.args ?
|
|
71
|
-
required: m.args ? Object.entries(m.args).filter((
|
|
70
|
+
properties: m.args ? z(m.args, (s, a) => ({ ...a, required: void 0 })) : {},
|
|
71
|
+
required: m.args ? Object.entries(m.args).filter((s) => s[1].required).map((s) => s[0]) : []
|
|
72
72
|
},
|
|
73
73
|
fn: void 0
|
|
74
74
|
})),
|
|
75
|
-
messages:
|
|
75
|
+
messages: o,
|
|
76
76
|
stream: !!e.stream
|
|
77
77
|
};
|
|
78
|
-
let
|
|
78
|
+
let l, c = !0;
|
|
79
79
|
do {
|
|
80
|
-
if (
|
|
81
|
-
throw
|
|
80
|
+
if (l = await this.client.messages.create(u).catch((s) => {
|
|
81
|
+
throw s.message += `
|
|
82
82
|
|
|
83
83
|
Messages:
|
|
84
|
-
${JSON.stringify(
|
|
84
|
+
${JSON.stringify(o, null, 2)}`, s;
|
|
85
85
|
}), e.stream) {
|
|
86
|
-
|
|
86
|
+
c ? c = !1 : e.stream({ text: `
|
|
87
87
|
|
|
88
|
-
` }),
|
|
89
|
-
for await (const
|
|
88
|
+
` }), l.content = [];
|
|
89
|
+
for await (const s of l) {
|
|
90
90
|
if (t.signal.aborted) break;
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
else if (
|
|
94
|
-
if (
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
} else
|
|
98
|
-
else if (
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
} else if (
|
|
91
|
+
if (s.type === "content_block_start")
|
|
92
|
+
s.content_block.type === "text" ? l.content.push({ type: "text", text: "" }) : s.content_block.type === "tool_use" && l.content.push({ type: "tool_use", id: s.content_block.id, name: s.content_block.name, input: "" });
|
|
93
|
+
else if (s.type === "content_block_delta")
|
|
94
|
+
if (s.delta.type === "text_delta") {
|
|
95
|
+
const a = s.delta.text;
|
|
96
|
+
l.content.at(-1).text += a, e.stream({ text: a });
|
|
97
|
+
} else s.delta.type === "input_json_delta" && (l.content.at(-1).input += s.delta.partial_json);
|
|
98
|
+
else if (s.type === "content_block_stop") {
|
|
99
|
+
const a = l.content.at(-1);
|
|
100
|
+
a.input != null && (a.input = a.input ? _(a.input, {}) : {});
|
|
101
|
+
} else if (s.type === "message_stop")
|
|
102
102
|
break;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
const m =
|
|
105
|
+
const m = l.content.filter((s) => s.type === "tool_use");
|
|
106
106
|
if (m.length && !t.signal.aborted) {
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
const d = n.find(
|
|
110
|
-
if (e.stream && e.stream({ tool:
|
|
107
|
+
o.push({ role: "assistant", content: l.content });
|
|
108
|
+
const s = await Promise.all(m.map(async (a) => {
|
|
109
|
+
const d = n.find(R("name", a.name));
|
|
110
|
+
if (e.stream && e.stream({ tool: a.name }), !d) return { tool_use_id: a.id, is_error: !0, content: "Tool not found" };
|
|
111
111
|
try {
|
|
112
|
-
const p = await d.fn(
|
|
113
|
-
return { type: "tool_result", tool_use_id:
|
|
112
|
+
const p = await d.fn(a.input, e?.stream, this.ai);
|
|
113
|
+
return { type: "tool_result", tool_use_id: a.id, content: S(p) };
|
|
114
114
|
} catch (p) {
|
|
115
|
-
return { type: "tool_result", tool_use_id:
|
|
115
|
+
return { type: "tool_result", tool_use_id: a.id, is_error: !0, content: p?.message || p?.toString() || "Unknown" };
|
|
116
116
|
}
|
|
117
117
|
}));
|
|
118
|
-
|
|
118
|
+
o.push({ role: "user", content: s }), u.messages = o;
|
|
119
119
|
}
|
|
120
|
-
} while (!t.signal.aborted &&
|
|
121
|
-
|
|
120
|
+
} while (!t.signal.aborted && l.content.some((m) => m.type === "tool_use"));
|
|
121
|
+
o.push({ role: "assistant", content: l.content.filter((m) => m.type == "text").map((m) => m.text).join(`
|
|
122
122
|
|
|
123
|
-
`) }),
|
|
123
|
+
`) }), o = this.toStandard(o), e.stream && e.stream({ done: !0 }), e.history && e.history.splice(0, e.history.length, ...o), i(o.at(-1)?.content);
|
|
124
124
|
}), { abort: () => t.abort() });
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
class
|
|
128
|
-
constructor(r, e, t,
|
|
129
|
-
super(), this.ai = r, this.host = e, this.token = t, this.model =
|
|
127
|
+
class P extends L {
|
|
128
|
+
constructor(r, e, t, i) {
|
|
129
|
+
super(), this.ai = r, this.host = e, this.token = t, this.model = i, this.client = new D(W({
|
|
130
130
|
baseURL: e,
|
|
131
131
|
apiKey: t || e ? "ignored" : void 0
|
|
132
132
|
}));
|
|
@@ -136,17 +136,17 @@ class A extends z {
|
|
|
136
136
|
for (let e = 0; e < r.length; e++) {
|
|
137
137
|
const t = r[e];
|
|
138
138
|
if (t.role === "assistant" && t.tool_calls) {
|
|
139
|
-
const
|
|
139
|
+
const i = t.tool_calls.map((o) => ({
|
|
140
140
|
role: "tool",
|
|
141
|
-
id:
|
|
142
|
-
name:
|
|
143
|
-
args: _(
|
|
141
|
+
id: o.id,
|
|
142
|
+
name: o.function.name,
|
|
143
|
+
args: _(o.function.arguments, {}),
|
|
144
144
|
timestamp: t.timestamp
|
|
145
145
|
}));
|
|
146
|
-
r.splice(e, 1, ...
|
|
146
|
+
r.splice(e, 1, ...i), e += i.length - 1;
|
|
147
147
|
} else if (t.role === "tool" && t.content) {
|
|
148
|
-
const
|
|
149
|
-
|
|
148
|
+
const i = r.find((o) => t.tool_call_id == o.id);
|
|
149
|
+
i && (t.content.includes('"error":') ? i.error = t.content : i.content = t.content), r.splice(e, 1), e--;
|
|
150
150
|
}
|
|
151
151
|
r[e]?.timestamp || (r[e].timestamp = Date.now());
|
|
152
152
|
}
|
|
@@ -167,53 +167,53 @@ class A extends z {
|
|
|
167
167
|
content: t.error || t.content
|
|
168
168
|
});
|
|
169
169
|
else {
|
|
170
|
-
const { timestamp:
|
|
171
|
-
e.push(
|
|
170
|
+
const { timestamp: i, ...o } = t;
|
|
171
|
+
e.push(o);
|
|
172
172
|
}
|
|
173
173
|
return e;
|
|
174
174
|
}, []);
|
|
175
175
|
}
|
|
176
176
|
ask(r, e = {}) {
|
|
177
177
|
const t = new AbortController();
|
|
178
|
-
return Object.assign(new Promise(async (
|
|
178
|
+
return Object.assign(new Promise(async (i, o) => {
|
|
179
179
|
e.system && (e.history?.[0]?.role != "system" ? e.history?.splice(0, 0, { role: "system", content: e.system, timestamp: Date.now() }) : e.history[0].content = e.system);
|
|
180
180
|
let n = this.fromStandard([...e.history || [], { role: "user", content: r, timestamp: Date.now() }]);
|
|
181
|
-
const
|
|
181
|
+
const u = e.tools || this.ai.options.llm?.tools || [], l = {
|
|
182
182
|
model: e.model || this.model,
|
|
183
183
|
messages: n,
|
|
184
184
|
stream: !!e.stream,
|
|
185
185
|
max_tokens: e.max_tokens || this.ai.options.llm?.max_tokens || 4096,
|
|
186
186
|
temperature: e.temperature || this.ai.options.llm?.temperature || 0.7,
|
|
187
|
-
tools:
|
|
187
|
+
tools: u.map((s) => ({
|
|
188
188
|
type: "function",
|
|
189
189
|
function: {
|
|
190
|
-
name:
|
|
191
|
-
description:
|
|
190
|
+
name: s.name,
|
|
191
|
+
description: s.description,
|
|
192
192
|
parameters: {
|
|
193
193
|
type: "object",
|
|
194
|
-
properties:
|
|
195
|
-
required:
|
|
194
|
+
properties: s.args ? z(s.args, (a, d) => ({ ...d, required: void 0 })) : {},
|
|
195
|
+
required: s.args ? Object.entries(s.args).filter((a) => a[1].required).map((a) => a[0]) : []
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
}))
|
|
199
199
|
};
|
|
200
|
-
let
|
|
200
|
+
let c, m = !0;
|
|
201
201
|
do {
|
|
202
|
-
if (
|
|
203
|
-
throw
|
|
202
|
+
if (c = await this.client.chat.completions.create(l).catch((a) => {
|
|
203
|
+
throw a.message += `
|
|
204
204
|
|
|
205
205
|
Messages:
|
|
206
|
-
${JSON.stringify(n, null, 2)}`,
|
|
206
|
+
${JSON.stringify(n, null, 2)}`, a;
|
|
207
207
|
}), e.stream) {
|
|
208
208
|
m ? m = !1 : e.stream({ text: `
|
|
209
209
|
|
|
210
|
-
` }),
|
|
211
|
-
for await (const
|
|
210
|
+
` }), c.choices = [{ message: { role: "assistant", content: "", tool_calls: [] } }];
|
|
211
|
+
for await (const a of c) {
|
|
212
212
|
if (t.signal.aborted) break;
|
|
213
|
-
if (
|
|
214
|
-
for (const d of
|
|
215
|
-
const p =
|
|
216
|
-
p ? (d.id && (p.id = d.id), d.type && (p.type = d.type), d.function && (p.function || (p.function = {}), d.function.name && (p.function.name = d.function.name), d.function.arguments && (p.function.arguments = (p.function.arguments || "") + d.function.arguments))) :
|
|
213
|
+
if (a.choices[0].delta.content && (c.choices[0].message.content += a.choices[0].delta.content, e.stream({ text: a.choices[0].delta.content })), a.choices[0].delta.tool_calls)
|
|
214
|
+
for (const d of a.choices[0].delta.tool_calls) {
|
|
215
|
+
const p = c.choices[0].message.tool_calls.find((f) => f.index === d.index);
|
|
216
|
+
p ? (d.id && (p.id = d.id), d.type && (p.type = d.type), d.function && (p.function || (p.function = {}), d.function.name && (p.function.name = d.function.name), d.function.arguments && (p.function.arguments = (p.function.arguments || "") + d.function.arguments))) : c.choices[0].message.tool_calls.push({
|
|
217
217
|
index: d.index,
|
|
218
218
|
id: d.id || "",
|
|
219
219
|
type: d.type || "function",
|
|
@@ -225,11 +225,11 @@ ${JSON.stringify(n, null, 2)}`, l;
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
|
-
const
|
|
229
|
-
if (
|
|
230
|
-
n.push(
|
|
231
|
-
const
|
|
232
|
-
const p =
|
|
228
|
+
const s = c.choices[0].message.tool_calls || [];
|
|
229
|
+
if (s.length && !t.signal.aborted) {
|
|
230
|
+
n.push(c.choices[0].message);
|
|
231
|
+
const a = await Promise.all(s.map(async (d) => {
|
|
232
|
+
const p = u?.find(R("name", d.function.name));
|
|
233
233
|
if (e.stream && e.stream({ tool: d.function.name }), !p) return { role: "tool", tool_call_id: d.id, content: '{"error": "Tool not found"}' };
|
|
234
234
|
try {
|
|
235
235
|
const f = _(d.function.arguments, {}), g = await p.fn(f, e.stream, this.ai);
|
|
@@ -238,17 +238,17 @@ ${JSON.stringify(n, null, 2)}`, l;
|
|
|
238
238
|
return { role: "tool", tool_call_id: d.id, content: S({ error: f?.message || f?.toString() || "Unknown" }) };
|
|
239
239
|
}
|
|
240
240
|
}));
|
|
241
|
-
n.push(...
|
|
241
|
+
n.push(...a), l.messages = n;
|
|
242
242
|
}
|
|
243
|
-
} while (!t.signal.aborted &&
|
|
244
|
-
n.push({ role: "assistant", content:
|
|
243
|
+
} while (!t.signal.aborted && c.choices?.[0]?.message?.tool_calls?.length);
|
|
244
|
+
n.push({ role: "assistant", content: c.choices[0].message.content || "" }), n = this.toStandard(n), e.stream && e.stream({ done: !0 }), e.history && e.history.splice(0, e.history.length, ...n), i(n.at(-1)?.content);
|
|
245
245
|
}), { abort: () => t.abort() });
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
class X {
|
|
249
249
|
constructor(r) {
|
|
250
250
|
this.ai = r, r.options.llm?.models && Object.entries(r.options.llm.models).forEach(([e, t]) => {
|
|
251
|
-
this.defaultModel || (this.defaultModel = e), t.proto == "anthropic" ? this.models[e] = new Q(this.ai, t.token, e) : t.proto == "ollama" ? this.models[e] = new
|
|
251
|
+
this.defaultModel || (this.defaultModel = e), t.proto == "anthropic" ? this.models[e] = new Q(this.ai, t.token, e) : t.proto == "ollama" ? this.models[e] = new P(this.ai, t.host, "not-needed", e) : t.proto == "openai" && (this.models[e] = new P(this.ai, t.host || null, t.token, e));
|
|
252
252
|
});
|
|
253
253
|
}
|
|
254
254
|
defaultModel;
|
|
@@ -270,27 +270,27 @@ class X {
|
|
|
270
270
|
};
|
|
271
271
|
const t = e.model || this.defaultModel;
|
|
272
272
|
if (!this.models[t]) throw new Error(`Model does not exist: ${t}`);
|
|
273
|
-
let
|
|
273
|
+
let i = () => {
|
|
274
274
|
};
|
|
275
|
-
return Object.assign(new Promise(async (
|
|
275
|
+
return Object.assign(new Promise(async (o) => {
|
|
276
276
|
if (e.history || (e.history = []), e.memory) {
|
|
277
|
-
const
|
|
278
|
-
const [
|
|
277
|
+
const u = async (c, m, s = 10) => {
|
|
278
|
+
const [a, d] = await Promise.all([
|
|
279
279
|
m ? this.embedding(m) : Promise.resolve(null),
|
|
280
|
-
|
|
280
|
+
c ? this.embedding(c) : Promise.resolve(null)
|
|
281
281
|
]);
|
|
282
282
|
return (e.memory || []).map((p) => {
|
|
283
|
-
const f = (
|
|
283
|
+
const f = (a ? this.cosineSimilarity(p.embeddings[0], a[0].embedding) : 0) + (d ? this.cosineSimilarity(p.embeddings[1], d[0].embedding) : 0);
|
|
284
284
|
return { ...p, score: f };
|
|
285
|
-
}).toSorted((p, f) => p.score - f.score).slice(0,
|
|
285
|
+
}).toSorted((p, f) => p.score - f.score).slice(0, s).map((p) => `- ${p.owner}: ${p.fact}`).join(`
|
|
286
286
|
`);
|
|
287
287
|
};
|
|
288
288
|
e.system += `
|
|
289
289
|
You have RAG memory and will be given the top_k closest memories regarding the users query. Save anything new you have learned worth remembering from the user message using the remember tool and feel free to recall memories manually.
|
|
290
290
|
`;
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
${
|
|
291
|
+
const l = await u(r);
|
|
292
|
+
l.length && e.history.push({ role: "tool", name: "recall", id: "auto_recall_" + Math.random().toString(), args: {}, content: `Things I remembered:
|
|
293
|
+
${l}` }), e.tools = [{
|
|
294
294
|
name: "recall",
|
|
295
295
|
description: "Recall the closest memories you have regarding a query using RAG",
|
|
296
296
|
args: {
|
|
@@ -298,9 +298,9 @@ ${u}` }), e.tools = [{
|
|
|
298
298
|
query: { type: "string", description: "Search memory based on a query, can be used with or without subject argument" },
|
|
299
299
|
topK: { type: "number", description: "Result limit, default 5" }
|
|
300
300
|
},
|
|
301
|
-
fn: (
|
|
302
|
-
if (!
|
|
303
|
-
return c
|
|
301
|
+
fn: (c) => {
|
|
302
|
+
if (!c.subject && !c.query) throw new Error("Either a subject or query argument is required");
|
|
303
|
+
return u(c.query, c.subject, c.topK);
|
|
304
304
|
}
|
|
305
305
|
}, {
|
|
306
306
|
name: "remember",
|
|
@@ -309,31 +309,31 @@ ${u}` }), e.tools = [{
|
|
|
309
309
|
owner: { type: "string", description: "Subject/person this fact is about" },
|
|
310
310
|
fact: { type: "string", description: "The information to remember" }
|
|
311
311
|
},
|
|
312
|
-
fn: async (
|
|
312
|
+
fn: async (c) => {
|
|
313
313
|
if (!e.memory) return;
|
|
314
314
|
const m = await Promise.all([
|
|
315
|
-
this.embedding(
|
|
316
|
-
this.embedding(`${
|
|
317
|
-
]),
|
|
318
|
-
return e.memory.splice(0, e.memory.length, ...e.memory.filter((
|
|
315
|
+
this.embedding(c.owner),
|
|
316
|
+
this.embedding(`${c.owner}: ${c.fact}`)
|
|
317
|
+
]), s = { owner: c.owner, fact: c.fact, embeddings: [m[0][0].embedding, m[1][0].embedding] };
|
|
318
|
+
return e.memory.splice(0, e.memory.length, ...e.memory.filter((a) => !(this.cosineSimilarity(s.embeddings[0], a.embeddings[0]) >= 0.9 && this.cosineSimilarity(s.embeddings[1], a.embeddings[1]) >= 0.8)), s), "Remembered!";
|
|
319
319
|
}
|
|
320
320
|
}, ...e.tools || []];
|
|
321
321
|
}
|
|
322
322
|
const n = await this.models[t].ask(r, e);
|
|
323
|
-
if (e.memory && e.history.splice(0, e.history.length, ...e.history.filter((
|
|
324
|
-
const
|
|
325
|
-
e.history.splice(0, e.history.length, ...
|
|
323
|
+
if (e.memory && e.history.splice(0, e.history.length, ...e.history.filter((u) => u.role != "tool" || u.name != "recall" && u.name != "remember")), e.compress) {
|
|
324
|
+
const u = await this.ai.language.compressHistory(e.history, e.compress.max, e.compress.min, e);
|
|
325
|
+
e.history.splice(0, e.history.length, ...u);
|
|
326
326
|
}
|
|
327
|
-
return
|
|
328
|
-
}), { abort:
|
|
327
|
+
return o(n);
|
|
328
|
+
}), { abort: i });
|
|
329
329
|
}
|
|
330
330
|
async code(r, e) {
|
|
331
331
|
const t = await this.ask(r, { ...e, system: [
|
|
332
332
|
e?.system,
|
|
333
333
|
"Return your response in a code block"
|
|
334
|
-
].filter((
|
|
335
|
-
`) }),
|
|
336
|
-
return
|
|
334
|
+
].filter((o) => !!o).join(`
|
|
335
|
+
`) }), i = /```(?:.+)?\s*([\s\S]*?)```/.exec(t);
|
|
336
|
+
return i ? i[1].trim() : null;
|
|
337
337
|
}
|
|
338
338
|
/**
|
|
339
339
|
* Compress chat history to reduce context size
|
|
@@ -343,17 +343,17 @@ ${u}` }), e.tools = [{
|
|
|
343
343
|
* @param {LLMRequest} options LLM options
|
|
344
344
|
* @returns {Promise<LLMMessage[]>} New chat history will summary at index 0
|
|
345
345
|
*/
|
|
346
|
-
async compressHistory(r, e, t,
|
|
346
|
+
async compressHistory(r, e, t, i) {
|
|
347
347
|
if (this.estimateTokens(r) < e) return r;
|
|
348
|
-
let
|
|
348
|
+
let o = 0, n = 0;
|
|
349
349
|
for (let d of r.toReversed())
|
|
350
|
-
if (n += this.estimateTokens(d.content), n < t)
|
|
350
|
+
if (n += this.estimateTokens(d.content), n < t) o++;
|
|
351
351
|
else break;
|
|
352
|
-
if (r.length <=
|
|
353
|
-
const
|
|
352
|
+
if (r.length <= o) return r;
|
|
353
|
+
const u = r[0].role == "system" ? r[0] : null, l = o == 0 ? [] : r.slice(-o), c = (o == 0 ? r : r.slice(0, -o)).filter((d) => d.role === "assistant" || d.role === "user"), m = await this.summarize(c.map((d) => `[${d.role}]: ${d.content}`).join(`
|
|
354
354
|
|
|
355
|
-
`), 500,
|
|
356
|
-
return
|
|
355
|
+
`), 500, i), s = Date.now(), a = [{ role: "tool", name: "summary", id: "summary_" + s, args: {}, content: `Conversation Summary: ${m?.summary}`, timestamp: s }, ...l];
|
|
356
|
+
return u && a.splice(0, 0, u), a;
|
|
357
357
|
}
|
|
358
358
|
/**
|
|
359
359
|
* Compare the difference between embeddings (calculates the angle between two vectors)
|
|
@@ -363,10 +363,10 @@ ${u}` }), e.tools = [{
|
|
|
363
363
|
*/
|
|
364
364
|
cosineSimilarity(r, e) {
|
|
365
365
|
if (r.length !== e.length) throw new Error("Vectors must be same length");
|
|
366
|
-
let t = 0,
|
|
367
|
-
for (let
|
|
368
|
-
t += r[
|
|
369
|
-
const n = Math.sqrt(
|
|
366
|
+
let t = 0, i = 0, o = 0;
|
|
367
|
+
for (let u = 0; u < r.length; u++)
|
|
368
|
+
t += r[u] * e[u], i += r[u] * r[u], o += e[u] * e[u];
|
|
369
|
+
const n = Math.sqrt(i) * Math.sqrt(o);
|
|
370
370
|
return n === 0 ? 0 : t / n;
|
|
371
371
|
}
|
|
372
372
|
/**
|
|
@@ -377,25 +377,25 @@ ${u}` }), e.tools = [{
|
|
|
377
377
|
* @returns {string[]} Chunked strings
|
|
378
378
|
*/
|
|
379
379
|
chunk(r, e = 500, t = 50) {
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
return typeof
|
|
383
|
-
}) : [], n = (typeof r == "object" ?
|
|
384
|
-
`)).flatMap((
|
|
385
|
-
`]),
|
|
386
|
-
for (let
|
|
387
|
-
let
|
|
380
|
+
const i = (l, c = "") => l ? Object.entries(l).flatMap(([m, s]) => {
|
|
381
|
+
const a = c ? `${c}${isNaN(+m) ? `.${m}` : `[${m}]`}` : m;
|
|
382
|
+
return typeof s == "object" && !Array.isArray(s) ? i(s, a) : `${a}: ${Array.isArray(s) ? s.join(", ") : s}`;
|
|
383
|
+
}) : [], n = (typeof r == "object" ? i(r) : r.toString().split(`
|
|
384
|
+
`)).flatMap((l) => [...l.split(/\s+/).filter(Boolean), `
|
|
385
|
+
`]), u = [];
|
|
386
|
+
for (let l = 0; l < n.length; ) {
|
|
387
|
+
let c = "", m = l;
|
|
388
388
|
for (; m < n.length; ) {
|
|
389
|
-
const
|
|
390
|
-
if (this.estimateTokens(
|
|
391
|
-
`)) > e &&
|
|
392
|
-
|
|
389
|
+
const a = c + (c ? " " : "") + n[m];
|
|
390
|
+
if (this.estimateTokens(a.replace(/\s*\n\s*/g, `
|
|
391
|
+
`)) > e && c) break;
|
|
392
|
+
c = a, m++;
|
|
393
393
|
}
|
|
394
|
-
const
|
|
394
|
+
const s = c.replace(/\s*\n\s*/g, `
|
|
395
395
|
`).trim();
|
|
396
|
-
|
|
396
|
+
s && u.push(s), l = Math.max(m - t, m === l ? l + 1 : m);
|
|
397
397
|
}
|
|
398
|
-
return
|
|
398
|
+
return u;
|
|
399
399
|
}
|
|
400
400
|
/**
|
|
401
401
|
* Create a vector representation of a string
|
|
@@ -404,39 +404,39 @@ ${u}` }), e.tools = [{
|
|
|
404
404
|
* @returns {Promise<Awaited<{index: number, embedding: number[], text: string, tokens: number}>[]>} Chunked embeddings
|
|
405
405
|
*/
|
|
406
406
|
embedding(r, e = {}) {
|
|
407
|
-
let { maxTokens: t = 500, overlapTokens:
|
|
407
|
+
let { maxTokens: t = 500, overlapTokens: i = 50 } = e, o = !1;
|
|
408
408
|
const n = () => {
|
|
409
|
-
|
|
410
|
-
},
|
|
411
|
-
if (
|
|
412
|
-
const
|
|
413
|
-
I(
|
|
409
|
+
o = !0;
|
|
410
|
+
}, u = (c) => new Promise((m, s) => {
|
|
411
|
+
if (o) return s(new Error("Aborted"));
|
|
412
|
+
const a = [
|
|
413
|
+
I(G(H(import.meta.url)), "embedder.js"),
|
|
414
414
|
this.ai.options.path,
|
|
415
415
|
this.ai.options?.embedder || "bge-small-en-v1.5"
|
|
416
|
-
], d =
|
|
417
|
-
d.stdin.write(
|
|
416
|
+
], d = w("node", a, { stdio: ["pipe", "pipe", "ignore"] });
|
|
417
|
+
d.stdin.write(c), d.stdin.end();
|
|
418
418
|
let p = "";
|
|
419
419
|
d.stdout.on("data", (f) => p += f.toString()), d.on("close", (f) => {
|
|
420
|
-
if (
|
|
420
|
+
if (o) return s(new Error("Aborted"));
|
|
421
421
|
if (f === 0)
|
|
422
422
|
try {
|
|
423
423
|
const g = JSON.parse(p);
|
|
424
424
|
m(g.embedding);
|
|
425
425
|
} catch {
|
|
426
|
-
|
|
426
|
+
s(new Error("Failed to parse embedding output"));
|
|
427
427
|
}
|
|
428
428
|
else
|
|
429
|
-
|
|
430
|
-
}), d.on("error",
|
|
431
|
-
}),
|
|
432
|
-
const
|
|
433
|
-
for (let
|
|
434
|
-
const
|
|
435
|
-
m.push({ index:
|
|
429
|
+
s(new Error(`Embedder process exited with code ${f}`));
|
|
430
|
+
}), d.on("error", s);
|
|
431
|
+
}), l = (async () => {
|
|
432
|
+
const c = this.chunk(r, t, i), m = [];
|
|
433
|
+
for (let s = 0; s < c.length && !o; s++) {
|
|
434
|
+
const a = c[s], d = await u(a);
|
|
435
|
+
m.push({ index: s, embedding: d, text: a, tokens: this.estimateTokens(a) });
|
|
436
436
|
}
|
|
437
437
|
return m;
|
|
438
438
|
})();
|
|
439
|
-
return Object.assign(
|
|
439
|
+
return Object.assign(l, { abort: n });
|
|
440
440
|
}
|
|
441
441
|
/**
|
|
442
442
|
* Estimate variable as tokens
|
|
@@ -455,8 +455,8 @@ ${u}` }), e.tools = [{
|
|
|
455
455
|
*/
|
|
456
456
|
fuzzyMatch(r, ...e) {
|
|
457
457
|
if (e.length < 2) throw new Error("Requires at least 2 strings to compare");
|
|
458
|
-
const t = (n,
|
|
459
|
-
return { avg:
|
|
458
|
+
const t = (n, u = 10) => n.toLowerCase().split("").map((l, c) => l.charCodeAt(0) * (c + 1) % u / u).slice(0, u), i = t(r), o = e.map((n) => t(n)).map((n) => this.cosineSimilarity(i, n));
|
|
459
|
+
return { avg: o.reduce((n, u) => n + u, 0) / o.length, max: Math.max(...o), similarities: o };
|
|
460
460
|
}
|
|
461
461
|
/**
|
|
462
462
|
* Ask a question with JSON response
|
|
@@ -466,15 +466,15 @@ ${u}` }), e.tools = [{
|
|
|
466
466
|
* @returns {Promise<{} | {} | RegExpExecArray | null>}
|
|
467
467
|
*/
|
|
468
468
|
async json(r, e, t) {
|
|
469
|
-
const
|
|
469
|
+
const i = await this.code(r, { ...t, system: [
|
|
470
470
|
t?.system,
|
|
471
471
|
`Only respond using JSON matching this schema:
|
|
472
472
|
\`\`\`json
|
|
473
473
|
${e}
|
|
474
474
|
\`\`\``
|
|
475
|
-
].filter((
|
|
475
|
+
].filter((o) => !!o).join(`
|
|
476
476
|
`) });
|
|
477
|
-
return
|
|
477
|
+
return i ? _(i, {}) : null;
|
|
478
478
|
}
|
|
479
479
|
/**
|
|
480
480
|
* Create a summary of some text
|
|
@@ -510,21 +510,21 @@ print(json.dumps(segments))
|
|
|
510
510
|
pyannote;
|
|
511
511
|
whisperModel;
|
|
512
512
|
async addPunctuation(r, e, t = 150) {
|
|
513
|
-
const
|
|
513
|
+
const i = (n) => {
|
|
514
514
|
if (n = n.toLowerCase().replace(/[^a-z]/g, ""), n.length <= 3) return 1;
|
|
515
|
-
const
|
|
516
|
-
let
|
|
517
|
-
return n.endsWith("e") &&
|
|
515
|
+
const u = n.match(/[aeiouy]+/g);
|
|
516
|
+
let l = u ? u.length : 1;
|
|
517
|
+
return n.endsWith("e") && l--, Math.max(1, l);
|
|
518
518
|
};
|
|
519
|
-
let
|
|
520
|
-
return r.transcription.filter((n,
|
|
521
|
-
let
|
|
522
|
-
const
|
|
523
|
-
return !n.text && m ? (m.offsets.from = n.offsets.from, m.timestamps.from = n.offsets.from) : n.text && n.text[0] != " " &&
|
|
519
|
+
let o = "";
|
|
520
|
+
return r.transcription.filter((n, u) => {
|
|
521
|
+
let l = !1;
|
|
522
|
+
const c = r.transcription[u - 1], m = r.transcription[u + 1];
|
|
523
|
+
return !n.text && m ? (m.offsets.from = n.offsets.from, m.timestamps.from = n.offsets.from) : n.text && n.text[0] != " " && c && (c.offsets.to = n.offsets.to, c.timestamps.to = n.timestamps.to, c.text += n.text, l = !0), !!n.text && !l;
|
|
524
524
|
}).forEach((n) => {
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
}), e ? this.ai.language.ask(
|
|
525
|
+
const u = /^[A-Z]/.test(n.text.trim()), l = n.offsets.to - n.offsets.from, m = i(n.text.trim()) * t;
|
|
526
|
+
u && l > m * 2 && n.text[0] == " " && (o += "."), o += n.text;
|
|
527
|
+
}), e ? this.ai.language.ask(o, {
|
|
528
528
|
system: "Remove any misplaced punctuation from the following ASR transcript using the replace tool. Avoid modifying words unless there is an obvious typo",
|
|
529
529
|
temperature: 0.1,
|
|
530
530
|
tools: [{
|
|
@@ -534,138 +534,138 @@ print(json.dumps(segments))
|
|
|
534
534
|
find: { type: "string", description: "Text to find", required: !0 },
|
|
535
535
|
replace: { type: "string", description: "Text to replace", required: !0 }
|
|
536
536
|
},
|
|
537
|
-
fn: (n) =>
|
|
537
|
+
fn: (n) => o = o.replace(n.find, n.replace)
|
|
538
538
|
}]
|
|
539
|
-
}).then(() =>
|
|
539
|
+
}).then(() => o) : o.trim();
|
|
540
540
|
}
|
|
541
541
|
async diarizeTranscript(r, e, t) {
|
|
542
|
-
const
|
|
543
|
-
let
|
|
542
|
+
const i = /* @__PURE__ */ new Map();
|
|
543
|
+
let o = 0;
|
|
544
544
|
e.forEach((p) => {
|
|
545
|
-
|
|
545
|
+
i.has(p.speaker) || i.set(p.speaker, ++o);
|
|
546
546
|
});
|
|
547
|
-
const n = await this.addPunctuation(r, t),
|
|
547
|
+
const n = await this.addPunctuation(r, t), u = n.match(/[^.!?]+[.!?]+/g) || [n], l = r.transcription.filter((p) => p.text.trim()), c = u.map((p) => {
|
|
548
548
|
if (p = p.trim(), !p) return null;
|
|
549
549
|
const f = p.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/), g = /* @__PURE__ */ new Map();
|
|
550
|
-
f.forEach((
|
|
551
|
-
const
|
|
552
|
-
if (!
|
|
553
|
-
const $ =
|
|
550
|
+
f.forEach((x) => {
|
|
551
|
+
const k = l.find((y) => x === y.text.trim().toLowerCase().replace(/[^\w]/g, ""));
|
|
552
|
+
if (!k) return;
|
|
553
|
+
const $ = k.offsets.from / 1e3, v = e.find((y) => $ >= y.start && $ <= y.end);
|
|
554
554
|
if (v) {
|
|
555
|
-
const y =
|
|
555
|
+
const y = i.get(v.speaker);
|
|
556
556
|
g.set(y, (g.get(y) || 0) + 1);
|
|
557
557
|
}
|
|
558
558
|
});
|
|
559
559
|
let T = 1, E = 0;
|
|
560
|
-
return g.forEach((
|
|
561
|
-
|
|
560
|
+
return g.forEach((x, k) => {
|
|
561
|
+
x > E && (E = x, T = k);
|
|
562
562
|
}), { speaker: T, text: p };
|
|
563
563
|
}).filter((p) => p !== null), m = [];
|
|
564
|
-
|
|
564
|
+
c.forEach((p) => {
|
|
565
565
|
const f = m[m.length - 1];
|
|
566
566
|
f && f.speaker === p.speaker ? f.text += " " + p.text : m.push({ ...p });
|
|
567
567
|
});
|
|
568
|
-
let
|
|
568
|
+
let s = m.map((p) => `[Speaker ${p.speaker}]: ${p.text}`).join(`
|
|
569
569
|
`).trim();
|
|
570
|
-
if (!t) return
|
|
571
|
-
let
|
|
572
|
-
|
|
573
|
-
const d = await this.ai.language.json(
|
|
570
|
+
if (!t) return s;
|
|
571
|
+
let a = this.ai.language.chunk(s, 500, 0);
|
|
572
|
+
a.length > 4 && (a = [...a.slice(0, 3), a.at(-1)]);
|
|
573
|
+
const d = await this.ai.language.json(a.join(`
|
|
574
574
|
`), '{1: "Detected Name", 2: "Second Name"}', {
|
|
575
575
|
system: "Use the following transcript to identify speakers. Only identify speakers you are positive about, dont mention speakers you are unsure about in your response",
|
|
576
576
|
temperature: 0.1
|
|
577
577
|
});
|
|
578
|
-
return Object.entries(d).forEach(([p, f]) =>
|
|
578
|
+
return Object.entries(d).forEach(([p, f]) => s = s.replaceAll(`[Speaker ${p}]`, `[${f}]`)), s;
|
|
579
579
|
}
|
|
580
580
|
runAsr(r, e = {}) {
|
|
581
581
|
let t;
|
|
582
|
-
const
|
|
583
|
-
this.downloadAsrModel(e.model).then((
|
|
582
|
+
const i = new Promise((o, n) => {
|
|
583
|
+
this.downloadAsrModel(e.model).then((u) => {
|
|
584
584
|
if (e.diarization) {
|
|
585
|
-
let
|
|
586
|
-
t =
|
|
585
|
+
let l = M.join(M.dirname(r), "transcript");
|
|
586
|
+
t = w(
|
|
587
587
|
this.ai.options.whisper,
|
|
588
|
-
["-m",
|
|
588
|
+
["-m", u, "-f", r, "-np", "-ml", "1", "-oj", "-of", l],
|
|
589
589
|
{ stdio: ["ignore", "ignore", "pipe"] }
|
|
590
|
-
), t.on("error", (
|
|
591
|
-
if (
|
|
592
|
-
|
|
590
|
+
), t.on("error", (c) => n(c)), t.on("close", async (c) => {
|
|
591
|
+
if (c === 0) {
|
|
592
|
+
l = await b.readFile(l + ".json", "utf-8"), b.rm(l + ".json").catch(() => {
|
|
593
593
|
});
|
|
594
594
|
try {
|
|
595
|
-
|
|
595
|
+
o(JSON.parse(l));
|
|
596
596
|
} catch {
|
|
597
597
|
n(new Error("Failed to parse whisper JSON"));
|
|
598
598
|
}
|
|
599
599
|
} else
|
|
600
|
-
n(new Error(`Exit code ${
|
|
600
|
+
n(new Error(`Exit code ${c}`));
|
|
601
601
|
});
|
|
602
602
|
} else {
|
|
603
|
-
let
|
|
604
|
-
t =
|
|
605
|
-
|
|
603
|
+
let l = "";
|
|
604
|
+
t = w(this.ai.options.whisper, ["-m", u, "-f", r, "-np", "-nt"]), t.on("error", (c) => n(c)), t.stdout.on("data", (c) => l += c.toString()), t.on("close", async (c) => {
|
|
605
|
+
c === 0 ? o(l.trim() || null) : n(new Error(`Exit code ${c}`));
|
|
606
606
|
});
|
|
607
607
|
}
|
|
608
608
|
});
|
|
609
609
|
});
|
|
610
|
-
return Object.assign(
|
|
610
|
+
return Object.assign(i, { abort: () => t?.kill("SIGTERM") });
|
|
611
611
|
}
|
|
612
612
|
runDiarization(r) {
|
|
613
613
|
let e = !1, t = () => {
|
|
614
614
|
e = !0;
|
|
615
615
|
};
|
|
616
|
-
const
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
}),
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
]).then((async ([n,
|
|
616
|
+
const i = (n) => new Promise((u) => {
|
|
617
|
+
const l = w(n, ["-W", "ignore", "-c", "import pyannote.audio"]);
|
|
618
|
+
l.on("close", (c) => u(c === 0)), l.on("error", () => u(!1));
|
|
619
|
+
}), o = Promise.all([
|
|
620
|
+
i("python"),
|
|
621
|
+
i("python3")
|
|
622
|
+
]).then((async ([n, u]) => {
|
|
623
623
|
if (e) return;
|
|
624
|
-
if (!n && !
|
|
625
|
-
const
|
|
626
|
-
return new Promise((
|
|
624
|
+
if (!n && !u) throw new Error("Pyannote is not installed: pip install pyannote.audio");
|
|
625
|
+
const l = u ? "python3" : "python";
|
|
626
|
+
return new Promise((c, m) => {
|
|
627
627
|
if (e) return;
|
|
628
|
-
let
|
|
629
|
-
const
|
|
630
|
-
|
|
628
|
+
let s = "";
|
|
629
|
+
const a = w(l, ["-W", "ignore", "-c", this.pyannote, r]);
|
|
630
|
+
a.stdout.on("data", (d) => s += d.toString()), a.stderr.on("data", (d) => console.error(d.toString())), a.on("close", (d) => {
|
|
631
631
|
if (d === 0)
|
|
632
632
|
try {
|
|
633
|
-
|
|
633
|
+
c(JSON.parse(s));
|
|
634
634
|
} catch {
|
|
635
635
|
m(new Error("Failed to parse diarization output"));
|
|
636
636
|
}
|
|
637
637
|
else
|
|
638
638
|
m(new Error(`Python process exited with code ${d}`));
|
|
639
|
-
}),
|
|
639
|
+
}), a.on("error", m), t = () => a.kill("SIGTERM");
|
|
640
640
|
});
|
|
641
641
|
}));
|
|
642
|
-
return Object.assign(
|
|
642
|
+
return Object.assign(o, { abort: t });
|
|
643
643
|
}
|
|
644
644
|
asr(r, e = {}) {
|
|
645
645
|
if (!this.ai.options.whisper) throw new Error("Whisper not configured");
|
|
646
|
-
const t =
|
|
646
|
+
const t = A(V(A(U(), "audio-")), "converted.wav");
|
|
647
647
|
K(`ffmpeg -i "${r}" -ar 16000 -ac 1 -f wav "${t}"`, { stdio: "ignore" });
|
|
648
|
-
const
|
|
648
|
+
const i = () => b.rm(q.dirname(t), { recursive: !0, force: !0 }).catch(() => {
|
|
649
649
|
});
|
|
650
650
|
if (!e.diarization) return this.runAsr(t, { model: e.model });
|
|
651
|
-
const
|
|
652
|
-
let
|
|
653
|
-
|
|
651
|
+
const o = this.runAsr(t, { model: e.model, diarization: !0 }), n = this.runDiarization(t);
|
|
652
|
+
let u = !1, l = () => {
|
|
653
|
+
u = !0, o.abort(), n.abort(), i();
|
|
654
654
|
};
|
|
655
|
-
const
|
|
655
|
+
const c = Promise.allSettled([o, n]).then(async ([m, s]) => {
|
|
656
656
|
if (m.status == "rejected") throw new Error(`Whisper.cpp timestamps:
|
|
657
657
|
` + m.reason);
|
|
658
|
-
if (
|
|
659
|
-
` +
|
|
660
|
-
return
|
|
661
|
-
}).finally(() =>
|
|
662
|
-
return Object.assign(
|
|
658
|
+
if (s.status == "rejected") throw new Error(`Pyannote:
|
|
659
|
+
` + s.reason);
|
|
660
|
+
return u || !e.diarization ? m.value : this.diarizeTranscript(m.value, s.value, e.diarization == "llm");
|
|
661
|
+
}).finally(() => i());
|
|
662
|
+
return Object.assign(c, { abort: l });
|
|
663
663
|
}
|
|
664
664
|
async downloadAsrModel(r = this.whisperModel) {
|
|
665
665
|
if (!this.ai.options.whisper) throw new Error("Whisper not configured");
|
|
666
666
|
r.endsWith(".bin") || (r += ".bin");
|
|
667
|
-
const e =
|
|
668
|
-
return await
|
|
667
|
+
const e = q.join(this.ai.options.path, r);
|
|
668
|
+
return await b.stat(e).then(() => !0).catch(() => !1) ? e : this.downloads[r] ? this.downloads[r] : (this.downloads[r] = fetch(`https://huggingface.co/ggerganov/whisper.cpp/resolve/main/${r}`).then((t) => t.arrayBuffer()).then((t) => Buffer.from(t)).then(async (t) => (await b.writeFile(e, t), delete this.downloads[r], e)), this.downloads[r]);
|
|
669
669
|
}
|
|
670
670
|
}
|
|
671
671
|
class te {
|
|
@@ -679,15 +679,15 @@ class te {
|
|
|
679
679
|
*/
|
|
680
680
|
ocr(r) {
|
|
681
681
|
let e;
|
|
682
|
-
const t = new Promise(async (
|
|
682
|
+
const t = new Promise(async (i) => {
|
|
683
683
|
e = await Y(this.ai.options.ocr || "eng", 2, { cachePath: this.ai.options.path });
|
|
684
|
-
const { data:
|
|
685
|
-
await e.terminate(),
|
|
684
|
+
const { data: o } = await e.recognize(r);
|
|
685
|
+
await e.terminate(), i(o.text.trim() || null);
|
|
686
686
|
});
|
|
687
687
|
return Object.assign(t, { abort: () => e?.terminate() });
|
|
688
688
|
}
|
|
689
689
|
}
|
|
690
|
-
class
|
|
690
|
+
class be {
|
|
691
691
|
constructor(r) {
|
|
692
692
|
this.options = r, r.path || (r.path = O.tmpdir()), process.env.TRANSFORMERS_CACHE = r.path, this.audio = new ee(this), this.language = new X(this), this.vision = new te(this);
|
|
693
693
|
}
|
|
@@ -703,17 +703,17 @@ const re = () => O.platform() == "win32" ? "cmd" : j`echo $SHELL`?.split("/").po
|
|
|
703
703
|
description: "Use the command line interface, returns any output",
|
|
704
704
|
args: { command: { type: "string", description: "Command to run", required: !0 } },
|
|
705
705
|
fn: (h) => j`${h.command}`
|
|
706
|
-
},
|
|
706
|
+
}, we = {
|
|
707
707
|
name: "get_datetime",
|
|
708
708
|
description: "Get local date / time",
|
|
709
709
|
args: {},
|
|
710
710
|
fn: async () => (/* @__PURE__ */ new Date()).toString()
|
|
711
|
-
},
|
|
711
|
+
}, xe = {
|
|
712
712
|
name: "get_datetime_utc",
|
|
713
713
|
description: "Get current UTC date / time",
|
|
714
714
|
args: {},
|
|
715
715
|
fn: async () => (/* @__PURE__ */ new Date()).toUTCString()
|
|
716
|
-
},
|
|
716
|
+
}, ke = {
|
|
717
717
|
name: "exec",
|
|
718
718
|
description: "Run code/scripts",
|
|
719
719
|
args: {
|
|
@@ -745,7 +745,7 @@ const re = () => O.platform() == "win32" ? "cmd" : j`echo $SHELL`?.split("/").po
|
|
|
745
745
|
headers: { type: "object", description: "HTTP headers to send", default: {} },
|
|
746
746
|
body: { type: "object", description: "HTTP body to send" }
|
|
747
747
|
},
|
|
748
|
-
fn: (h) => new
|
|
748
|
+
fn: (h) => new C({ url: h.url, headers: h.headers }).request({ method: h.method || "GET", body: h.body })
|
|
749
749
|
}, se = {
|
|
750
750
|
name: "exec_javascript",
|
|
751
751
|
description: "Execute commonjs javascript",
|
|
@@ -753,7 +753,7 @@ const re = () => O.platform() == "win32" ? "cmd" : j`echo $SHELL`?.split("/").po
|
|
|
753
753
|
code: { type: "string", description: "CommonJS javascript", required: !0 }
|
|
754
754
|
},
|
|
755
755
|
fn: async (h) => {
|
|
756
|
-
const r =
|
|
756
|
+
const r = N(null), e = await J({ console: r }, h.code, !0).catch((t) => r.output.error.push(t));
|
|
757
757
|
return { ...r.output, return: e, stdout: void 0, stderr: void 0 };
|
|
758
758
|
}
|
|
759
759
|
}, oe = {
|
|
@@ -765,30 +765,78 @@ const re = () => O.platform() == "win32" ? "cmd" : j`echo $SHELL`?.split("/").po
|
|
|
765
765
|
fn: async (h) => ({ result: j`python -c "${h.code}"` })
|
|
766
766
|
}, Se = {
|
|
767
767
|
name: "read_webpage",
|
|
768
|
-
description: "Extract clean, structured content from a webpage
|
|
768
|
+
description: "Extract clean, structured content from a webpage or convert media/documents to accessible formats",
|
|
769
769
|
args: {
|
|
770
770
|
url: { type: "string", description: "URL to extract content from", required: !0 },
|
|
771
|
-
|
|
771
|
+
mimeRegex: { type: "string", description: 'Optional: Regex pattern to filter MIME types (e.g., "^image/", "text/", "application/pdf")' },
|
|
772
|
+
maxSize: { type: "number", description: "Optional: Max file size in bytes for binary content (default: 10MB)" }
|
|
772
773
|
},
|
|
773
774
|
fn: async (h) => {
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
775
|
+
const e = await fetch(h.url, {
|
|
776
|
+
headers: {
|
|
777
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
778
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
779
|
+
"Accept-Language": "en-US,en;q=0.5"
|
|
780
|
+
},
|
|
781
|
+
redirect: "follow"
|
|
782
|
+
}).catch((s) => {
|
|
783
|
+
throw new Error(`Failed to fetch: ${s.message}`);
|
|
784
|
+
}), t = e.headers.get("content-type") || "", i = t.split(";")[0].trim().toLowerCase();
|
|
785
|
+
if (t.match(/charset=([^;]+)/)?.[1], h.mimeRegex && !new RegExp(h.mimeRegex, "i").test(i))
|
|
786
|
+
return { url: h.url, error: "MIME type rejected", mimeType: i, filter: h.mimeRegex };
|
|
787
|
+
if (i.startsWith("image/") || i.startsWith("audio/") || i.startsWith("video/")) {
|
|
788
|
+
const s = await e.arrayBuffer();
|
|
789
|
+
if (s.byteLength > 10485760)
|
|
790
|
+
return { url: h.url, type: "media", mimeType: i, error: "File too large", size: s.byteLength, maxSize: 10485760 };
|
|
791
|
+
const a = Buffer.from(s).toString("base64");
|
|
792
|
+
return { url: h.url, type: "media", mimeType: i, dataUrl: `data:${i};base64,${a}`, size: s.byteLength };
|
|
793
|
+
}
|
|
794
|
+
if (i.match(/^(text\/(plain|csv|xml)|application\/(json|xml|csv|x-yaml))/) || h.url.match(/\.(txt|json|xml|csv|yaml|yml|md)$/i)) {
|
|
795
|
+
const s = await e.text();
|
|
796
|
+
return { url: h.url, type: "text", mimeType: i, content: s.slice(0, 1e5) };
|
|
797
|
+
}
|
|
798
|
+
if (i === "application/pdf" || i.startsWith("application/") && !i.includes("html")) {
|
|
799
|
+
const s = await e.arrayBuffer();
|
|
800
|
+
if (s.byteLength > 10485760)
|
|
801
|
+
return { url: h.url, type: "binary", mimeType: i, error: "File too large", size: s.byteLength, maxSize: 10485760 };
|
|
802
|
+
const a = Buffer.from(s).toString("base64");
|
|
803
|
+
return { url: h.url, type: "binary", mimeType: i, dataUrl: `data:${i};base64,${a}`, size: s.byteLength };
|
|
804
|
+
}
|
|
805
|
+
const o = await e.text(), n = Z.load(o);
|
|
806
|
+
n('script, style, nav, footer, header, aside, iframe, noscript, svg, [role="navigation"], [role="banner"], [role="complementary"], .ad, .ads, .advertisement, .cookie, .popup, .modal, .sidebar, .related, .comments, .social-share').remove();
|
|
807
|
+
const u = {
|
|
808
|
+
title: n('meta[property="og:title"]').attr("content") || n("title").text() || "",
|
|
809
|
+
description: n('meta[name="description"]').attr("content") || n('meta[property="og:description"]').attr("content") || "",
|
|
810
|
+
author: n('meta[name="author"]').attr("content") || "",
|
|
811
|
+
published: n('meta[property="article:published_time"]').attr("content") || n("time").attr("datetime") || "",
|
|
812
|
+
image: n('meta[property="og:image"]').attr("content") || ""
|
|
781
813
|
};
|
|
782
|
-
let
|
|
783
|
-
const
|
|
784
|
-
for (const
|
|
785
|
-
const
|
|
786
|
-
if (
|
|
787
|
-
|
|
814
|
+
let l = "";
|
|
815
|
+
const c = ["article", "main", '[role="main"]', ".content", ".post-content", ".entry-content", ".article-content", "body"];
|
|
816
|
+
for (const s of c) {
|
|
817
|
+
const a = n(s).first();
|
|
818
|
+
if (a.length && a.text().trim().length > 200) {
|
|
819
|
+
l = a.text();
|
|
788
820
|
break;
|
|
789
821
|
}
|
|
790
822
|
}
|
|
791
|
-
|
|
823
|
+
l || (l = n("body").text()), l = l.replace(/\n\s*\n\s*\n/g, `
|
|
824
|
+
|
|
825
|
+
`).replace(/[ \t]+/g, " ").trim().slice(0, 5e4);
|
|
826
|
+
let m = [];
|
|
827
|
+
return l.length < 500 && (n("a[href]").each((s, a) => {
|
|
828
|
+
const d = n(a).attr("href"), p = n(a).text().trim();
|
|
829
|
+
d && p && !d.startsWith("#") && m.push({ text: p, href: d });
|
|
830
|
+
}), m = m.slice(0, 50)), {
|
|
831
|
+
url: h.url,
|
|
832
|
+
type: "html",
|
|
833
|
+
title: u.title.trim(),
|
|
834
|
+
description: u.description.trim(),
|
|
835
|
+
author: u.author.trim(),
|
|
836
|
+
published: u.published,
|
|
837
|
+
content: l,
|
|
838
|
+
links: m.length ? m : void 0
|
|
839
|
+
};
|
|
792
840
|
}
|
|
793
841
|
}, je = {
|
|
794
842
|
name: "web_search",
|
|
@@ -800,28 +848,28 @@ const re = () => O.platform() == "win32" ? "cmd" : j`echo $SHELL`?.split("/").po
|
|
|
800
848
|
fn: async (h) => {
|
|
801
849
|
const r = await fetch(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(h.query)}`, {
|
|
802
850
|
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Accept-Language": "en-US,en;q=0.9" }
|
|
803
|
-
}).then((
|
|
851
|
+
}).then((o) => o.text());
|
|
804
852
|
let e, t = /<a .*?href="(.+?)".+?<\/a>/g;
|
|
805
|
-
const
|
|
853
|
+
const i = new F();
|
|
806
854
|
for (; (e = t.exec(r)) !== null; ) {
|
|
807
|
-
let
|
|
808
|
-
if (
|
|
855
|
+
let o = /uddg=(.+)&?/.exec(decodeURIComponent(e[1]))?.[1];
|
|
856
|
+
if (o && (o = decodeURIComponent(o)), o && i.add(o), i.size >= (h.length || 5)) break;
|
|
809
857
|
}
|
|
810
|
-
return
|
|
858
|
+
return i;
|
|
811
859
|
}
|
|
812
860
|
};
|
|
813
861
|
export {
|
|
814
|
-
|
|
862
|
+
be as Ai,
|
|
815
863
|
Q as Anthropic,
|
|
816
864
|
ee as Audio,
|
|
817
865
|
ne as CliTool,
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
866
|
+
we as DateTimeTool,
|
|
867
|
+
xe as DateTimeUTCTool,
|
|
868
|
+
ke as ExecTool,
|
|
821
869
|
_e as FetchTool,
|
|
822
870
|
se as JSTool,
|
|
823
|
-
|
|
824
|
-
|
|
871
|
+
L as LLMProvider,
|
|
872
|
+
P as OpenAi,
|
|
825
873
|
oe as PythonTool,
|
|
826
874
|
Se as ReadWebpageTool,
|
|
827
875
|
te as Vision,
|