@ztimson/ai-utils 0.8.14 → 0.8.16
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 +41 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +510 -435
- package/dist/index.mjs.map +1 -1
- package/dist/llm.d.ts +2 -2
- package/dist/tools.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,254 +1,254 @@
|
|
|
1
1
|
import * as O from "node:os";
|
|
2
|
-
import { tmpdir as
|
|
3
|
-
import { Anthropic as
|
|
4
|
-
import { objectMap as
|
|
2
|
+
import { tmpdir as L } from "node:os";
|
|
3
|
+
import { Anthropic as N } from "@anthropic-ai/sdk";
|
|
4
|
+
import { objectMap as R, JSONAttemptParse as S, findByProp as z, JSONSanitize as _, clean as C, Http as J, consoleInterceptor as I, fn as F, ASet as H } from "@ztimson/utils";
|
|
5
5
|
import { OpenAI as B } from "openai";
|
|
6
6
|
import { fileURLToPath as D } from "url";
|
|
7
|
-
import { join as
|
|
8
|
-
import { spawn as
|
|
9
|
-
import { mkdtempSync as
|
|
10
|
-
import
|
|
11
|
-
import * as
|
|
12
|
-
import
|
|
13
|
-
import { createWorker as
|
|
14
|
-
import * as
|
|
15
|
-
import { $Sync as
|
|
16
|
-
class
|
|
7
|
+
import { join as G, dirname as K } from "path";
|
|
8
|
+
import { spawn as b, execSync as Y } from "node:child_process";
|
|
9
|
+
import { mkdtempSync as V } from "node:fs";
|
|
10
|
+
import w from "node:fs/promises";
|
|
11
|
+
import * as q from "node:path";
|
|
12
|
+
import M, { join as A } from "node:path";
|
|
13
|
+
import { createWorker as Z } from "tesseract.js";
|
|
14
|
+
import * as Q from "cheerio";
|
|
15
|
+
import { $Sync as T } from "@ztimson/node-utils";
|
|
16
|
+
class W {
|
|
17
17
|
}
|
|
18
|
-
class
|
|
19
|
-
constructor(
|
|
20
|
-
super(), this.ai =
|
|
18
|
+
class X extends W {
|
|
19
|
+
constructor(t, e, r) {
|
|
20
|
+
super(), this.ai = t, this.apiToken = e, this.model = r, this.client = new N({ apiKey: e });
|
|
21
21
|
}
|
|
22
22
|
client;
|
|
23
|
-
toStandard(
|
|
24
|
-
const e = Date.now(),
|
|
25
|
-
for (let
|
|
26
|
-
if (typeof
|
|
27
|
-
|
|
23
|
+
toStandard(t) {
|
|
24
|
+
const e = Date.now(), r = [];
|
|
25
|
+
for (let o of t)
|
|
26
|
+
if (typeof o.content == "string")
|
|
27
|
+
r.push({ timestamp: e, ...o });
|
|
28
28
|
else {
|
|
29
|
-
const
|
|
29
|
+
const i = o.content?.filter((n) => n.type == "text").map((n) => n.text).join(`
|
|
30
30
|
|
|
31
31
|
`);
|
|
32
|
-
|
|
32
|
+
i && r.push({ timestamp: e, role: o.role, content: i }), o.content.forEach((n) => {
|
|
33
33
|
if (n.type == "tool_use")
|
|
34
|
-
|
|
34
|
+
r.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 l = r.findLast((m) => m.id == n.tool_use_id);
|
|
37
|
+
l && (l[n.is_error ? "error" : "content"] = n.content);
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
|
-
return
|
|
41
|
+
return r;
|
|
42
42
|
}
|
|
43
|
-
fromStandard(
|
|
44
|
-
for (let e = 0; e <
|
|
45
|
-
if (
|
|
46
|
-
const
|
|
47
|
-
|
|
43
|
+
fromStandard(t) {
|
|
44
|
+
for (let e = 0; e < t.length; e++)
|
|
45
|
+
if (t[e].role == "tool") {
|
|
46
|
+
const r = t[e];
|
|
47
|
+
t.splice(
|
|
48
48
|
e,
|
|
49
49
|
1,
|
|
50
|
-
{ role: "assistant", content: [{ type: "tool_use", id:
|
|
51
|
-
{ role: "user", content: [{ type: "tool_result", tool_use_id:
|
|
50
|
+
{ role: "assistant", content: [{ type: "tool_use", id: r.id, name: r.name, input: r.args }] },
|
|
51
|
+
{ role: "user", content: [{ type: "tool_result", tool_use_id: r.id, is_error: !!r.error, content: r.error || r.content }] }
|
|
52
52
|
), e++;
|
|
53
53
|
}
|
|
54
|
-
return
|
|
54
|
+
return t.map(({ timestamp: e, ...r }) => r);
|
|
55
55
|
}
|
|
56
|
-
ask(
|
|
57
|
-
const
|
|
58
|
-
return Object.assign(new Promise(async (
|
|
59
|
-
let
|
|
60
|
-
const n = e.tools || this.ai.options.llm?.tools || [],
|
|
56
|
+
ask(t, e = {}) {
|
|
57
|
+
const r = new AbortController();
|
|
58
|
+
return Object.assign(new Promise(async (o) => {
|
|
59
|
+
let i = this.fromStandard([...e.history || [], { role: "user", content: t, timestamp: Date.now() }]);
|
|
60
|
+
const n = e.tools || this.ai.options.llm?.tools || [], l = {
|
|
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 || "",
|
|
64
64
|
temperature: e.temperature || this.ai.options.llm?.temperature || 0.7,
|
|
65
|
-
tools: n.map((
|
|
66
|
-
name:
|
|
67
|
-
description:
|
|
65
|
+
tools: n.map((u) => ({
|
|
66
|
+
name: u.name,
|
|
67
|
+
description: u.description,
|
|
68
68
|
input_schema: {
|
|
69
69
|
type: "object",
|
|
70
|
-
properties:
|
|
71
|
-
required:
|
|
70
|
+
properties: u.args ? R(u.args, (s, c) => ({ ...c, required: void 0 })) : {},
|
|
71
|
+
required: u.args ? Object.entries(u.args).filter((s) => s[1].required).map((s) => s[0]) : []
|
|
72
72
|
},
|
|
73
73
|
fn: void 0
|
|
74
74
|
})),
|
|
75
|
-
messages:
|
|
75
|
+
messages: i,
|
|
76
76
|
stream: !!e.stream
|
|
77
77
|
};
|
|
78
|
-
let m,
|
|
78
|
+
let m, a = !0;
|
|
79
79
|
do {
|
|
80
|
-
if (m = await this.client.messages.create(
|
|
80
|
+
if (m = await this.client.messages.create(l).catch((s) => {
|
|
81
81
|
throw s.message += `
|
|
82
82
|
|
|
83
83
|
Messages:
|
|
84
|
-
${JSON.stringify(
|
|
84
|
+
${JSON.stringify(i, null, 2)}`, s;
|
|
85
85
|
}), e.stream) {
|
|
86
|
-
|
|
86
|
+
a ? a = !1 : e.stream({ text: `
|
|
87
87
|
|
|
88
88
|
` }), m.content = [];
|
|
89
89
|
for await (const s of m) {
|
|
90
|
-
if (
|
|
90
|
+
if (r.signal.aborted) break;
|
|
91
91
|
if (s.type === "content_block_start")
|
|
92
92
|
s.content_block.type === "text" ? m.content.push({ type: "text", text: "" }) : s.content_block.type === "tool_use" && m.content.push({ type: "tool_use", id: s.content_block.id, name: s.content_block.name, input: "" });
|
|
93
93
|
else if (s.type === "content_block_delta")
|
|
94
94
|
if (s.delta.type === "text_delta") {
|
|
95
|
-
const
|
|
96
|
-
m.content.at(-1).text +=
|
|
95
|
+
const c = s.delta.text;
|
|
96
|
+
m.content.at(-1).text += c, e.stream({ text: c });
|
|
97
97
|
} else s.delta.type === "input_json_delta" && (m.content.at(-1).input += s.delta.partial_json);
|
|
98
98
|
else if (s.type === "content_block_stop") {
|
|
99
|
-
const
|
|
100
|
-
|
|
99
|
+
const c = m.content.at(-1);
|
|
100
|
+
c.input != null && (c.input = c.input ? S(c.input, {}) : {});
|
|
101
101
|
} else if (s.type === "message_stop")
|
|
102
102
|
break;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
const s = await Promise.all(
|
|
109
|
-
const
|
|
110
|
-
if (e.stream && e.stream({ tool:
|
|
105
|
+
const u = m.content.filter((s) => s.type === "tool_use");
|
|
106
|
+
if (u.length && !r.signal.aborted) {
|
|
107
|
+
i.push({ role: "assistant", content: m.content });
|
|
108
|
+
const s = await Promise.all(u.map(async (c) => {
|
|
109
|
+
const p = n.find(z("name", c.name));
|
|
110
|
+
if (e.stream && e.stream({ tool: c.name }), !p) return { tool_use_id: c.id, is_error: !0, content: "Tool not found" };
|
|
111
111
|
try {
|
|
112
|
-
const
|
|
113
|
-
return { type: "tool_result", tool_use_id:
|
|
114
|
-
} catch (
|
|
115
|
-
return { type: "tool_result", tool_use_id:
|
|
112
|
+
const d = await p.fn(c.input, e?.stream, this.ai);
|
|
113
|
+
return { type: "tool_result", tool_use_id: c.id, content: _(d) };
|
|
114
|
+
} catch (d) {
|
|
115
|
+
return { type: "tool_result", tool_use_id: c.id, is_error: !0, content: d?.message || d?.toString() || "Unknown" };
|
|
116
116
|
}
|
|
117
117
|
}));
|
|
118
|
-
|
|
118
|
+
i.push({ role: "user", content: s }), l.messages = i;
|
|
119
119
|
}
|
|
120
|
-
} while (!
|
|
121
|
-
|
|
120
|
+
} while (!r.signal.aborted && m.content.some((u) => u.type === "tool_use"));
|
|
121
|
+
i.push({ role: "assistant", content: m.content.filter((u) => u.type == "text").map((u) => u.text).join(`
|
|
122
122
|
|
|
123
|
-
`) }),
|
|
124
|
-
}), { abort: () =>
|
|
123
|
+
`) }), i = this.toStandard(i), e.stream && e.stream({ done: !0 }), e.history && e.history.splice(0, e.history.length, ...i), o(i.at(-1)?.content);
|
|
124
|
+
}), { abort: () => r.abort() });
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
class P extends
|
|
128
|
-
constructor(
|
|
129
|
-
super(), this.ai =
|
|
127
|
+
class P extends W {
|
|
128
|
+
constructor(t, e, r, o) {
|
|
129
|
+
super(), this.ai = t, this.host = e, this.token = r, this.model = o, this.client = new B(C({
|
|
130
130
|
baseURL: e,
|
|
131
|
-
apiKey:
|
|
131
|
+
apiKey: r || e ? "ignored" : void 0
|
|
132
132
|
}));
|
|
133
133
|
}
|
|
134
134
|
client;
|
|
135
|
-
toStandard(
|
|
136
|
-
for (let e = 0; e <
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
139
|
-
const
|
|
135
|
+
toStandard(t) {
|
|
136
|
+
for (let e = 0; e < t.length; e++) {
|
|
137
|
+
const r = t[e];
|
|
138
|
+
if (r.role === "assistant" && r.tool_calls) {
|
|
139
|
+
const o = r.tool_calls.map((i) => ({
|
|
140
140
|
role: "tool",
|
|
141
|
-
id:
|
|
142
|
-
name:
|
|
143
|
-
args: S(
|
|
144
|
-
timestamp:
|
|
141
|
+
id: i.id,
|
|
142
|
+
name: i.function.name,
|
|
143
|
+
args: S(i.function.arguments, {}),
|
|
144
|
+
timestamp: r.timestamp
|
|
145
145
|
}));
|
|
146
|
-
|
|
147
|
-
} else if (
|
|
148
|
-
const
|
|
149
|
-
|
|
146
|
+
t.splice(e, 1, ...o), e += o.length - 1;
|
|
147
|
+
} else if (r.role === "tool" && r.content) {
|
|
148
|
+
const o = t.find((i) => r.tool_call_id == i.id);
|
|
149
|
+
o && (r.content.includes('"error":') ? o.error = r.content : o.content = r.content), t.splice(e, 1), e--;
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
t[e]?.timestamp || (t[e].timestamp = Date.now());
|
|
152
152
|
}
|
|
153
|
-
return
|
|
153
|
+
return t;
|
|
154
154
|
}
|
|
155
|
-
fromStandard(
|
|
156
|
-
return
|
|
157
|
-
if (
|
|
155
|
+
fromStandard(t) {
|
|
156
|
+
return t.reduce((e, r) => {
|
|
157
|
+
if (r.role === "tool")
|
|
158
158
|
e.push({
|
|
159
159
|
role: "assistant",
|
|
160
160
|
content: null,
|
|
161
|
-
tool_calls: [{ id:
|
|
161
|
+
tool_calls: [{ id: r.id, type: "function", function: { name: r.name, arguments: JSON.stringify(r.args) } }],
|
|
162
162
|
refusal: null,
|
|
163
163
|
annotations: []
|
|
164
164
|
}, {
|
|
165
165
|
role: "tool",
|
|
166
|
-
tool_call_id:
|
|
167
|
-
content:
|
|
166
|
+
tool_call_id: r.id,
|
|
167
|
+
content: r.error || r.content
|
|
168
168
|
});
|
|
169
169
|
else {
|
|
170
|
-
const { timestamp:
|
|
171
|
-
e.push(
|
|
170
|
+
const { timestamp: o, ...i } = r;
|
|
171
|
+
e.push(i);
|
|
172
172
|
}
|
|
173
173
|
return e;
|
|
174
174
|
}, []);
|
|
175
175
|
}
|
|
176
|
-
ask(
|
|
177
|
-
const
|
|
178
|
-
return Object.assign(new Promise(async (
|
|
176
|
+
ask(t, e = {}) {
|
|
177
|
+
const r = new AbortController();
|
|
178
|
+
return Object.assign(new Promise(async (o, i) => {
|
|
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
|
-
let n = this.fromStandard([...e.history || [], { role: "user", content:
|
|
181
|
-
const
|
|
180
|
+
let n = this.fromStandard([...e.history || [], { role: "user", content: t, timestamp: Date.now() }]);
|
|
181
|
+
const l = e.tools || this.ai.options.llm?.tools || [], m = {
|
|
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: l.map((s) => ({
|
|
188
188
|
type: "function",
|
|
189
189
|
function: {
|
|
190
190
|
name: s.name,
|
|
191
191
|
description: s.description,
|
|
192
192
|
parameters: {
|
|
193
193
|
type: "object",
|
|
194
|
-
properties: s.args ?
|
|
195
|
-
required: s.args ? Object.entries(s.args).filter((
|
|
194
|
+
properties: s.args ? R(s.args, (c, p) => ({ ...p, required: void 0 })) : {},
|
|
195
|
+
required: s.args ? Object.entries(s.args).filter((c) => c[1].required).map((c) => c[0]) : []
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
}))
|
|
199
199
|
};
|
|
200
|
-
let
|
|
200
|
+
let a, u = !0;
|
|
201
201
|
do {
|
|
202
|
-
if (
|
|
203
|
-
throw
|
|
202
|
+
if (a = await this.client.chat.completions.create(m).catch((c) => {
|
|
203
|
+
throw c.message += `
|
|
204
204
|
|
|
205
205
|
Messages:
|
|
206
|
-
${JSON.stringify(n, null, 2)}`,
|
|
206
|
+
${JSON.stringify(n, null, 2)}`, c;
|
|
207
207
|
}), e.stream) {
|
|
208
|
-
|
|
208
|
+
u ? u = !1 : e.stream({ text: `
|
|
209
209
|
|
|
210
|
-
` }),
|
|
211
|
-
for await (const
|
|
212
|
-
if (
|
|
213
|
-
if (
|
|
214
|
-
for (const
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
index:
|
|
218
|
-
id:
|
|
219
|
-
type:
|
|
210
|
+
` }), a.choices = [{ message: { role: "assistant", content: "", tool_calls: [] } }];
|
|
211
|
+
for await (const c of a) {
|
|
212
|
+
if (r.signal.aborted) break;
|
|
213
|
+
if (c.choices[0].delta.content && (a.choices[0].message.content += c.choices[0].delta.content, e.stream({ text: c.choices[0].delta.content })), c.choices[0].delta.tool_calls)
|
|
214
|
+
for (const p of c.choices[0].delta.tool_calls) {
|
|
215
|
+
const d = a.choices[0].message.tool_calls.find((f) => f.index === p.index);
|
|
216
|
+
d ? (p.id && (d.id = p.id), p.type && (d.type = p.type), p.function && (d.function || (d.function = {}), p.function.name && (d.function.name = p.function.name), p.function.arguments && (d.function.arguments = (d.function.arguments || "") + p.function.arguments))) : a.choices[0].message.tool_calls.push({
|
|
217
|
+
index: p.index,
|
|
218
|
+
id: p.id || "",
|
|
219
|
+
type: p.type || "function",
|
|
220
220
|
function: {
|
|
221
|
-
name:
|
|
222
|
-
arguments:
|
|
221
|
+
name: p.function?.name || "",
|
|
222
|
+
arguments: p.function?.arguments || ""
|
|
223
223
|
}
|
|
224
224
|
});
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
|
-
const s =
|
|
229
|
-
if (s.length && !
|
|
230
|
-
n.push(
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
if (e.stream && e.stream({ tool:
|
|
228
|
+
const s = a.choices[0].message.tool_calls || [];
|
|
229
|
+
if (s.length && !r.signal.aborted) {
|
|
230
|
+
n.push(a.choices[0].message);
|
|
231
|
+
const c = await Promise.all(s.map(async (p) => {
|
|
232
|
+
const d = l?.find(z("name", p.function.name));
|
|
233
|
+
if (e.stream && e.stream({ tool: p.function.name }), !d) return { role: "tool", tool_call_id: p.id, content: '{"error": "Tool not found"}' };
|
|
234
234
|
try {
|
|
235
|
-
const f = S(
|
|
236
|
-
return { role: "tool", tool_call_id:
|
|
235
|
+
const f = S(p.function.arguments, {}), g = await d.fn(f, e.stream, this.ai);
|
|
236
|
+
return { role: "tool", tool_call_id: p.id, content: _(g) };
|
|
237
237
|
} catch (f) {
|
|
238
|
-
return { role: "tool", tool_call_id:
|
|
238
|
+
return { role: "tool", tool_call_id: p.id, content: _({ error: f?.message || f?.toString() || "Unknown" }) };
|
|
239
239
|
}
|
|
240
240
|
}));
|
|
241
|
-
n.push(...
|
|
241
|
+
n.push(...c), m.messages = n;
|
|
242
242
|
}
|
|
243
|
-
} while (!
|
|
244
|
-
n.push({ role: "assistant", content:
|
|
245
|
-
}), { abort: () =>
|
|
243
|
+
} while (!r.signal.aborted && a.choices?.[0]?.message?.tool_calls?.length);
|
|
244
|
+
n.push({ role: "assistant", content: a.choices[0].message.content || "" }), 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);
|
|
245
|
+
}), { abort: () => r.abort() });
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
-
class
|
|
249
|
-
constructor(
|
|
250
|
-
this.ai =
|
|
251
|
-
this.defaultModel || (this.defaultModel = e),
|
|
248
|
+
class ee {
|
|
249
|
+
constructor(t) {
|
|
250
|
+
this.ai = t, t.options.llm?.models && Object.entries(t.options.llm.models).forEach(([e, r]) => {
|
|
251
|
+
this.defaultModel || (this.defaultModel = e), r.proto == "anthropic" ? this.models[e] = new X(this.ai, r.token, e) : r.proto == "ollama" ? this.models[e] = new P(this.ai, r.host, "not-needed", e) : r.proto == "openai" && (this.models[e] = new P(this.ai, r.host || null, r.token, e));
|
|
252
252
|
});
|
|
253
253
|
}
|
|
254
254
|
defaultModel;
|
|
@@ -259,7 +259,7 @@ class X {
|
|
|
259
259
|
* @param {LLMRequest} options Configuration options and chat history
|
|
260
260
|
* @returns {{abort: () => void, response: Promise<string>}} Function to abort response and chat history
|
|
261
261
|
*/
|
|
262
|
-
ask(
|
|
262
|
+
ask(t, e = {}) {
|
|
263
263
|
e = {
|
|
264
264
|
system: "",
|
|
265
265
|
temperature: 0.8,
|
|
@@ -268,27 +268,27 @@ class X {
|
|
|
268
268
|
history: [],
|
|
269
269
|
...e
|
|
270
270
|
};
|
|
271
|
-
const
|
|
272
|
-
if (!this.models[
|
|
273
|
-
let
|
|
271
|
+
const r = e.model || this.defaultModel;
|
|
272
|
+
if (!this.models[r]) throw new Error(`Model does not exist: ${r}`);
|
|
273
|
+
let o = () => {
|
|
274
274
|
};
|
|
275
|
-
return Object.assign(new Promise(async (
|
|
275
|
+
return Object.assign(new Promise(async (i) => {
|
|
276
276
|
if (e.history || (e.history = []), e.memory) {
|
|
277
|
-
const
|
|
278
|
-
const [
|
|
279
|
-
|
|
280
|
-
|
|
277
|
+
const l = async (a, u, s = 10) => {
|
|
278
|
+
const [c, p] = await Promise.all([
|
|
279
|
+
u ? this.embedding(u) : Promise.resolve(null),
|
|
280
|
+
a ? this.embedding(a) : Promise.resolve(null)
|
|
281
281
|
]);
|
|
282
|
-
return (e.memory || []).map((
|
|
283
|
-
const f = (
|
|
284
|
-
return { ...
|
|
285
|
-
}).toSorted((
|
|
282
|
+
return (e.memory || []).map((d) => {
|
|
283
|
+
const f = (c ? this.cosineSimilarity(d.embeddings[0], c[0].embedding) : 0) + (p ? this.cosineSimilarity(d.embeddings[1], p[0].embedding) : 0);
|
|
284
|
+
return { ...d, score: f };
|
|
285
|
+
}).toSorted((d, f) => d.score - f.score).slice(0, s).map((d) => `- ${d.owner}: ${d.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 m = await
|
|
291
|
+
const m = await l(t);
|
|
292
292
|
m.length && e.history.push({ role: "tool", name: "recall", id: "auto_recall_" + Math.random().toString(), args: {}, content: `Things I remembered:
|
|
293
293
|
${m}` }), e.tools = [{
|
|
294
294
|
name: "recall",
|
|
@@ -298,9 +298,9 @@ ${m}` }), 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
|
|
301
|
+
fn: (a) => {
|
|
302
|
+
if (!a.subject && !a.query) throw new Error("Either a subject or query argument is required");
|
|
303
|
+
return l(a.query, a.subject, a.topK);
|
|
304
304
|
}
|
|
305
305
|
}, {
|
|
306
306
|
name: "remember",
|
|
@@ -309,31 +309,31 @@ ${m}` }), 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 (a) => {
|
|
313
313
|
if (!e.memory) return;
|
|
314
|
-
const
|
|
315
|
-
this.embedding(
|
|
316
|
-
this.embedding(`${
|
|
317
|
-
]), s = { owner:
|
|
318
|
-
return e.memory.splice(0, e.memory.length, ...e.memory.filter((
|
|
314
|
+
const u = await Promise.all([
|
|
315
|
+
this.embedding(a.owner),
|
|
316
|
+
this.embedding(`${a.owner}: ${a.fact}`)
|
|
317
|
+
]), s = { owner: a.owner, fact: a.fact, embeddings: [u[0][0].embedding, u[1][0].embedding] };
|
|
318
|
+
return e.memory.splice(0, e.memory.length, ...e.memory.filter((c) => !(this.cosineSimilarity(s.embeddings[0], c.embeddings[0]) >= 0.9 && this.cosineSimilarity(s.embeddings[1], c.embeddings[1]) >= 0.8)), s), "Remembered!";
|
|
319
319
|
}
|
|
320
320
|
}, ...e.tools || []];
|
|
321
321
|
}
|
|
322
|
-
const n = await this.models[
|
|
323
|
-
if (e.memory && e.history.splice(0, e.history.length, ...e.history.filter((
|
|
324
|
-
const
|
|
325
|
-
e.history.splice(0, e.history.length, ...
|
|
322
|
+
const n = await this.models[r].ask(t, e);
|
|
323
|
+
if (e.memory && e.history.splice(0, e.history.length, ...e.history.filter((l) => l.role != "tool" || l.name != "recall" && l.name != "remember")), e.compress) {
|
|
324
|
+
const l = await this.ai.language.compressHistory(e.history, e.compress.max, e.compress.min, e);
|
|
325
|
+
e.history.splice(0, e.history.length, ...l);
|
|
326
326
|
}
|
|
327
|
-
return
|
|
328
|
-
}), { abort:
|
|
327
|
+
return i(n);
|
|
328
|
+
}), { abort: o });
|
|
329
329
|
}
|
|
330
|
-
async code(
|
|
331
|
-
const
|
|
330
|
+
async code(t, e) {
|
|
331
|
+
const r = await this.ask(t, { ...e, system: [
|
|
332
332
|
e?.system,
|
|
333
333
|
"Return your response in a code block"
|
|
334
|
-
].filter((
|
|
335
|
-
`) }),
|
|
336
|
-
return
|
|
334
|
+
].filter((i) => !!i).join(`
|
|
335
|
+
`) }), o = /```(?:.+)?\s*([\s\S]*?)```/.exec(r);
|
|
336
|
+
return o ? o[1].trim() : null;
|
|
337
337
|
}
|
|
338
338
|
/**
|
|
339
339
|
* Compress chat history to reduce context size
|
|
@@ -343,17 +343,17 @@ ${m}` }), 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(
|
|
347
|
-
if (this.estimateTokens(
|
|
348
|
-
let
|
|
349
|
-
for (let
|
|
350
|
-
if (n += this.estimateTokens(
|
|
346
|
+
async compressHistory(t, e, r, o) {
|
|
347
|
+
if (this.estimateTokens(t) < e) return t;
|
|
348
|
+
let i = 0, n = 0;
|
|
349
|
+
for (let p of t.toReversed())
|
|
350
|
+
if (n += this.estimateTokens(p.content), n < r) i++;
|
|
351
351
|
else break;
|
|
352
|
-
if (
|
|
353
|
-
const
|
|
352
|
+
if (t.length <= i) return t;
|
|
353
|
+
const l = t[0].role == "system" ? t[0] : null, m = i == 0 ? [] : t.slice(-i), a = (i == 0 ? t : t.slice(0, -i)).filter((p) => p.role === "assistant" || p.role === "user"), u = await this.summarize(a.map((p) => `[${p.role}]: ${p.content}`).join(`
|
|
354
354
|
|
|
355
|
-
`), 500,
|
|
356
|
-
return
|
|
355
|
+
`), 500, o), s = Date.now(), c = [{ role: "tool", name: "summary", id: "summary_" + s, args: {}, content: `Conversation Summary: ${u?.summary}`, timestamp: s }, ...m];
|
|
356
|
+
return l && c.splice(0, 0, l), c;
|
|
357
357
|
}
|
|
358
358
|
/**
|
|
359
359
|
* Compare the difference between embeddings (calculates the angle between two vectors)
|
|
@@ -361,13 +361,13 @@ ${m}` }), e.tools = [{
|
|
|
361
361
|
* @param {number[]} v2 Second embedding / vector for comparison
|
|
362
362
|
* @returns {number} Similarity values 0-1: 0 = unique, 1 = identical
|
|
363
363
|
*/
|
|
364
|
-
cosineSimilarity(
|
|
365
|
-
if (
|
|
366
|
-
let
|
|
367
|
-
for (let
|
|
368
|
-
|
|
369
|
-
const n = Math.sqrt(
|
|
370
|
-
return n === 0 ? 0 :
|
|
364
|
+
cosineSimilarity(t, e) {
|
|
365
|
+
if (t.length !== e.length) throw new Error("Vectors must be same length");
|
|
366
|
+
let r = 0, o = 0, i = 0;
|
|
367
|
+
for (let l = 0; l < t.length; l++)
|
|
368
|
+
r += t[l] * e[l], o += t[l] * t[l], i += e[l] * e[l];
|
|
369
|
+
const n = Math.sqrt(o) * Math.sqrt(i);
|
|
370
|
+
return n === 0 ? 0 : r / n;
|
|
371
371
|
}
|
|
372
372
|
/**
|
|
373
373
|
* Chunk text into parts for AI digestion
|
|
@@ -376,26 +376,26 @@ ${m}` }), e.tools = [{
|
|
|
376
376
|
* @param {number} overlapTokens Includes previous X tokens to provide continuity to AI (In addition to max tokens)
|
|
377
377
|
* @returns {string[]} Chunked strings
|
|
378
378
|
*/
|
|
379
|
-
chunk(
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
return typeof s == "object" && !Array.isArray(s) ?
|
|
383
|
-
}) : [], n = (typeof
|
|
379
|
+
chunk(t, e = 500, r = 50) {
|
|
380
|
+
const o = (m, a = "") => m ? Object.entries(m).flatMap(([u, s]) => {
|
|
381
|
+
const c = a ? `${a}${isNaN(+u) ? `.${u}` : `[${u}]`}` : u;
|
|
382
|
+
return typeof s == "object" && !Array.isArray(s) ? o(s, c) : `${c}: ${Array.isArray(s) ? s.join(", ") : s}`;
|
|
383
|
+
}) : [], n = (typeof t == "object" ? o(t) : t.toString().split(`
|
|
384
384
|
`)).flatMap((m) => [...m.split(/\s+/).filter(Boolean), `
|
|
385
|
-
`]),
|
|
385
|
+
`]), l = [];
|
|
386
386
|
for (let m = 0; m < n.length; ) {
|
|
387
|
-
let
|
|
388
|
-
for (;
|
|
389
|
-
const
|
|
390
|
-
if (this.estimateTokens(
|
|
391
|
-
`)) > e &&
|
|
392
|
-
|
|
387
|
+
let a = "", u = m;
|
|
388
|
+
for (; u < n.length; ) {
|
|
389
|
+
const c = a + (a ? " " : "") + n[u];
|
|
390
|
+
if (this.estimateTokens(c.replace(/\s*\n\s*/g, `
|
|
391
|
+
`)) > e && a) break;
|
|
392
|
+
a = c, u++;
|
|
393
393
|
}
|
|
394
|
-
const s =
|
|
394
|
+
const s = a.replace(/\s*\n\s*/g, `
|
|
395
395
|
`).trim();
|
|
396
|
-
s &&
|
|
396
|
+
s && l.push(s), m = Math.max(u - r, u === m ? m + 1 : u);
|
|
397
397
|
}
|
|
398
|
-
return
|
|
398
|
+
return l;
|
|
399
399
|
}
|
|
400
400
|
/**
|
|
401
401
|
* Create a vector representation of a string
|
|
@@ -403,38 +403,38 @@ ${m}` }), e.tools = [{
|
|
|
403
403
|
* @param {maxTokens?: number, overlapTokens?: number} opts Options for embedding such as chunk sizes
|
|
404
404
|
* @returns {Promise<Awaited<{index: number, embedding: number[], text: string, tokens: number}>[]>} Chunked embeddings
|
|
405
405
|
*/
|
|
406
|
-
embedding(
|
|
407
|
-
let { maxTokens:
|
|
406
|
+
embedding(t, e = {}) {
|
|
407
|
+
let { maxTokens: r = 500, overlapTokens: o = 50 } = e, i = !1;
|
|
408
408
|
const n = () => {
|
|
409
|
-
|
|
410
|
-
},
|
|
411
|
-
if (
|
|
412
|
-
const
|
|
413
|
-
|
|
409
|
+
i = !0;
|
|
410
|
+
}, l = (a) => new Promise((u, s) => {
|
|
411
|
+
if (i) return s(new Error("Aborted"));
|
|
412
|
+
const c = [
|
|
413
|
+
G(K(D(import.meta.url)), "embedder.js"),
|
|
414
414
|
this.ai.options.path,
|
|
415
415
|
this.ai.options?.embedder || "bge-small-en-v1.5"
|
|
416
|
-
],
|
|
417
|
-
|
|
418
|
-
let
|
|
419
|
-
|
|
420
|
-
if (
|
|
416
|
+
], p = b("node", c, { stdio: ["pipe", "pipe", "ignore"] });
|
|
417
|
+
p.stdin.write(a), p.stdin.end();
|
|
418
|
+
let d = "";
|
|
419
|
+
p.stdout.on("data", (f) => d += f.toString()), p.on("close", (f) => {
|
|
420
|
+
if (i) return s(new Error("Aborted"));
|
|
421
421
|
if (f === 0)
|
|
422
422
|
try {
|
|
423
|
-
const g = JSON.parse(
|
|
424
|
-
|
|
423
|
+
const g = JSON.parse(d);
|
|
424
|
+
u(g.embedding);
|
|
425
425
|
} catch {
|
|
426
426
|
s(new Error("Failed to parse embedding output"));
|
|
427
427
|
}
|
|
428
428
|
else
|
|
429
429
|
s(new Error(`Embedder process exited with code ${f}`));
|
|
430
|
-
}),
|
|
430
|
+
}), p.on("error", s);
|
|
431
431
|
}), m = (async () => {
|
|
432
|
-
const
|
|
433
|
-
for (let s = 0; s <
|
|
434
|
-
const
|
|
435
|
-
|
|
432
|
+
const a = this.chunk(t, r, o), u = [];
|
|
433
|
+
for (let s = 0; s < a.length && !i; s++) {
|
|
434
|
+
const c = a[s], p = await l(c);
|
|
435
|
+
u.push({ index: s, embedding: p, text: c, tokens: this.estimateTokens(c) });
|
|
436
436
|
}
|
|
437
|
-
return
|
|
437
|
+
return u;
|
|
438
438
|
})();
|
|
439
439
|
return Object.assign(m, { abort: n });
|
|
440
440
|
}
|
|
@@ -443,8 +443,8 @@ ${m}` }), e.tools = [{
|
|
|
443
443
|
* @param history Object to size
|
|
444
444
|
* @returns {number} Rough token count
|
|
445
445
|
*/
|
|
446
|
-
estimateTokens(
|
|
447
|
-
const e = JSON.stringify(
|
|
446
|
+
estimateTokens(t) {
|
|
447
|
+
const e = JSON.stringify(t);
|
|
448
448
|
return Math.ceil(e.length / 4 * 1.2);
|
|
449
449
|
}
|
|
450
450
|
/**
|
|
@@ -453,10 +453,10 @@ ${m}` }), e.tools = [{
|
|
|
453
453
|
* @param {string} searchTerms Multiple search terms to check against target
|
|
454
454
|
* @returns {{avg: number, max: number, similarities: number[]}} Similarity values 0-1: 0 = unique, 1 = identical
|
|
455
455
|
*/
|
|
456
|
-
fuzzyMatch(
|
|
456
|
+
fuzzyMatch(t, ...e) {
|
|
457
457
|
if (e.length < 2) throw new Error("Requires at least 2 strings to compare");
|
|
458
|
-
const
|
|
459
|
-
return { avg:
|
|
458
|
+
const r = (n, l = 10) => n.toLowerCase().split("").map((m, a) => m.charCodeAt(0) * (a + 1) % l / l).slice(0, l), o = r(t), i = e.map((n) => r(n)).map((n) => this.cosineSimilarity(o, n));
|
|
459
|
+
return { avg: i.reduce((n, l) => n + l, 0) / i.length, max: Math.max(...i), similarities: i };
|
|
460
460
|
}
|
|
461
461
|
/**
|
|
462
462
|
* Ask a question with JSON response
|
|
@@ -465,83 +465,79 @@ ${m}` }), e.tools = [{
|
|
|
465
465
|
* @param {LLMRequest} options Configuration options and chat history
|
|
466
466
|
* @returns {Promise<{} | {} | RegExpExecArray | null>}
|
|
467
467
|
*/
|
|
468
|
-
async json(
|
|
469
|
-
let
|
|
468
|
+
async json(t, e, r) {
|
|
469
|
+
let o = `Your job is to convert input to JSON using tool calls. Call the \`submit\` tool at least once with JSON matching this schema:
|
|
470
470
|
\`\`\`json
|
|
471
471
|
${e}
|
|
472
472
|
\`\`\`
|
|
473
473
|
|
|
474
474
|
Responses are ignored`;
|
|
475
|
-
return
|
|
475
|
+
return r?.system && (o += `
|
|
476
476
|
|
|
477
|
-
` +
|
|
478
|
-
let
|
|
479
|
-
const m = await this.ask(
|
|
477
|
+
` + r.system), new Promise(async (i, n) => {
|
|
478
|
+
let l = !1;
|
|
479
|
+
const m = await this.ask(t, {
|
|
480
480
|
temperature: 0.3,
|
|
481
|
-
...
|
|
482
|
-
system:
|
|
481
|
+
...r,
|
|
482
|
+
system: o,
|
|
483
483
|
tools: [{
|
|
484
484
|
name: "submit",
|
|
485
485
|
description: "Submit JSON",
|
|
486
486
|
args: { json: { type: "string", description: "Javascript parsable JSON string", required: !0 } },
|
|
487
|
-
fn: (
|
|
487
|
+
fn: (a) => {
|
|
488
488
|
try {
|
|
489
|
-
const
|
|
490
|
-
|
|
489
|
+
const u = JSON.parse(a.json);
|
|
490
|
+
i(u), l = !0;
|
|
491
491
|
} catch {
|
|
492
492
|
return "Invalid JSON";
|
|
493
493
|
}
|
|
494
494
|
return "Saved";
|
|
495
495
|
}
|
|
496
|
-
}, ...
|
|
496
|
+
}, ...r?.tools || []]
|
|
497
497
|
});
|
|
498
|
-
|
|
498
|
+
l || n(`AI failed to create JSON:
|
|
499
499
|
${m}`);
|
|
500
500
|
});
|
|
501
501
|
}
|
|
502
502
|
/**
|
|
503
503
|
* Create a summary of some text
|
|
504
504
|
* @param {string} text Text to summarize
|
|
505
|
-
* @param {number}
|
|
505
|
+
* @param {number} length Max number of words
|
|
506
506
|
* @param options LLM request options
|
|
507
507
|
* @returns {Promise<string>} Summary
|
|
508
508
|
*/
|
|
509
|
-
async summarize(
|
|
510
|
-
let
|
|
511
|
-
return
|
|
509
|
+
async summarize(t, e = 500, r) {
|
|
510
|
+
let o = `Your job is to summarize the users message using tool calls. Call the \`submit\` tool at least once with the shortest summary possible that's <= ${e} words. The tool call will respond with the token count. Responses are ignored`;
|
|
511
|
+
return r?.system && (o += `
|
|
512
512
|
|
|
513
|
-
` +
|
|
514
|
-
let
|
|
515
|
-
const m = await this.ask(
|
|
513
|
+
` + r.system), new Promise(async (i, n) => {
|
|
514
|
+
let l = !1;
|
|
515
|
+
const m = await this.ask(t, {
|
|
516
516
|
temperature: 0.3,
|
|
517
|
-
...
|
|
518
|
-
system:
|
|
517
|
+
...r,
|
|
518
|
+
system: o,
|
|
519
519
|
tools: [{
|
|
520
520
|
name: "submit",
|
|
521
521
|
description: "Submit summary",
|
|
522
522
|
args: { summary: { type: "string", description: "Text summarization", required: !0 } },
|
|
523
|
-
fn: (
|
|
524
|
-
|
|
525
|
-
const c = this.estimateTokens(i.summary);
|
|
526
|
-
return c > e ? `Summary is too long (${c} tokens)` : (u = !0, o(i.summary || null), `Saved (${c} tokens)`);
|
|
527
|
-
}
|
|
528
|
-
}, ...t?.tools || []]
|
|
523
|
+
fn: (a) => a.summary ? a.summary.split(" ").length > e ? `Too long: ${e} words` : (l = !0, i(a.summary || null), `Saved: ${e} words`) : "No summary provided"
|
|
524
|
+
}, ...r?.tools || []]
|
|
529
525
|
});
|
|
530
|
-
|
|
526
|
+
l || n(`AI failed to create summary:
|
|
531
527
|
${m}`);
|
|
532
528
|
});
|
|
533
529
|
}
|
|
534
530
|
}
|
|
535
|
-
class
|
|
536
|
-
constructor(
|
|
537
|
-
this.ai =
|
|
531
|
+
class te {
|
|
532
|
+
constructor(t) {
|
|
533
|
+
this.ai = t, t.options.whisper && (this.whisperModel = t.options.asr || "ggml-base.en.bin", this.downloadAsrModel()), this.pyannote = `
|
|
538
534
|
import sys
|
|
539
535
|
import json
|
|
540
536
|
import os
|
|
541
537
|
from pyannote.audio import Pipeline
|
|
542
538
|
|
|
543
|
-
os.environ['TORCH_HOME'] = r"${
|
|
544
|
-
pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization-3.1", token="${
|
|
539
|
+
os.environ['TORCH_HOME'] = r"${t.options.path}"
|
|
540
|
+
pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization-3.1", token="${t.options.hfToken}")
|
|
545
541
|
output = pipeline(sys.argv[1])
|
|
546
542
|
|
|
547
543
|
segments = []
|
|
@@ -554,22 +550,22 @@ print(json.dumps(segments))
|
|
|
554
550
|
downloads = {};
|
|
555
551
|
pyannote;
|
|
556
552
|
whisperModel;
|
|
557
|
-
async addPunctuation(
|
|
558
|
-
const
|
|
553
|
+
async addPunctuation(t, e, r = 150) {
|
|
554
|
+
const o = (n) => {
|
|
559
555
|
if (n = n.toLowerCase().replace(/[^a-z]/g, ""), n.length <= 3) return 1;
|
|
560
|
-
const
|
|
561
|
-
let m =
|
|
556
|
+
const l = n.match(/[aeiouy]+/g);
|
|
557
|
+
let m = l ? l.length : 1;
|
|
562
558
|
return n.endsWith("e") && m--, Math.max(1, m);
|
|
563
559
|
};
|
|
564
|
-
let
|
|
565
|
-
return
|
|
560
|
+
let i = "";
|
|
561
|
+
return t.transcription.filter((n, l) => {
|
|
566
562
|
let m = !1;
|
|
567
|
-
const
|
|
568
|
-
return !n.text &&
|
|
563
|
+
const a = t.transcription[l - 1], u = t.transcription[l + 1];
|
|
564
|
+
return !n.text && u ? (u.offsets.from = n.offsets.from, u.timestamps.from = n.offsets.from) : n.text && n.text[0] != " " && a && (a.offsets.to = n.offsets.to, a.timestamps.to = n.timestamps.to, a.text += n.text, m = !0), !!n.text && !m;
|
|
569
565
|
}).forEach((n) => {
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
}), e ? this.ai.language.ask(
|
|
566
|
+
const l = /^[A-Z]/.test(n.text.trim()), m = n.offsets.to - n.offsets.from, u = o(n.text.trim()) * r;
|
|
567
|
+
l && m > u * 2 && n.text[0] == " " && (i += "."), i += n.text;
|
|
568
|
+
}), e ? this.ai.language.ask(i, {
|
|
573
569
|
system: "Remove any misplaced punctuation from the following ASR transcript using the replace tool. Avoid modifying words unless there is an obvious typo",
|
|
574
570
|
temperature: 0.1,
|
|
575
571
|
tools: [{
|
|
@@ -579,162 +575,162 @@ print(json.dumps(segments))
|
|
|
579
575
|
find: { type: "string", description: "Text to find", required: !0 },
|
|
580
576
|
replace: { type: "string", description: "Text to replace", required: !0 }
|
|
581
577
|
},
|
|
582
|
-
fn: (n) =>
|
|
578
|
+
fn: (n) => i = i.replace(n.find, n.replace)
|
|
583
579
|
}]
|
|
584
|
-
}).then(() =>
|
|
580
|
+
}).then(() => i) : i.trim();
|
|
585
581
|
}
|
|
586
|
-
async diarizeTranscript(
|
|
587
|
-
const
|
|
588
|
-
let
|
|
589
|
-
e.forEach((
|
|
590
|
-
|
|
582
|
+
async diarizeTranscript(t, e, r) {
|
|
583
|
+
const o = /* @__PURE__ */ new Map();
|
|
584
|
+
let i = 0;
|
|
585
|
+
e.forEach((d) => {
|
|
586
|
+
o.has(d.speaker) || o.set(d.speaker, ++i);
|
|
591
587
|
});
|
|
592
|
-
const n = await this.addPunctuation(
|
|
593
|
-
if (
|
|
594
|
-
const f =
|
|
588
|
+
const n = await this.addPunctuation(t, r), l = n.match(/[^.!?]+[.!?]+/g) || [n], m = t.transcription.filter((d) => d.text.trim()), a = l.map((d) => {
|
|
589
|
+
if (d = d.trim(), !d) return null;
|
|
590
|
+
const f = d.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/), g = /* @__PURE__ */ new Map();
|
|
595
591
|
f.forEach((x) => {
|
|
596
592
|
const k = m.find((y) => x === y.text.trim().toLowerCase().replace(/[^\w]/g, ""));
|
|
597
593
|
if (!k) return;
|
|
598
|
-
const
|
|
599
|
-
if (
|
|
600
|
-
const y =
|
|
594
|
+
const v = k.offsets.from / 1e3, E = e.find((y) => v >= y.start && v <= y.end);
|
|
595
|
+
if (E) {
|
|
596
|
+
const y = o.get(E.speaker);
|
|
601
597
|
g.set(y, (g.get(y) || 0) + 1);
|
|
602
598
|
}
|
|
603
599
|
});
|
|
604
|
-
let
|
|
600
|
+
let j = 1, $ = 0;
|
|
605
601
|
return g.forEach((x, k) => {
|
|
606
|
-
x >
|
|
607
|
-
}), { speaker:
|
|
608
|
-
}).filter((
|
|
609
|
-
|
|
610
|
-
const f =
|
|
611
|
-
f && f.speaker ===
|
|
602
|
+
x > $ && ($ = x, j = k);
|
|
603
|
+
}), { speaker: j, text: d };
|
|
604
|
+
}).filter((d) => d !== null), u = [];
|
|
605
|
+
a.forEach((d) => {
|
|
606
|
+
const f = u[u.length - 1];
|
|
607
|
+
f && f.speaker === d.speaker ? f.text += " " + d.text : u.push({ ...d });
|
|
612
608
|
});
|
|
613
|
-
let s =
|
|
609
|
+
let s = u.map((d) => `[Speaker ${d.speaker}]: ${d.text}`).join(`
|
|
614
610
|
`).trim();
|
|
615
|
-
if (!
|
|
616
|
-
let
|
|
617
|
-
|
|
618
|
-
const
|
|
611
|
+
if (!r) return s;
|
|
612
|
+
let c = this.ai.language.chunk(s, 500, 0);
|
|
613
|
+
c.length > 4 && (c = [...c.slice(0, 3), c.at(-1)]);
|
|
614
|
+
const p = await this.ai.language.json(c.join(`
|
|
619
615
|
`), '{1: "Detected Name", 2: "Second Name"}', {
|
|
620
616
|
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",
|
|
621
617
|
temperature: 0.1
|
|
622
618
|
});
|
|
623
|
-
return Object.entries(
|
|
619
|
+
return Object.entries(p).forEach(([d, f]) => s = s.replaceAll(`[Speaker ${d}]`, `[${f}]`)), s;
|
|
624
620
|
}
|
|
625
|
-
runAsr(
|
|
626
|
-
let
|
|
627
|
-
const
|
|
628
|
-
this.downloadAsrModel(e.model).then((
|
|
621
|
+
runAsr(t, e = {}) {
|
|
622
|
+
let r;
|
|
623
|
+
const o = new Promise((i, n) => {
|
|
624
|
+
this.downloadAsrModel(e.model).then((l) => {
|
|
629
625
|
if (e.diarization) {
|
|
630
|
-
let m =
|
|
631
|
-
|
|
626
|
+
let m = q.join(q.dirname(t), "transcript");
|
|
627
|
+
r = b(
|
|
632
628
|
this.ai.options.whisper,
|
|
633
|
-
["-m",
|
|
629
|
+
["-m", l, "-f", t, "-np", "-ml", "1", "-oj", "-of", m],
|
|
634
630
|
{ stdio: ["ignore", "ignore", "pipe"] }
|
|
635
|
-
),
|
|
636
|
-
if (
|
|
637
|
-
m = await
|
|
631
|
+
), r.on("error", (a) => n(a)), r.on("close", async (a) => {
|
|
632
|
+
if (a === 0) {
|
|
633
|
+
m = await w.readFile(m + ".json", "utf-8"), w.rm(m + ".json").catch(() => {
|
|
638
634
|
});
|
|
639
635
|
try {
|
|
640
|
-
|
|
636
|
+
i(JSON.parse(m));
|
|
641
637
|
} catch {
|
|
642
638
|
n(new Error("Failed to parse whisper JSON"));
|
|
643
639
|
}
|
|
644
640
|
} else
|
|
645
|
-
n(new Error(`Exit code ${
|
|
641
|
+
n(new Error(`Exit code ${a}`));
|
|
646
642
|
});
|
|
647
643
|
} else {
|
|
648
644
|
let m = "";
|
|
649
|
-
|
|
650
|
-
|
|
645
|
+
r = b(this.ai.options.whisper, ["-m", l, "-f", t, "-np", "-nt"]), r.on("error", (a) => n(a)), r.stdout.on("data", (a) => m += a.toString()), r.on("close", async (a) => {
|
|
646
|
+
a === 0 ? i(m.trim() || null) : n(new Error(`Exit code ${a}`));
|
|
651
647
|
});
|
|
652
648
|
}
|
|
653
649
|
});
|
|
654
650
|
});
|
|
655
|
-
return Object.assign(
|
|
651
|
+
return Object.assign(o, { abort: () => r?.kill("SIGTERM") });
|
|
656
652
|
}
|
|
657
|
-
runDiarization(
|
|
658
|
-
let e = !1,
|
|
653
|
+
runDiarization(t) {
|
|
654
|
+
let e = !1, r = () => {
|
|
659
655
|
e = !0;
|
|
660
656
|
};
|
|
661
|
-
const
|
|
662
|
-
const m =
|
|
663
|
-
m.on("close", (
|
|
664
|
-
}),
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
]).then((async ([n,
|
|
657
|
+
const o = (n) => new Promise((l) => {
|
|
658
|
+
const m = b(n, ["-W", "ignore", "-c", "import pyannote.audio"]);
|
|
659
|
+
m.on("close", (a) => l(a === 0)), m.on("error", () => l(!1));
|
|
660
|
+
}), i = Promise.all([
|
|
661
|
+
o("python"),
|
|
662
|
+
o("python3")
|
|
663
|
+
]).then((async ([n, l]) => {
|
|
668
664
|
if (e) return;
|
|
669
|
-
if (!n && !
|
|
670
|
-
const m =
|
|
671
|
-
return new Promise((
|
|
665
|
+
if (!n && !l) throw new Error("Pyannote is not installed: pip install pyannote.audio");
|
|
666
|
+
const m = l ? "python3" : "python";
|
|
667
|
+
return new Promise((a, u) => {
|
|
672
668
|
if (e) return;
|
|
673
669
|
let s = "";
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
if (
|
|
670
|
+
const c = b(m, ["-W", "ignore", "-c", this.pyannote, t]);
|
|
671
|
+
c.stdout.on("data", (p) => s += p.toString()), c.stderr.on("data", (p) => console.error(p.toString())), c.on("close", (p) => {
|
|
672
|
+
if (p === 0)
|
|
677
673
|
try {
|
|
678
|
-
|
|
674
|
+
a(JSON.parse(s));
|
|
679
675
|
} catch {
|
|
680
|
-
|
|
676
|
+
u(new Error("Failed to parse diarization output"));
|
|
681
677
|
}
|
|
682
678
|
else
|
|
683
|
-
|
|
684
|
-
}),
|
|
679
|
+
u(new Error(`Python process exited with code ${p}`));
|
|
680
|
+
}), c.on("error", u), r = () => c.kill("SIGTERM");
|
|
685
681
|
});
|
|
686
682
|
}));
|
|
687
|
-
return Object.assign(
|
|
683
|
+
return Object.assign(i, { abort: r });
|
|
688
684
|
}
|
|
689
|
-
asr(
|
|
685
|
+
asr(t, e = {}) {
|
|
690
686
|
if (!this.ai.options.whisper) throw new Error("Whisper not configured");
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
const
|
|
687
|
+
const r = A(V(A(L(), "audio-")), "converted.wav");
|
|
688
|
+
Y(`ffmpeg -i "${t}" -ar 16000 -ac 1 -f wav "${r}"`, { stdio: "ignore" });
|
|
689
|
+
const o = () => w.rm(M.dirname(r), { recursive: !0, force: !0 }).catch(() => {
|
|
694
690
|
});
|
|
695
|
-
if (!e.diarization) return this.runAsr(
|
|
696
|
-
const
|
|
697
|
-
let
|
|
698
|
-
|
|
691
|
+
if (!e.diarization) return this.runAsr(r, { model: e.model });
|
|
692
|
+
const i = this.runAsr(r, { model: e.model, diarization: !0 }), n = this.runDiarization(r);
|
|
693
|
+
let l = !1, m = () => {
|
|
694
|
+
l = !0, i.abort(), n.abort(), o();
|
|
699
695
|
};
|
|
700
|
-
const
|
|
701
|
-
if (
|
|
702
|
-
` +
|
|
696
|
+
const a = Promise.allSettled([i, n]).then(async ([u, s]) => {
|
|
697
|
+
if (u.status == "rejected") throw new Error(`Whisper.cpp timestamps:
|
|
698
|
+
` + u.reason);
|
|
703
699
|
if (s.status == "rejected") throw new Error(`Pyannote:
|
|
704
700
|
` + s.reason);
|
|
705
|
-
return
|
|
706
|
-
}).finally(() =>
|
|
707
|
-
return Object.assign(
|
|
701
|
+
return l || !e.diarization ? u.value : this.diarizeTranscript(u.value, s.value, e.diarization == "llm");
|
|
702
|
+
}).finally(() => o());
|
|
703
|
+
return Object.assign(a, { abort: m });
|
|
708
704
|
}
|
|
709
|
-
async downloadAsrModel(
|
|
705
|
+
async downloadAsrModel(t = this.whisperModel) {
|
|
710
706
|
if (!this.ai.options.whisper) throw new Error("Whisper not configured");
|
|
711
|
-
|
|
712
|
-
const e =
|
|
713
|
-
return await
|
|
707
|
+
t.endsWith(".bin") || (t += ".bin");
|
|
708
|
+
const e = M.join(this.ai.options.path, t);
|
|
709
|
+
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((r) => r.arrayBuffer()).then((r) => Buffer.from(r)).then(async (r) => (await w.writeFile(e, r), delete this.downloads[t], e)), this.downloads[t]);
|
|
714
710
|
}
|
|
715
711
|
}
|
|
716
|
-
class
|
|
717
|
-
constructor(
|
|
718
|
-
this.ai =
|
|
712
|
+
class re {
|
|
713
|
+
constructor(t) {
|
|
714
|
+
this.ai = t;
|
|
719
715
|
}
|
|
720
716
|
/**
|
|
721
717
|
* Convert image to text using Optical Character Recognition
|
|
722
718
|
* @param {string} path Path to image
|
|
723
719
|
* @returns {AbortablePromise<string | null>} Promise of extracted text with abort method
|
|
724
720
|
*/
|
|
725
|
-
ocr(
|
|
721
|
+
ocr(t) {
|
|
726
722
|
let e;
|
|
727
|
-
const
|
|
728
|
-
e = await
|
|
729
|
-
const { data:
|
|
730
|
-
await e.terminate(),
|
|
723
|
+
const r = new Promise(async (o) => {
|
|
724
|
+
e = await Z(this.ai.options.ocr || "eng", 2, { cachePath: this.ai.options.path });
|
|
725
|
+
const { data: i } = await e.recognize(t);
|
|
726
|
+
await e.terminate(), o(i.text.trim() || null);
|
|
731
727
|
});
|
|
732
|
-
return Object.assign(
|
|
728
|
+
return Object.assign(r, { abort: () => e?.terminate() });
|
|
733
729
|
}
|
|
734
730
|
}
|
|
735
731
|
class be {
|
|
736
|
-
constructor(
|
|
737
|
-
this.options =
|
|
732
|
+
constructor(t) {
|
|
733
|
+
this.options = t, t.path || (t.path = O.tmpdir()), process.env.TRANSFORMERS_CACHE = t.path, this.audio = new te(this), this.language = new ee(this), this.vision = new re(this);
|
|
738
734
|
}
|
|
739
735
|
/** Audio processing AI */
|
|
740
736
|
audio;
|
|
@@ -743,45 +739,45 @@ class be {
|
|
|
743
739
|
/** Vision processing AI */
|
|
744
740
|
vision;
|
|
745
741
|
}
|
|
746
|
-
const
|
|
742
|
+
const ne = () => O.platform() == "win32" ? "cmd" : T`echo $SHELL`?.split("/").pop() || "bash", se = {
|
|
747
743
|
name: "cli",
|
|
748
744
|
description: "Use the command line interface, returns any output",
|
|
749
745
|
args: { command: { type: "string", description: "Command to run", required: !0 } },
|
|
750
|
-
fn: (h) =>
|
|
751
|
-
},
|
|
746
|
+
fn: (h) => T`${h.command}`
|
|
747
|
+
}, xe = {
|
|
752
748
|
name: "get_datetime",
|
|
753
749
|
description: "Get local date / time",
|
|
754
750
|
args: {},
|
|
755
751
|
fn: async () => (/* @__PURE__ */ new Date()).toString()
|
|
756
|
-
},
|
|
752
|
+
}, ke = {
|
|
757
753
|
name: "get_datetime_utc",
|
|
758
754
|
description: "Get current UTC date / time",
|
|
759
755
|
args: {},
|
|
760
756
|
fn: async () => (/* @__PURE__ */ new Date()).toUTCString()
|
|
761
|
-
},
|
|
757
|
+
}, Se = {
|
|
762
758
|
name: "exec",
|
|
763
759
|
description: "Run code/scripts",
|
|
764
760
|
args: {
|
|
765
|
-
language: { type: "string", description: `Execution language (CLI: ${
|
|
761
|
+
language: { type: "string", description: `Execution language (CLI: ${ne()})`, enum: ["cli", "node", "python"], required: !0 },
|
|
766
762
|
code: { type: "string", description: "Code to execute", required: !0 }
|
|
767
763
|
},
|
|
768
|
-
fn: async (h,
|
|
764
|
+
fn: async (h, t, e) => {
|
|
769
765
|
try {
|
|
770
766
|
switch (h.language) {
|
|
771
767
|
case "cli":
|
|
772
|
-
return await
|
|
768
|
+
return await se.fn({ command: h.code }, t, e);
|
|
773
769
|
case "node":
|
|
774
|
-
return await
|
|
770
|
+
return await oe.fn({ code: h.code }, t, e);
|
|
775
771
|
case "python":
|
|
776
|
-
return await
|
|
772
|
+
return await ie.fn({ code: h.code }, t, e);
|
|
777
773
|
default:
|
|
778
774
|
throw new Error(`Unsupported language: ${h.language}`);
|
|
779
775
|
}
|
|
780
|
-
} catch (
|
|
781
|
-
return { error:
|
|
776
|
+
} catch (r) {
|
|
777
|
+
return { error: r?.message || r.toString() };
|
|
782
778
|
}
|
|
783
779
|
}
|
|
784
|
-
},
|
|
780
|
+
}, _e = {
|
|
785
781
|
name: "fetch",
|
|
786
782
|
description: "Make HTTP request to URL",
|
|
787
783
|
args: {
|
|
@@ -790,25 +786,25 @@ const re = () => O.platform() == "win32" ? "cmd" : j`echo $SHELL`?.split("/").po
|
|
|
790
786
|
headers: { type: "object", description: "HTTP headers to send", default: {} },
|
|
791
787
|
body: { type: "object", description: "HTTP body to send" }
|
|
792
788
|
},
|
|
793
|
-
fn: (h) => new
|
|
794
|
-
},
|
|
789
|
+
fn: (h) => new J({ url: h.url, headers: h.headers }).request({ method: h.method || "GET", body: h.body })
|
|
790
|
+
}, oe = {
|
|
795
791
|
name: "exec_javascript",
|
|
796
792
|
description: "Execute commonjs javascript",
|
|
797
793
|
args: {
|
|
798
794
|
code: { type: "string", description: "CommonJS javascript", required: !0 }
|
|
799
795
|
},
|
|
800
796
|
fn: async (h) => {
|
|
801
|
-
const
|
|
802
|
-
return { ...
|
|
797
|
+
const t = I(null), e = await F({ console: t }, h.code, !0).catch((r) => t.output.error.push(r));
|
|
798
|
+
return { ...t.output, return: e, stdout: void 0, stderr: void 0 };
|
|
803
799
|
}
|
|
804
|
-
},
|
|
800
|
+
}, ie = {
|
|
805
801
|
name: "exec_javascript",
|
|
806
802
|
description: "Execute commonjs javascript",
|
|
807
803
|
args: {
|
|
808
804
|
code: { type: "string", description: "CommonJS javascript", required: !0 }
|
|
809
805
|
},
|
|
810
|
-
fn: async (h) => ({ result:
|
|
811
|
-
},
|
|
806
|
+
fn: async (h) => ({ result: T`python -c "${h.code}"` })
|
|
807
|
+
}, Te = {
|
|
812
808
|
name: "read_webpage",
|
|
813
809
|
description: "Extract clean, structured content from a webpage or convert media/documents to accessible formats",
|
|
814
810
|
args: {
|
|
@@ -826,30 +822,30 @@ const re = () => O.platform() == "win32" ? "cmd" : j`echo $SHELL`?.split("/").po
|
|
|
826
822
|
redirect: "follow"
|
|
827
823
|
}).catch((s) => {
|
|
828
824
|
throw new Error(`Failed to fetch: ${s.message}`);
|
|
829
|
-
}),
|
|
830
|
-
if (
|
|
831
|
-
return { url: h.url, error: "MIME type rejected", mimeType:
|
|
832
|
-
if (
|
|
825
|
+
}), r = e.headers.get("content-type") || "", o = r.split(";")[0].trim().toLowerCase();
|
|
826
|
+
if (r.match(/charset=([^;]+)/)?.[1], h.mimeRegex && !new RegExp(h.mimeRegex, "i").test(o))
|
|
827
|
+
return { url: h.url, error: "MIME type rejected", mimeType: o, filter: h.mimeRegex };
|
|
828
|
+
if (o.startsWith("image/") || o.startsWith("audio/") || o.startsWith("video/")) {
|
|
833
829
|
const s = await e.arrayBuffer();
|
|
834
830
|
if (s.byteLength > 10485760)
|
|
835
|
-
return { url: h.url, type: "media", mimeType:
|
|
836
|
-
const
|
|
837
|
-
return { url: h.url, type: "media", mimeType:
|
|
831
|
+
return { url: h.url, type: "media", mimeType: o, error: "File too large", size: s.byteLength, maxSize: 10485760 };
|
|
832
|
+
const c = Buffer.from(s).toString("base64");
|
|
833
|
+
return { url: h.url, type: "media", mimeType: o, dataUrl: `data:${o};base64,${c}`, size: s.byteLength };
|
|
838
834
|
}
|
|
839
|
-
if (
|
|
835
|
+
if (o.match(/^(text\/(plain|csv|xml)|application\/(json|xml|csv|x-yaml))/) || h.url.match(/\.(txt|json|xml|csv|yaml|yml|md)$/i)) {
|
|
840
836
|
const s = await e.text();
|
|
841
|
-
return { url: h.url, type: "text", mimeType:
|
|
837
|
+
return { url: h.url, type: "text", mimeType: o, content: s.slice(0, 1e5) };
|
|
842
838
|
}
|
|
843
|
-
if (
|
|
839
|
+
if (o === "application/pdf" || o.startsWith("application/") && !o.includes("html")) {
|
|
844
840
|
const s = await e.arrayBuffer();
|
|
845
841
|
if (s.byteLength > 10485760)
|
|
846
|
-
return { url: h.url, type: "binary", mimeType:
|
|
847
|
-
const
|
|
848
|
-
return { url: h.url, type: "binary", mimeType:
|
|
842
|
+
return { url: h.url, type: "binary", mimeType: o, error: "File too large", size: s.byteLength, maxSize: 10485760 };
|
|
843
|
+
const c = Buffer.from(s).toString("base64");
|
|
844
|
+
return { url: h.url, type: "binary", mimeType: o, dataUrl: `data:${o};base64,${c}`, size: s.byteLength };
|
|
849
845
|
}
|
|
850
|
-
const
|
|
846
|
+
const i = await e.text(), n = Q.load(i);
|
|
851
847
|
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();
|
|
852
|
-
const
|
|
848
|
+
const l = {
|
|
853
849
|
title: n('meta[property="og:title"]').attr("content") || n("title").text() || "",
|
|
854
850
|
description: n('meta[name="description"]').attr("content") || n('meta[property="og:description"]').attr("content") || "",
|
|
855
851
|
author: n('meta[name="author"]').attr("content") || "",
|
|
@@ -857,30 +853,30 @@ const re = () => O.platform() == "win32" ? "cmd" : j`echo $SHELL`?.split("/").po
|
|
|
857
853
|
image: n('meta[property="og:image"]').attr("content") || ""
|
|
858
854
|
};
|
|
859
855
|
let m = "";
|
|
860
|
-
const
|
|
861
|
-
for (const s of
|
|
862
|
-
const
|
|
863
|
-
if (
|
|
864
|
-
m =
|
|
856
|
+
const a = ["article", "main", '[role="main"]', ".content", ".post-content", ".entry-content", ".article-content", "body"];
|
|
857
|
+
for (const s of a) {
|
|
858
|
+
const c = n(s).first();
|
|
859
|
+
if (c.length && c.text().trim().length > 200) {
|
|
860
|
+
m = c.text();
|
|
865
861
|
break;
|
|
866
862
|
}
|
|
867
863
|
}
|
|
868
864
|
m || (m = n("body").text()), m = m.replace(/\n\s*\n\s*\n/g, `
|
|
869
865
|
|
|
870
866
|
`).replace(/[ \t]+/g, " ").trim().slice(0, 5e4);
|
|
871
|
-
let
|
|
872
|
-
return m.length < 500 && (n("a[href]").each((s,
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
}),
|
|
867
|
+
let u = [];
|
|
868
|
+
return m.length < 500 && (n("a[href]").each((s, c) => {
|
|
869
|
+
const p = n(c).attr("href"), d = n(c).text().trim();
|
|
870
|
+
p && d && !p.startsWith("#") && u.push({ text: d, href: p });
|
|
871
|
+
}), u = u.slice(0, 50)), {
|
|
876
872
|
url: h.url,
|
|
877
873
|
type: "html",
|
|
878
|
-
title:
|
|
879
|
-
description:
|
|
880
|
-
author:
|
|
881
|
-
published:
|
|
874
|
+
title: l.title.trim(),
|
|
875
|
+
description: l.description.trim(),
|
|
876
|
+
author: l.author.trim(),
|
|
877
|
+
published: l.published,
|
|
882
878
|
content: m,
|
|
883
|
-
links:
|
|
879
|
+
links: u.length ? u : void 0
|
|
884
880
|
};
|
|
885
881
|
}
|
|
886
882
|
}, je = {
|
|
@@ -891,33 +887,112 @@ const re = () => O.platform() == "win32" ? "cmd" : j`echo $SHELL`?.split("/").po
|
|
|
891
887
|
length: { type: "string", description: "Number of results to return", default: 5 }
|
|
892
888
|
},
|
|
893
889
|
fn: async (h) => {
|
|
894
|
-
const
|
|
890
|
+
const t = await fetch(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(h.query)}`, {
|
|
895
891
|
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Accept-Language": "en-US,en;q=0.9" }
|
|
896
|
-
}).then((
|
|
897
|
-
let e,
|
|
898
|
-
const
|
|
899
|
-
for (; (e =
|
|
900
|
-
let
|
|
901
|
-
if (
|
|
892
|
+
}).then((i) => i.text());
|
|
893
|
+
let e, r = /<a .*?href="(.+?)".+?<\/a>/g;
|
|
894
|
+
const o = new H();
|
|
895
|
+
for (; (e = r.exec(t)) !== null; ) {
|
|
896
|
+
let i = /uddg=(.+)&?/.exec(decodeURIComponent(e[1]))?.[1];
|
|
897
|
+
if (i && (i = decodeURIComponent(i)), i && o.add(i), o.size >= (h.length || 5)) break;
|
|
902
898
|
}
|
|
903
|
-
return
|
|
899
|
+
return o;
|
|
904
900
|
}
|
|
905
901
|
};
|
|
902
|
+
class U {
|
|
903
|
+
ua = "AiTools-Wikipedia/1.0";
|
|
904
|
+
async get(t) {
|
|
905
|
+
return (await fetch(t, { headers: { "User-Agent": this.ua } })).json();
|
|
906
|
+
}
|
|
907
|
+
api(t) {
|
|
908
|
+
const e = new URLSearchParams({ ...t, format: "json", utf8: "1" }).toString();
|
|
909
|
+
return this.get(`https://en.wikipedia.org/w/api.php?${e}`);
|
|
910
|
+
}
|
|
911
|
+
clean(t) {
|
|
912
|
+
return t.replace(/\n{3,}/g, `
|
|
913
|
+
|
|
914
|
+
`).replace(/ {2,}/g, " ").replace(/\[\d+\]/g, "").trim();
|
|
915
|
+
}
|
|
916
|
+
truncate(t, e) {
|
|
917
|
+
if (t.length <= e) return t;
|
|
918
|
+
const r = t.slice(0, e), o = r.lastIndexOf(`
|
|
919
|
+
|
|
920
|
+
`);
|
|
921
|
+
return o > e * 0.7 ? r.slice(0, o) : r;
|
|
922
|
+
}
|
|
923
|
+
async searchTitles(t, e = 6) {
|
|
924
|
+
return (await this.api({ action: "query", list: "search", srsearch: t, srlimit: e, srprop: "snippet" })).query?.search || [];
|
|
925
|
+
}
|
|
926
|
+
async fetchExtract(t, e = !1) {
|
|
927
|
+
const r = { action: "query", prop: "extracts", titles: t, explaintext: 1, redirects: 1 };
|
|
928
|
+
e && (r.exintro = 1);
|
|
929
|
+
const o = await this.api(r), i = Object.values(o.query?.pages || {})[0];
|
|
930
|
+
return this.clean(i?.extract || "");
|
|
931
|
+
}
|
|
932
|
+
pageUrl(t) {
|
|
933
|
+
return `https://en.wikipedia.org/wiki/${encodeURIComponent(t.replace(/ /g, "_"))}`;
|
|
934
|
+
}
|
|
935
|
+
stripHtml(t) {
|
|
936
|
+
return t.replace(/<[^>]+>/g, "");
|
|
937
|
+
}
|
|
938
|
+
async lookup(t, e = "intro") {
|
|
939
|
+
const r = await this.searchTitles(t, 6);
|
|
940
|
+
if (!r.length) return `❌ No Wikipedia articles found for "${t}"`;
|
|
941
|
+
const o = r[0].title, i = this.pageUrl(o), n = await this.fetchExtract(o, e === "intro"), l = this.truncate(n, e === "intro" ? 2e3 : 8e3);
|
|
942
|
+
return `## ${o}
|
|
943
|
+
🔗 ${i}
|
|
944
|
+
|
|
945
|
+
${l}`;
|
|
946
|
+
}
|
|
947
|
+
async search(t) {
|
|
948
|
+
const e = await this.searchTitles(t, 8);
|
|
949
|
+
if (!e.length) return `❌ No results for "${t}"`;
|
|
950
|
+
const r = [`### Search results for "${t}"
|
|
951
|
+
`];
|
|
952
|
+
for (let o = 0; o < e.length; o++) {
|
|
953
|
+
const i = e[o], n = this.truncate(this.stripHtml(i.snippet || ""), 150);
|
|
954
|
+
r.push(`**${o + 1}. ${i.title}**
|
|
955
|
+
${n}
|
|
956
|
+
${this.pageUrl(i.title)}`);
|
|
957
|
+
}
|
|
958
|
+
return r.join(`
|
|
959
|
+
|
|
960
|
+
`);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
const $e = {
|
|
964
|
+
name: "wikipedia_lookup",
|
|
965
|
+
description: "Get Wikipedia article content",
|
|
966
|
+
args: {
|
|
967
|
+
query: { type: "string", description: "Topic or article title", required: !0 },
|
|
968
|
+
detail: { type: "string", description: 'Content level: "intro" (summary, default) or "full" (complete article)', enum: ["intro", "full"], default: "intro" }
|
|
969
|
+
},
|
|
970
|
+
fn: async (h) => new U().lookup(h.query, h.detail || "intro")
|
|
971
|
+
}, ve = {
|
|
972
|
+
name: "wikipedia_search",
|
|
973
|
+
description: "Search Wikipedia for matching articles",
|
|
974
|
+
args: {
|
|
975
|
+
query: { type: "string", description: "Search terms", required: !0 }
|
|
976
|
+
},
|
|
977
|
+
fn: async (h) => new U().search(h.query)
|
|
978
|
+
};
|
|
906
979
|
export {
|
|
907
980
|
be as Ai,
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
981
|
+
X as Anthropic,
|
|
982
|
+
te as Audio,
|
|
983
|
+
se as CliTool,
|
|
984
|
+
xe as DateTimeTool,
|
|
985
|
+
ke as DateTimeUTCTool,
|
|
986
|
+
Se as ExecTool,
|
|
987
|
+
_e as FetchTool,
|
|
988
|
+
oe as JSTool,
|
|
989
|
+
W as LLMProvider,
|
|
917
990
|
P as OpenAi,
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
je as WebSearchTool
|
|
991
|
+
ie as PythonTool,
|
|
992
|
+
Te as ReadWebpageTool,
|
|
993
|
+
re as Vision,
|
|
994
|
+
je as WebSearchTool,
|
|
995
|
+
$e as WikipediaLookupTool,
|
|
996
|
+
ve as WikipediaSearchTool
|
|
922
997
|
};
|
|
923
998
|
//# sourceMappingURL=index.mjs.map
|