@jcheesepkg/nanobot 0.1.0
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/README.md +139 -0
- package/dist/_virtual/_rolldown/runtime.mjs +40 -0
- package/dist/agent/context.d.mts +44 -0
- package/dist/agent/context.d.mts.map +1 -0
- package/dist/agent/context.mjs +153 -0
- package/dist/agent/context.mjs.map +1 -0
- package/dist/agent/loop.d.mts +56 -0
- package/dist/agent/loop.d.mts.map +1 -0
- package/dist/agent/loop.mjs +232 -0
- package/dist/agent/loop.mjs.map +1 -0
- package/dist/agent/memory.d.mts +30 -0
- package/dist/agent/memory.d.mts.map +1 -0
- package/dist/agent/memory.mjs +77 -0
- package/dist/agent/memory.mjs.map +1 -0
- package/dist/agent/skills.d.mts +43 -0
- package/dist/agent/skills.d.mts.map +1 -0
- package/dist/agent/skills.mjs +159 -0
- package/dist/agent/skills.mjs.map +1 -0
- package/dist/agent/subagent.d.mts +39 -0
- package/dist/agent/subagent.d.mts.map +1 -0
- package/dist/agent/subagent.mjs +167 -0
- package/dist/agent/subagent.mjs.map +1 -0
- package/dist/agent/tools/base.d.mts +16 -0
- package/dist/agent/tools/base.d.mts.map +1 -0
- package/dist/agent/tools/base.mjs +19 -0
- package/dist/agent/tools/base.mjs.map +1 -0
- package/dist/agent/tools/cron.d.mts +49 -0
- package/dist/agent/tools/cron.d.mts.map +1 -0
- package/dist/agent/tools/cron.mjs +101 -0
- package/dist/agent/tools/cron.mjs.map +1 -0
- package/dist/agent/tools/filesystem.d.mts +87 -0
- package/dist/agent/tools/filesystem.d.mts.map +1 -0
- package/dist/agent/tools/filesystem.mjs +145 -0
- package/dist/agent/tools/filesystem.mjs.map +1 -0
- package/dist/agent/tools/message.d.mts +44 -0
- package/dist/agent/tools/message.d.mts.map +1 -0
- package/dist/agent/tools/message.mjs +68 -0
- package/dist/agent/tools/message.mjs.map +1 -0
- package/dist/agent/tools/registry.d.mts +29 -0
- package/dist/agent/tools/registry.d.mts.map +1 -0
- package/dist/agent/tools/registry.mjs +49 -0
- package/dist/agent/tools/registry.mjs.map +1 -0
- package/dist/agent/tools/shell.d.mts +36 -0
- package/dist/agent/tools/shell.d.mts.map +1 -0
- package/dist/agent/tools/shell.mjs +73 -0
- package/dist/agent/tools/shell.mjs.map +1 -0
- package/dist/agent/tools/spawn.d.mts +35 -0
- package/dist/agent/tools/spawn.d.mts.map +1 -0
- package/dist/agent/tools/spawn.mjs +50 -0
- package/dist/agent/tools/spawn.mjs.map +1 -0
- package/dist/agent/tools/web.d.mts +63 -0
- package/dist/agent/tools/web.d.mts.map +1 -0
- package/dist/agent/tools/web.mjs +195 -0
- package/dist/agent/tools/web.mjs.map +1 -0
- package/dist/bus/events.d.mts +29 -0
- package/dist/bus/events.d.mts.map +1 -0
- package/dist/bus/events.mjs +30 -0
- package/dist/bus/events.mjs.map +1 -0
- package/dist/bus/queue.d.mts +38 -0
- package/dist/bus/queue.d.mts.map +1 -0
- package/dist/bus/queue.mjs +90 -0
- package/dist/bus/queue.mjs.map +1 -0
- package/dist/channels/base.d.mts +31 -0
- package/dist/channels/base.d.mts.map +1 -0
- package/dist/channels/base.mjs +49 -0
- package/dist/channels/base.mjs.map +1 -0
- package/dist/channels/manager.d.mts +30 -0
- package/dist/channels/manager.d.mts.map +1 -0
- package/dist/channels/manager.mjs +100 -0
- package/dist/channels/manager.mjs.map +1 -0
- package/dist/channels/telegram.d.mts +23 -0
- package/dist/channels/telegram.d.mts.map +1 -0
- package/dist/channels/telegram.mjs +161 -0
- package/dist/channels/telegram.mjs.map +1 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +337 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/config/loader.d.mts +14 -0
- package/dist/config/loader.d.mts.map +1 -0
- package/dist/config/loader.mjs +40 -0
- package/dist/config/loader.mjs.map +1 -0
- package/dist/config/schema.d.mts +724 -0
- package/dist/config/schema.d.mts.map +1 -0
- package/dist/config/schema.mjs +123 -0
- package/dist/config/schema.mjs.map +1 -0
- package/dist/cron/service.d.mts +42 -0
- package/dist/cron/service.d.mts.map +1 -0
- package/dist/cron/service.mjs +256 -0
- package/dist/cron/service.mjs.map +1 -0
- package/dist/cron/types.d.mts +55 -0
- package/dist/cron/types.d.mts.map +1 -0
- package/dist/cron/types.mjs +30 -0
- package/dist/cron/types.mjs.map +1 -0
- package/dist/heartbeat/service.d.mts +29 -0
- package/dist/heartbeat/service.d.mts.map +1 -0
- package/dist/heartbeat/service.mjs +96 -0
- package/dist/heartbeat/service.mjs.map +1 -0
- package/dist/index.d.mts +23 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +23 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers/base.d.mts +65 -0
- package/dist/providers/base.d.mts.map +1 -0
- package/dist/providers/base.mjs +1 -0
- package/dist/providers/openai-provider.d.mts +27 -0
- package/dist/providers/openai-provider.d.mts.map +1 -0
- package/dist/providers/openai-provider.mjs +67 -0
- package/dist/providers/openai-provider.mjs.map +1 -0
- package/dist/providers/transcription.d.mts +14 -0
- package/dist/providers/transcription.d.mts.map +1 -0
- package/dist/providers/transcription.mjs +46 -0
- package/dist/providers/transcription.mjs.map +1 -0
- package/dist/session/manager.d.mts +56 -0
- package/dist/session/manager.d.mts.map +1 -0
- package/dist/session/manager.mjs +144 -0
- package/dist/session/manager.mjs.map +1 -0
- package/dist/utils/helpers.d.mts +26 -0
- package/dist/utils/helpers.d.mts.map +1 -0
- package/dist/utils/helpers.mjs +60 -0
- package/dist/utils/helpers.mjs.map +1 -0
- package/dist/utils/which.d.mts +6 -0
- package/dist/utils/which.d.mts.map +1 -0
- package/dist/utils/which.mjs +20 -0
- package/dist/utils/which.mjs.map +1 -0
- package/package.json +45 -0
- package/skills/cron/SKILL.md +40 -0
- package/skills/github/SKILL.md +48 -0
- package/skills/skill-creator/SKILL.md +73 -0
- package/skills/summarize/SKILL.md +47 -0
- package/skills/weather/SKILL.md +43 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Tool } from "./base.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/agent/tools/web.ts
|
|
4
|
+
const USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36";
|
|
5
|
+
/** Strip HTML tags and decode entities. */
|
|
6
|
+
function stripTags(text) {
|
|
7
|
+
let result = text;
|
|
8
|
+
result = result.replace(/<script[\s\S]*?<\/script>/gi, "");
|
|
9
|
+
result = result.replace(/<style[\s\S]*?<\/style>/gi, "");
|
|
10
|
+
result = result.replace(/<[^>]+>/g, "");
|
|
11
|
+
result = result.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'").replace(/ /g, " ");
|
|
12
|
+
return result.trim();
|
|
13
|
+
}
|
|
14
|
+
/** Normalize whitespace. */
|
|
15
|
+
function normalize(text) {
|
|
16
|
+
return text.replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
17
|
+
}
|
|
18
|
+
/** Validate URL. */
|
|
19
|
+
function validateUrl(url) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = new URL(url);
|
|
22
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return {
|
|
23
|
+
valid: false,
|
|
24
|
+
error: `Only http/https allowed, got '${parsed.protocol}'`
|
|
25
|
+
};
|
|
26
|
+
if (!parsed.hostname) return {
|
|
27
|
+
valid: false,
|
|
28
|
+
error: "Missing domain"
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
valid: true,
|
|
32
|
+
error: ""
|
|
33
|
+
};
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return {
|
|
36
|
+
valid: false,
|
|
37
|
+
error: String(err)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Convert HTML to basic markdown. */
|
|
42
|
+
function htmlToMarkdown(html) {
|
|
43
|
+
let text = html;
|
|
44
|
+
text = text.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_m, url, inner) => `[${stripTags(inner)}](${url})`);
|
|
45
|
+
text = text.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_m, level, inner) => `\n${"#".repeat(Number(level))} ${stripTags(inner)}\n`);
|
|
46
|
+
text = text.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_m, inner) => `\n- ${stripTags(inner)}`);
|
|
47
|
+
text = text.replace(/<\/(p|div|section|article)>/gi, "\n\n");
|
|
48
|
+
text = text.replace(/<(br|hr)\s*\/?>/gi, "\n");
|
|
49
|
+
return normalize(stripTags(text));
|
|
50
|
+
}
|
|
51
|
+
/** Search the web using Brave Search API. */
|
|
52
|
+
var WebSearchTool = class extends Tool {
|
|
53
|
+
name = "web_search";
|
|
54
|
+
description = "Search the web. Returns titles, URLs, and snippets.";
|
|
55
|
+
parameters = {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
query: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Search query"
|
|
61
|
+
},
|
|
62
|
+
count: {
|
|
63
|
+
type: "integer",
|
|
64
|
+
description: "Results (1-10)",
|
|
65
|
+
minimum: 1,
|
|
66
|
+
maximum: 10
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
required: ["query"]
|
|
70
|
+
};
|
|
71
|
+
apiKey;
|
|
72
|
+
maxResults;
|
|
73
|
+
constructor(params) {
|
|
74
|
+
super();
|
|
75
|
+
this.apiKey = params?.apiKey ?? process.env.BRAVE_API_KEY ?? "";
|
|
76
|
+
this.maxResults = params?.maxResults ?? 5;
|
|
77
|
+
}
|
|
78
|
+
async execute(args) {
|
|
79
|
+
const query = String(args.query);
|
|
80
|
+
const count = Math.min(Math.max(args.count ? Number(args.count) : this.maxResults, 1), 10);
|
|
81
|
+
if (!this.apiKey) return "Error: BRAVE_API_KEY not configured";
|
|
82
|
+
try {
|
|
83
|
+
const url = new URL("https://api.search.brave.com/res/v1/web/search");
|
|
84
|
+
url.searchParams.set("q", query);
|
|
85
|
+
url.searchParams.set("count", String(count));
|
|
86
|
+
const resp = await fetch(url.toString(), {
|
|
87
|
+
headers: {
|
|
88
|
+
Accept: "application/json",
|
|
89
|
+
"X-Subscription-Token": this.apiKey
|
|
90
|
+
},
|
|
91
|
+
signal: AbortSignal.timeout(1e4)
|
|
92
|
+
});
|
|
93
|
+
if (!resp.ok) return `Error: Search API returned ${resp.status}`;
|
|
94
|
+
const results = (await resp.json()).web?.results ?? [];
|
|
95
|
+
if (results.length === 0) return `No results for: ${query}`;
|
|
96
|
+
const lines = [`Results for: ${query}\n`];
|
|
97
|
+
for (let i = 0; i < Math.min(results.length, count); i++) {
|
|
98
|
+
const item = results[i];
|
|
99
|
+
lines.push(`${i + 1}. ${item.title ?? ""}\n ${item.url ?? ""}`);
|
|
100
|
+
if (item.description) lines.push(` ${item.description}`);
|
|
101
|
+
}
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
} catch (err) {
|
|
104
|
+
return `Error: ${err instanceof Error ? err.message : err}`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
/** Fetch and extract content from a URL. */
|
|
109
|
+
var WebFetchTool = class extends Tool {
|
|
110
|
+
name = "web_fetch";
|
|
111
|
+
description = "Fetch URL and extract readable content (HTML -> markdown/text).";
|
|
112
|
+
parameters = {
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: {
|
|
115
|
+
url: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "URL to fetch"
|
|
118
|
+
},
|
|
119
|
+
extractMode: {
|
|
120
|
+
type: "string",
|
|
121
|
+
enum: ["markdown", "text"],
|
|
122
|
+
description: "Extract mode"
|
|
123
|
+
},
|
|
124
|
+
maxChars: {
|
|
125
|
+
type: "integer",
|
|
126
|
+
minimum: 100
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
required: ["url"]
|
|
130
|
+
};
|
|
131
|
+
defaultMaxChars;
|
|
132
|
+
constructor(params) {
|
|
133
|
+
super();
|
|
134
|
+
this.defaultMaxChars = params?.maxChars ?? 5e4;
|
|
135
|
+
}
|
|
136
|
+
async execute(args) {
|
|
137
|
+
const url = String(args.url);
|
|
138
|
+
const extractMode = String(args.extractMode ?? "markdown");
|
|
139
|
+
const maxChars = args.maxChars ? Number(args.maxChars) : this.defaultMaxChars;
|
|
140
|
+
const { valid, error: validationError } = validateUrl(url);
|
|
141
|
+
if (!valid) return JSON.stringify({
|
|
142
|
+
error: `URL validation failed: ${validationError}`,
|
|
143
|
+
url
|
|
144
|
+
});
|
|
145
|
+
try {
|
|
146
|
+
const resp = await fetch(url, {
|
|
147
|
+
headers: { "User-Agent": USER_AGENT },
|
|
148
|
+
redirect: "follow",
|
|
149
|
+
signal: AbortSignal.timeout(3e4)
|
|
150
|
+
});
|
|
151
|
+
if (!resp.ok) return JSON.stringify({
|
|
152
|
+
error: `HTTP ${resp.status}`,
|
|
153
|
+
url
|
|
154
|
+
});
|
|
155
|
+
const contentType = resp.headers.get("content-type") ?? "";
|
|
156
|
+
const body = await resp.text();
|
|
157
|
+
let text;
|
|
158
|
+
let extractor;
|
|
159
|
+
if (contentType.includes("application/json")) {
|
|
160
|
+
try {
|
|
161
|
+
text = JSON.stringify(JSON.parse(body), null, 2);
|
|
162
|
+
} catch {
|
|
163
|
+
text = body;
|
|
164
|
+
}
|
|
165
|
+
extractor = "json";
|
|
166
|
+
} else if (contentType.includes("text/html") || body.slice(0, 256).toLowerCase().startsWith("<!doctype") || body.slice(0, 256).toLowerCase().startsWith("<html")) {
|
|
167
|
+
text = extractMode === "markdown" ? htmlToMarkdown(body) : stripTags(body);
|
|
168
|
+
extractor = "html";
|
|
169
|
+
} else {
|
|
170
|
+
text = body;
|
|
171
|
+
extractor = "raw";
|
|
172
|
+
}
|
|
173
|
+
const truncated = text.length > maxChars;
|
|
174
|
+
if (truncated) text = text.slice(0, maxChars);
|
|
175
|
+
return JSON.stringify({
|
|
176
|
+
url,
|
|
177
|
+
finalUrl: resp.url,
|
|
178
|
+
status: resp.status,
|
|
179
|
+
extractor,
|
|
180
|
+
truncated,
|
|
181
|
+
length: text.length,
|
|
182
|
+
text
|
|
183
|
+
});
|
|
184
|
+
} catch (err) {
|
|
185
|
+
return JSON.stringify({
|
|
186
|
+
error: err instanceof Error ? err.message : String(err),
|
|
187
|
+
url
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
//#endregion
|
|
194
|
+
export { WebFetchTool, WebSearchTool };
|
|
195
|
+
//# sourceMappingURL=web.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.mjs","names":[],"sources":["../../../src/agent/tools/web.ts"],"sourcesContent":["import { Tool } from \"./base.js\";\n\nconst USER_AGENT =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36\";\nconst MAX_REDIRECTS = 5;\n\n/** Strip HTML tags and decode entities. */\nfunction stripTags(text: string): string {\n let result = text;\n result = result.replace(/<script[\\s\\S]*?<\\/script>/gi, \"\");\n result = result.replace(/<style[\\s\\S]*?<\\/style>/gi, \"\");\n result = result.replace(/<[^>]+>/g, \"\");\n // Decode common HTML entities\n result = result\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/ /g, \" \");\n return result.trim();\n}\n\n/** Normalize whitespace. */\nfunction normalize(text: string): string {\n return text\n .replace(/[ \\t]+/g, \" \")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\n/** Validate URL. */\nfunction validateUrl(url: string): { valid: boolean; error: string } {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n return {\n valid: false,\n error: `Only http/https allowed, got '${parsed.protocol}'`,\n };\n }\n if (!parsed.hostname) {\n return { valid: false, error: \"Missing domain\" };\n }\n return { valid: true, error: \"\" };\n } catch (err) {\n return { valid: false, error: String(err) };\n }\n}\n\n/** Convert HTML to basic markdown. */\nfunction htmlToMarkdown(html: string): string {\n let text = html;\n // Links\n text = text.replace(\n /<a\\s+[^>]*href=[\"']([^\"']+)[\"'][^>]*>([\\s\\S]*?)<\\/a>/gi,\n (_m, url, inner) => `[${stripTags(inner)}](${url})`,\n );\n // Headings\n text = text.replace(\n /<h([1-6])[^>]*>([\\s\\S]*?)<\\/h\\1>/gi,\n (_m, level, inner) => `\\n${\"#\".repeat(Number(level))} ${stripTags(inner)}\\n`,\n );\n // List items\n text = text.replace(\n /<li[^>]*>([\\s\\S]*?)<\\/li>/gi,\n (_m, inner) => `\\n- ${stripTags(inner)}`,\n );\n // Block elements\n text = text.replace(/<\\/(p|div|section|article)>/gi, \"\\n\\n\");\n text = text.replace(/<(br|hr)\\s*\\/?>/gi, \"\\n\");\n return normalize(stripTags(text));\n}\n\n/** Search the web using Brave Search API. */\nexport class WebSearchTool extends Tool {\n readonly name = \"web_search\";\n readonly description = \"Search the web. Returns titles, URLs, and snippets.\";\n readonly parameters = {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"Search query\" },\n count: {\n type: \"integer\",\n description: \"Results (1-10)\",\n minimum: 1,\n maximum: 10,\n },\n },\n required: [\"query\"],\n };\n\n private apiKey: string;\n private maxResults: number;\n\n constructor(params?: { apiKey?: string; maxResults?: number }) {\n super();\n this.apiKey = params?.apiKey ?? process.env.BRAVE_API_KEY ?? \"\";\n this.maxResults = params?.maxResults ?? 5;\n }\n\n async execute(args: Record<string, unknown>): Promise<string> {\n const query = String(args.query);\n const count = Math.min(\n Math.max(args.count ? Number(args.count) : this.maxResults, 1),\n 10,\n );\n\n if (!this.apiKey) {\n return \"Error: BRAVE_API_KEY not configured\";\n }\n\n try {\n const url = new URL(\"https://api.search.brave.com/res/v1/web/search\");\n url.searchParams.set(\"q\", query);\n url.searchParams.set(\"count\", String(count));\n\n const resp = await fetch(url.toString(), {\n headers: {\n Accept: \"application/json\",\n \"X-Subscription-Token\": this.apiKey,\n },\n signal: AbortSignal.timeout(10000),\n });\n\n if (!resp.ok) {\n return `Error: Search API returned ${resp.status}`;\n }\n\n const data = (await resp.json()) as {\n web?: { results?: Array<{ title?: string; url?: string; description?: string }> };\n };\n const results = data.web?.results ?? [];\n\n if (results.length === 0) {\n return `No results for: ${query}`;\n }\n\n const lines = [`Results for: ${query}\\n`];\n for (let i = 0; i < Math.min(results.length, count); i++) {\n const item = results[i];\n lines.push(`${i + 1}. ${item.title ?? \"\"}\\n ${item.url ?? \"\"}`);\n if (item.description) {\n lines.push(` ${item.description}`);\n }\n }\n return lines.join(\"\\n\");\n } catch (err) {\n return `Error: ${err instanceof Error ? err.message : err}`;\n }\n }\n}\n\n/** Fetch and extract content from a URL. */\nexport class WebFetchTool extends Tool {\n readonly name = \"web_fetch\";\n readonly description =\n \"Fetch URL and extract readable content (HTML -> markdown/text).\";\n readonly parameters = {\n type: \"object\",\n properties: {\n url: { type: \"string\", description: \"URL to fetch\" },\n extractMode: {\n type: \"string\",\n enum: [\"markdown\", \"text\"],\n description: \"Extract mode\",\n },\n maxChars: { type: \"integer\", minimum: 100 },\n },\n required: [\"url\"],\n };\n\n private defaultMaxChars: number;\n\n constructor(params?: { maxChars?: number }) {\n super();\n this.defaultMaxChars = params?.maxChars ?? 50000;\n }\n\n async execute(args: Record<string, unknown>): Promise<string> {\n const url = String(args.url);\n const extractMode = String(args.extractMode ?? \"markdown\");\n const maxChars = args.maxChars\n ? Number(args.maxChars)\n : this.defaultMaxChars;\n\n const { valid, error: validationError } = validateUrl(url);\n if (!valid) {\n return JSON.stringify({\n error: `URL validation failed: ${validationError}`,\n url,\n });\n }\n\n try {\n const resp = await fetch(url, {\n headers: { \"User-Agent\": USER_AGENT },\n redirect: \"follow\",\n signal: AbortSignal.timeout(30000),\n });\n\n if (!resp.ok) {\n return JSON.stringify({\n error: `HTTP ${resp.status}`,\n url,\n });\n }\n\n const contentType = resp.headers.get(\"content-type\") ?? \"\";\n const body = await resp.text();\n let text: string;\n let extractor: string;\n\n if (contentType.includes(\"application/json\")) {\n try {\n text = JSON.stringify(JSON.parse(body), null, 2);\n } catch {\n text = body;\n }\n extractor = \"json\";\n } else if (\n contentType.includes(\"text/html\") ||\n body.slice(0, 256).toLowerCase().startsWith(\"<!doctype\") ||\n body.slice(0, 256).toLowerCase().startsWith(\"<html\")\n ) {\n // Extract readable content from HTML\n text =\n extractMode === \"markdown\"\n ? htmlToMarkdown(body)\n : stripTags(body);\n extractor = \"html\";\n } else {\n text = body;\n extractor = \"raw\";\n }\n\n const truncated = text.length > maxChars;\n if (truncated) {\n text = text.slice(0, maxChars);\n }\n\n return JSON.stringify({\n url,\n finalUrl: resp.url,\n status: resp.status,\n extractor,\n truncated,\n length: text.length,\n text,\n });\n } catch (err) {\n return JSON.stringify({\n error: err instanceof Error ? err.message : String(err),\n url,\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/bus/events.d.ts
|
|
2
|
+
/** Message received from a chat channel. */
|
|
3
|
+
interface InboundMessage {
|
|
4
|
+
channel: string;
|
|
5
|
+
senderId: string;
|
|
6
|
+
chatId: string;
|
|
7
|
+
content: string;
|
|
8
|
+
timestamp: Date;
|
|
9
|
+
media: string[];
|
|
10
|
+
metadata: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
/** Message to send to a chat channel. */
|
|
13
|
+
interface OutboundMessage {
|
|
14
|
+
channel: string;
|
|
15
|
+
chatId: string;
|
|
16
|
+
content: string;
|
|
17
|
+
replyTo?: string;
|
|
18
|
+
media: string[];
|
|
19
|
+
metadata: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
/** Create an InboundMessage with defaults. */
|
|
22
|
+
declare function createInboundMessage(partial: Pick<InboundMessage, "channel" | "senderId" | "chatId" | "content"> & Partial<InboundMessage>): InboundMessage;
|
|
23
|
+
/** Create an OutboundMessage with defaults. */
|
|
24
|
+
declare function createOutboundMessage(partial: Pick<OutboundMessage, "channel" | "chatId" | "content"> & Partial<OutboundMessage>): OutboundMessage;
|
|
25
|
+
/** Get session key from an inbound message. */
|
|
26
|
+
declare function getSessionKey(msg: InboundMessage): string;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { InboundMessage, OutboundMessage, createInboundMessage, createOutboundMessage, getSessionKey };
|
|
29
|
+
//# sourceMappingURL=events.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.mts","names":[],"sources":["../../src/bus/events.ts"],"mappings":";;UACiB,cAAA;EACf,OAAA;EACA,QAAA;EACA,MAAA;EACA,OAAA;EACA,SAAA,EAAW,IAAA;EACX,KAAA;EACA,QAAA,EAAU,MAAA;AAAA;;UAIK,eAAA;EACf,OAAA;EACA,MAAA;EACA,OAAA;EACA,OAAA;EACA,KAAA;EACA,QAAA,EAAU,MAAA;AAAA;;iBAII,oBAAA,CACd,OAAA,EAAS,IAAA,CAAK,cAAA,mDACZ,OAAA,CAAQ,cAAA,IACT,cAAA;;iBAUa,qBAAA,CACd,OAAA,EAAS,IAAA,CAAK,eAAA,sCACZ,OAAA,CAAQ,eAAA,IACT,eAAA;;iBASa,aAAA,CAAc,GAAA,EAAK,cAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { __esmMin } from "../_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/bus/events.ts
|
|
4
|
+
/** Create an InboundMessage with defaults. */
|
|
5
|
+
function createInboundMessage(partial) {
|
|
6
|
+
return {
|
|
7
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
8
|
+
media: [],
|
|
9
|
+
metadata: {},
|
|
10
|
+
...partial
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** Create an OutboundMessage with defaults. */
|
|
14
|
+
function createOutboundMessage(partial) {
|
|
15
|
+
return {
|
|
16
|
+
media: [],
|
|
17
|
+
metadata: {},
|
|
18
|
+
...partial
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Get session key from an inbound message. */
|
|
22
|
+
function getSessionKey(msg) {
|
|
23
|
+
return `${msg.channel}:${msg.chatId}`;
|
|
24
|
+
}
|
|
25
|
+
var init_events = __esmMin((() => {}));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
init_events();
|
|
29
|
+
export { createInboundMessage, createOutboundMessage, getSessionKey, init_events };
|
|
30
|
+
//# sourceMappingURL=events.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.mjs","names":[],"sources":["../../src/bus/events.ts"],"sourcesContent":["/** Message received from a chat channel. */\nexport interface InboundMessage {\n channel: string; // telegram, discord, etc.\n senderId: string; // User identifier\n chatId: string; // Chat/channel identifier\n content: string; // Message text\n timestamp: Date;\n media: string[]; // Media file paths\n metadata: Record<string, unknown>; // Channel-specific data\n}\n\n/** Message to send to a chat channel. */\nexport interface OutboundMessage {\n channel: string;\n chatId: string;\n content: string;\n replyTo?: string;\n media: string[];\n metadata: Record<string, unknown>;\n}\n\n/** Create an InboundMessage with defaults. */\nexport function createInboundMessage(\n partial: Pick<InboundMessage, \"channel\" | \"senderId\" | \"chatId\" | \"content\"> &\n Partial<InboundMessage>,\n): InboundMessage {\n return {\n timestamp: new Date(),\n media: [],\n metadata: {},\n ...partial,\n };\n}\n\n/** Create an OutboundMessage with defaults. */\nexport function createOutboundMessage(\n partial: Pick<OutboundMessage, \"channel\" | \"chatId\" | \"content\"> &\n Partial<OutboundMessage>,\n): OutboundMessage {\n return {\n media: [],\n metadata: {},\n ...partial,\n };\n}\n\n/** Get session key from an inbound message. */\nexport function getSessionKey(msg: InboundMessage): string {\n return `${msg.channel}:${msg.chatId}`;\n}\n"],"mappings":";;;;AAsBA,SAAgB,qBACd,SAEgB;AAChB,QAAO;EACL,2BAAW,IAAI,MAAM;EACrB,OAAO,EAAE;EACT,UAAU,EAAE;EACZ,GAAG;EACJ;;;AAIH,SAAgB,sBACd,SAEiB;AACjB,QAAO;EACL,OAAO,EAAE;EACT,UAAU,EAAE;EACZ,GAAG;EACJ;;;AAIH,SAAgB,cAAc,KAA6B;AACzD,QAAO,GAAG,IAAI,QAAQ,GAAG,IAAI"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { InboundMessage, OutboundMessage } from "./events.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/bus/queue.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Simple async queue. Mimics Python's asyncio.Queue.
|
|
6
|
+
*/
|
|
7
|
+
declare class AsyncQueue<T> {
|
|
8
|
+
private queue;
|
|
9
|
+
private waiters;
|
|
10
|
+
put(item: T): Promise<void>;
|
|
11
|
+
get(): Promise<T>;
|
|
12
|
+
get size(): number;
|
|
13
|
+
}
|
|
14
|
+
type OutboundCallback = (msg: OutboundMessage) => Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Async message bus that decouples chat channels from the agent core.
|
|
17
|
+
*
|
|
18
|
+
* Channels push messages to the inbound queue, and the agent processes
|
|
19
|
+
* them and pushes responses to the outbound queue.
|
|
20
|
+
*/
|
|
21
|
+
declare class MessageBus {
|
|
22
|
+
readonly inbound: AsyncQueue<InboundMessage>;
|
|
23
|
+
readonly outbound: AsyncQueue<OutboundMessage>;
|
|
24
|
+
private outboundSubscribers;
|
|
25
|
+
private _running;
|
|
26
|
+
publishInbound(msg: InboundMessage): Promise<void>;
|
|
27
|
+
consumeInbound(): Promise<InboundMessage>;
|
|
28
|
+
publishOutbound(msg: OutboundMessage): Promise<void>;
|
|
29
|
+
consumeOutbound(): Promise<OutboundMessage>;
|
|
30
|
+
subscribeOutbound(channel: string, callback: OutboundCallback): void;
|
|
31
|
+
dispatchOutbound(): Promise<void>;
|
|
32
|
+
stop(): void;
|
|
33
|
+
get inboundSize(): number;
|
|
34
|
+
get outboundSize(): number;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { MessageBus };
|
|
38
|
+
//# sourceMappingURL=queue.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.mts","names":[],"sources":["../../src/bus/queue.ts"],"mappings":";;;;;AAAmE;cAK7D,UAAA;EAAA,QACI,KAAA;EAAA,QACA,OAAA;EAEF,GAAA,CAAI,IAAA,EAAM,CAAA,GAAI,OAAA;EASd,GAAA,CAAA,GAAO,OAAA,CAAQ,CAAA;EAAA,IAUjB,IAAA,CAAA;AAAA;AAAA,KAKD,gBAAA,IAAoB,GAAA,EAAK,eAAA,KAAoB,OAAA;;;;;;;cAQrC,UAAA;EAAA,SACF,OAAA,EAAO,UAAA,CAAA,cAAA;EAAA,SACP,QAAA,EAAQ,UAAA,CAAA,eAAA;EAAA,QACT,mBAAA;EAAA,QACA,QAAA;EAEF,cAAA,CAAe,GAAA,EAAK,cAAA,GAAiB,OAAA;EAIrC,cAAA,CAAA,GAAkB,OAAA,CAAQ,cAAA;EAI1B,eAAA,CAAgB,GAAA,EAAK,eAAA,GAAkB,OAAA;EAIvC,eAAA,CAAA,GAAmB,OAAA,CAAQ,eAAA;EAIjC,iBAAA,CAAkB,OAAA,UAAiB,QAAA,EAAU,gBAAA;EAMvC,gBAAA,CAAA,GAAoB,OAAA;EAmB1B,IAAA,CAAA;EAAA,IAII,WAAA,CAAA;EAAA,IAIA,YAAA,CAAA;AAAA"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//#region src/bus/queue.ts
|
|
2
|
+
/**
|
|
3
|
+
* Simple async queue. Mimics Python's asyncio.Queue.
|
|
4
|
+
*/
|
|
5
|
+
var AsyncQueue = class {
|
|
6
|
+
queue = [];
|
|
7
|
+
waiters = [];
|
|
8
|
+
async put(item) {
|
|
9
|
+
const waiter = this.waiters.shift();
|
|
10
|
+
if (waiter) waiter(item);
|
|
11
|
+
else this.queue.push(item);
|
|
12
|
+
}
|
|
13
|
+
async get() {
|
|
14
|
+
const item = this.queue.shift();
|
|
15
|
+
if (item !== void 0) return item;
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
this.waiters.push(resolve);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
get size() {
|
|
21
|
+
return this.queue.length;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Async message bus that decouples chat channels from the agent core.
|
|
26
|
+
*
|
|
27
|
+
* Channels push messages to the inbound queue, and the agent processes
|
|
28
|
+
* them and pushes responses to the outbound queue.
|
|
29
|
+
*/
|
|
30
|
+
var MessageBus = class {
|
|
31
|
+
inbound = new AsyncQueue();
|
|
32
|
+
outbound = new AsyncQueue();
|
|
33
|
+
outboundSubscribers = /* @__PURE__ */ new Map();
|
|
34
|
+
_running = false;
|
|
35
|
+
async publishInbound(msg) {
|
|
36
|
+
await this.inbound.put(msg);
|
|
37
|
+
}
|
|
38
|
+
async consumeInbound() {
|
|
39
|
+
return this.inbound.get();
|
|
40
|
+
}
|
|
41
|
+
async publishOutbound(msg) {
|
|
42
|
+
await this.outbound.put(msg);
|
|
43
|
+
}
|
|
44
|
+
async consumeOutbound() {
|
|
45
|
+
return this.outbound.get();
|
|
46
|
+
}
|
|
47
|
+
subscribeOutbound(channel, callback) {
|
|
48
|
+
const subs = this.outboundSubscribers.get(channel) ?? [];
|
|
49
|
+
subs.push(callback);
|
|
50
|
+
this.outboundSubscribers.set(channel, subs);
|
|
51
|
+
}
|
|
52
|
+
async dispatchOutbound() {
|
|
53
|
+
this._running = true;
|
|
54
|
+
while (this._running) try {
|
|
55
|
+
const msg = await withTimeout(this.outbound.get(), 1e3);
|
|
56
|
+
const subscribers = this.outboundSubscribers.get(msg.channel) ?? [];
|
|
57
|
+
for (const callback of subscribers) try {
|
|
58
|
+
await callback(msg);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error(`Error dispatching to ${msg.channel}:`, err);
|
|
61
|
+
}
|
|
62
|
+
} catch {}
|
|
63
|
+
}
|
|
64
|
+
stop() {
|
|
65
|
+
this._running = false;
|
|
66
|
+
}
|
|
67
|
+
get inboundSize() {
|
|
68
|
+
return this.inbound.size;
|
|
69
|
+
}
|
|
70
|
+
get outboundSize() {
|
|
71
|
+
return this.outbound.size;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/** Promise.race with a timeout. Rejects on timeout. */
|
|
75
|
+
function withTimeout(promise, ms) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("timeout")), ms);
|
|
78
|
+
promise.then((val) => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
resolve(val);
|
|
81
|
+
}, (err) => {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
reject(err);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
export { MessageBus };
|
|
90
|
+
//# sourceMappingURL=queue.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.mjs","names":[],"sources":["../../src/bus/queue.ts"],"sourcesContent":["import type { InboundMessage, OutboundMessage } from \"./events.js\";\n\n/**\n * Simple async queue. Mimics Python's asyncio.Queue.\n */\nclass AsyncQueue<T> {\n private queue: T[] = [];\n private waiters: Array<(value: T) => void> = [];\n\n async put(item: T): Promise<void> {\n const waiter = this.waiters.shift();\n if (waiter) {\n waiter(item);\n } else {\n this.queue.push(item);\n }\n }\n\n async get(): Promise<T> {\n const item = this.queue.shift();\n if (item !== undefined) {\n return item;\n }\n return new Promise<T>((resolve) => {\n this.waiters.push(resolve);\n });\n }\n\n get size(): number {\n return this.queue.length;\n }\n}\n\ntype OutboundCallback = (msg: OutboundMessage) => Promise<void>;\n\n/**\n * Async message bus that decouples chat channels from the agent core.\n *\n * Channels push messages to the inbound queue, and the agent processes\n * them and pushes responses to the outbound queue.\n */\nexport class MessageBus {\n readonly inbound = new AsyncQueue<InboundMessage>();\n readonly outbound = new AsyncQueue<OutboundMessage>();\n private outboundSubscribers = new Map<string, OutboundCallback[]>();\n private _running = false;\n\n async publishInbound(msg: InboundMessage): Promise<void> {\n await this.inbound.put(msg);\n }\n\n async consumeInbound(): Promise<InboundMessage> {\n return this.inbound.get();\n }\n\n async publishOutbound(msg: OutboundMessage): Promise<void> {\n await this.outbound.put(msg);\n }\n\n async consumeOutbound(): Promise<OutboundMessage> {\n return this.outbound.get();\n }\n\n subscribeOutbound(channel: string, callback: OutboundCallback): void {\n const subs = this.outboundSubscribers.get(channel) ?? [];\n subs.push(callback);\n this.outboundSubscribers.set(channel, subs);\n }\n\n async dispatchOutbound(): Promise<void> {\n this._running = true;\n while (this._running) {\n try {\n const msg = await withTimeout(this.outbound.get(), 1000);\n const subscribers = this.outboundSubscribers.get(msg.channel) ?? [];\n for (const callback of subscribers) {\n try {\n await callback(msg);\n } catch (err) {\n console.error(`Error dispatching to ${msg.channel}:`, err);\n }\n }\n } catch {\n // timeout, continue loop\n }\n }\n }\n\n stop(): void {\n this._running = false;\n }\n\n get inboundSize(): number {\n return this.inbound.size;\n }\n\n get outboundSize(): number {\n return this.outbound.size;\n }\n}\n\n/** Promise.race with a timeout. Rejects on timeout. */\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(\"timeout\")), ms);\n promise.then(\n (val) => {\n clearTimeout(timer);\n resolve(val);\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n },\n );\n });\n}\n"],"mappings":";;;;AAKA,IAAM,aAAN,MAAoB;CAClB,AAAQ,QAAa,EAAE;CACvB,AAAQ,UAAqC,EAAE;CAE/C,MAAM,IAAI,MAAwB;EAChC,MAAM,SAAS,KAAK,QAAQ,OAAO;AACnC,MAAI,OACF,QAAO,KAAK;MAEZ,MAAK,MAAM,KAAK,KAAK;;CAIzB,MAAM,MAAkB;EACtB,MAAM,OAAO,KAAK,MAAM,OAAO;AAC/B,MAAI,SAAS,OACX,QAAO;AAET,SAAO,IAAI,SAAY,YAAY;AACjC,QAAK,QAAQ,KAAK,QAAQ;IAC1B;;CAGJ,IAAI,OAAe;AACjB,SAAO,KAAK,MAAM;;;;;;;;;AAYtB,IAAa,aAAb,MAAwB;CACtB,AAAS,UAAU,IAAI,YAA4B;CACnD,AAAS,WAAW,IAAI,YAA6B;CACrD,AAAQ,sCAAsB,IAAI,KAAiC;CACnE,AAAQ,WAAW;CAEnB,MAAM,eAAe,KAAoC;AACvD,QAAM,KAAK,QAAQ,IAAI,IAAI;;CAG7B,MAAM,iBAA0C;AAC9C,SAAO,KAAK,QAAQ,KAAK;;CAG3B,MAAM,gBAAgB,KAAqC;AACzD,QAAM,KAAK,SAAS,IAAI,IAAI;;CAG9B,MAAM,kBAA4C;AAChD,SAAO,KAAK,SAAS,KAAK;;CAG5B,kBAAkB,SAAiB,UAAkC;EACnE,MAAM,OAAO,KAAK,oBAAoB,IAAI,QAAQ,IAAI,EAAE;AACxD,OAAK,KAAK,SAAS;AACnB,OAAK,oBAAoB,IAAI,SAAS,KAAK;;CAG7C,MAAM,mBAAkC;AACtC,OAAK,WAAW;AAChB,SAAO,KAAK,SACV,KAAI;GACF,MAAM,MAAM,MAAM,YAAY,KAAK,SAAS,KAAK,EAAE,IAAK;GACxD,MAAM,cAAc,KAAK,oBAAoB,IAAI,IAAI,QAAQ,IAAI,EAAE;AACnE,QAAK,MAAM,YAAY,YACrB,KAAI;AACF,UAAM,SAAS,IAAI;YACZ,KAAK;AACZ,YAAQ,MAAM,wBAAwB,IAAI,QAAQ,IAAI,IAAI;;UAGxD;;CAMZ,OAAa;AACX,OAAK,WAAW;;CAGlB,IAAI,cAAsB;AACxB,SAAO,KAAK,QAAQ;;CAGtB,IAAI,eAAuB;AACzB,SAAO,KAAK,SAAS;;;;AAKzB,SAAS,YAAe,SAAqB,IAAwB;AACnE,QAAO,IAAI,SAAY,SAAS,WAAW;EACzC,MAAM,QAAQ,iBAAiB,uBAAO,IAAI,MAAM,UAAU,CAAC,EAAE,GAAG;AAChE,UAAQ,MACL,QAAQ;AACP,gBAAa,MAAM;AACnB,WAAQ,IAAI;MAEb,QAAQ;AACP,gBAAa,MAAM;AACnB,UAAO,IAAI;IAEd;GACD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { OutboundMessage } from "../bus/events.mjs";
|
|
2
|
+
import { MessageBus } from "../bus/queue.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/channels/base.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Abstract base class for chat channel implementations.
|
|
7
|
+
*/
|
|
8
|
+
declare abstract class BaseChannel {
|
|
9
|
+
abstract readonly name: string;
|
|
10
|
+
protected config: unknown;
|
|
11
|
+
protected bus: MessageBus;
|
|
12
|
+
protected _running: boolean;
|
|
13
|
+
constructor(config: unknown, bus: MessageBus);
|
|
14
|
+
abstract start(): Promise<void>;
|
|
15
|
+
abstract stop(): Promise<void>;
|
|
16
|
+
abstract send(msg: OutboundMessage): Promise<void>;
|
|
17
|
+
/** Check if a sender is allowed to use this bot. */
|
|
18
|
+
isAllowed(senderId: string): boolean;
|
|
19
|
+
/** Handle an incoming message from the chat platform. */
|
|
20
|
+
protected handleMessage(params: {
|
|
21
|
+
senderId: string;
|
|
22
|
+
chatId: string;
|
|
23
|
+
content: string;
|
|
24
|
+
media?: string[];
|
|
25
|
+
metadata?: Record<string, unknown>;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
get isRunning(): boolean;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { BaseChannel };
|
|
31
|
+
//# sourceMappingURL=base.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.mts","names":[],"sources":["../../src/channels/base.ts"],"mappings":";;;;;;AAOA;uBAAsB,WAAA;EAAA,kBACF,IAAA;EAAA,UACR,MAAA;EAAA,UACA,GAAA,EAAK,UAAA;EAAA,UACL,QAAA;cAEE,MAAA,WAAiB,GAAA,EAAK,UAAA;EAAA,SAKzB,KAAA,CAAA,GAAS,OAAA;EAAA,SACT,IAAA,CAAA,GAAQ,OAAA;EAAA,SACR,IAAA,CAAK,GAAA,EAAK,eAAA,GAAkB,OAAA;EA0BjC;EAvBJ,SAAA,CAAU,QAAA;EAuBC;EAAA,UANK,aAAA,CAAc,MAAA;IAC5B,QAAA;IACA,MAAA;IACA,OAAA;IACA,KAAA;IACA,QAAA,GAAW,MAAA;EAAA,IACT,OAAA;EAAA,IAeA,SAAA,CAAA;AAAA"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { __esmMin } from "../_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { createInboundMessage, init_events } from "../bus/events.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/channels/base.ts
|
|
5
|
+
var BaseChannel;
|
|
6
|
+
var init_base = __esmMin((() => {
|
|
7
|
+
init_events();
|
|
8
|
+
BaseChannel = class {
|
|
9
|
+
config;
|
|
10
|
+
bus;
|
|
11
|
+
_running = false;
|
|
12
|
+
constructor(config, bus) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.bus = bus;
|
|
15
|
+
}
|
|
16
|
+
/** Check if a sender is allowed to use this bot. */
|
|
17
|
+
isAllowed(senderId) {
|
|
18
|
+
const allowList = this.config?.allowFrom ?? [];
|
|
19
|
+
if (allowList.length === 0) return true;
|
|
20
|
+
const senderStr = String(senderId);
|
|
21
|
+
if (allowList.includes(senderStr)) return true;
|
|
22
|
+
if (senderStr.includes("|")) {
|
|
23
|
+
for (const part of senderStr.split("|")) if (part && allowList.includes(part)) return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
/** Handle an incoming message from the chat platform. */
|
|
28
|
+
async handleMessage(params) {
|
|
29
|
+
if (!this.isAllowed(params.senderId)) return;
|
|
30
|
+
const msg = createInboundMessage({
|
|
31
|
+
channel: this.name,
|
|
32
|
+
senderId: String(params.senderId),
|
|
33
|
+
chatId: String(params.chatId),
|
|
34
|
+
content: params.content,
|
|
35
|
+
media: params.media ?? [],
|
|
36
|
+
metadata: params.metadata ?? {}
|
|
37
|
+
});
|
|
38
|
+
await this.bus.publishInbound(msg);
|
|
39
|
+
}
|
|
40
|
+
get isRunning() {
|
|
41
|
+
return this._running;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
init_base();
|
|
48
|
+
export { BaseChannel, init_base };
|
|
49
|
+
//# sourceMappingURL=base.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.mjs","names":[],"sources":["../../src/channels/base.ts"],"sourcesContent":["import type { OutboundMessage, InboundMessage } from \"../bus/events.js\";\nimport { createInboundMessage } from \"../bus/events.js\";\nimport type { MessageBus } from \"../bus/queue.js\";\n\n/**\n * Abstract base class for chat channel implementations.\n */\nexport abstract class BaseChannel {\n abstract readonly name: string;\n protected config: unknown;\n protected bus: MessageBus;\n protected _running = false;\n\n constructor(config: unknown, bus: MessageBus) {\n this.config = config;\n this.bus = bus;\n }\n\n abstract start(): Promise<void>;\n abstract stop(): Promise<void>;\n abstract send(msg: OutboundMessage): Promise<void>;\n\n /** Check if a sender is allowed to use this bot. */\n isAllowed(senderId: string): boolean {\n const allowList = (this.config as { allowFrom?: string[] })?.allowFrom ?? [];\n if (allowList.length === 0) return true;\n\n const senderStr = String(senderId);\n if (allowList.includes(senderStr)) return true;\n\n // Check pipe-separated IDs (e.g., \"123456|username\")\n if (senderStr.includes(\"|\")) {\n for (const part of senderStr.split(\"|\")) {\n if (part && allowList.includes(part)) return true;\n }\n }\n return false;\n }\n\n /** Handle an incoming message from the chat platform. */\n protected async handleMessage(params: {\n senderId: string;\n chatId: string;\n content: string;\n media?: string[];\n metadata?: Record<string, unknown>;\n }): Promise<void> {\n if (!this.isAllowed(params.senderId)) return;\n\n const msg = createInboundMessage({\n channel: this.name,\n senderId: String(params.senderId),\n chatId: String(params.chatId),\n content: params.content,\n media: params.media ?? [],\n metadata: params.metadata ?? {},\n });\n\n await this.bus.publishInbound(msg);\n }\n\n get isRunning(): boolean {\n return this._running;\n }\n}\n"],"mappings":";;;;;;cACwD;CAMlC,cAAtB,MAAkC;EAEhC,AAAU;EACV,AAAU;EACV,AAAU,WAAW;EAErB,YAAY,QAAiB,KAAiB;AAC5C,QAAK,SAAS;AACd,QAAK,MAAM;;;EAQb,UAAU,UAA2B;GACnC,MAAM,YAAa,KAAK,QAAqC,aAAa,EAAE;AAC5E,OAAI,UAAU,WAAW,EAAG,QAAO;GAEnC,MAAM,YAAY,OAAO,SAAS;AAClC,OAAI,UAAU,SAAS,UAAU,CAAE,QAAO;AAG1C,OAAI,UAAU,SAAS,IAAI,EACzB;SAAK,MAAM,QAAQ,UAAU,MAAM,IAAI,CACrC,KAAI,QAAQ,UAAU,SAAS,KAAK,CAAE,QAAO;;AAGjD,UAAO;;;EAIT,MAAgB,cAAc,QAMZ;AAChB,OAAI,CAAC,KAAK,UAAU,OAAO,SAAS,CAAE;GAEtC,MAAM,MAAM,qBAAqB;IAC/B,SAAS,KAAK;IACd,UAAU,OAAO,OAAO,SAAS;IACjC,QAAQ,OAAO,OAAO,OAAO;IAC7B,SAAS,OAAO;IAChB,OAAO,OAAO,SAAS,EAAE;IACzB,UAAU,OAAO,YAAY,EAAE;IAChC,CAAC;AAEF,SAAM,KAAK,IAAI,eAAe,IAAI;;EAGpC,IAAI,YAAqB;AACvB,UAAO,KAAK"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MessageBus } from "../bus/queue.mjs";
|
|
2
|
+
import { Config } from "../config/schema.mjs";
|
|
3
|
+
import { BaseChannel } from "./base.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/channels/manager.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Manages chat channels and coordinates message routing.
|
|
8
|
+
*/
|
|
9
|
+
declare class ChannelManager {
|
|
10
|
+
private config;
|
|
11
|
+
private bus;
|
|
12
|
+
readonly channels: Map<string, BaseChannel>;
|
|
13
|
+
private dispatchAbort;
|
|
14
|
+
constructor(config: Config, bus: MessageBus);
|
|
15
|
+
private initChannels;
|
|
16
|
+
/** Start all channels and the outbound dispatcher. */
|
|
17
|
+
startAll(): Promise<void>;
|
|
18
|
+
/** Stop all channels and the dispatcher. */
|
|
19
|
+
stopAll(): Promise<void>;
|
|
20
|
+
private dispatchOutbound;
|
|
21
|
+
getChannel(name: string): BaseChannel | undefined;
|
|
22
|
+
getStatus(): Record<string, {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
running: boolean;
|
|
25
|
+
}>;
|
|
26
|
+
get enabledChannels(): string[];
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { ChannelManager };
|
|
30
|
+
//# sourceMappingURL=manager.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.d.mts","names":[],"sources":["../../src/channels/manager.ts"],"mappings":";;;;;;;AAQA;cAAa,cAAA;EAAA,QACH,MAAA;EAAA,QACA,GAAA;EAAA,SACC,QAAA,EAAQ,GAAA,SAAA,WAAA;EAAA,QACT,aAAA;cAEI,MAAA,EAAQ,MAAA,EAAQ,GAAA,EAAK,UAAA;EAAA,QAMzB,YAAA;EA2CS;EArBX,QAAA,CAAA,GAAY,OAAA;EAiEL;EA5CP,OAAA,CAAA,GAAW,OAAA;EAAA,QAkBH,gBAAA;EAsBd,UAAA,CAAW,IAAA,WAAe,WAAA;EAI1B,SAAA,CAAA,GAAa,MAAA;IAAiB,OAAA;IAAkB,OAAA;EAAA;EAAA,IAQ5C,eAAA,CAAA;AAAA"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { __toCommonJS } from "../_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { init_telegram, telegram_exports } from "./telegram.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/channels/manager.ts
|
|
5
|
+
/**
|
|
6
|
+
* Manages chat channels and coordinates message routing.
|
|
7
|
+
*/
|
|
8
|
+
var ChannelManager = class {
|
|
9
|
+
config;
|
|
10
|
+
bus;
|
|
11
|
+
channels = /* @__PURE__ */ new Map();
|
|
12
|
+
dispatchAbort = null;
|
|
13
|
+
constructor(config, bus) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.bus = bus;
|
|
16
|
+
this.initChannels();
|
|
17
|
+
}
|
|
18
|
+
initChannels() {
|
|
19
|
+
if (this.config.channels.telegram.enabled) try {
|
|
20
|
+
const { TelegramChannel } = (init_telegram(), __toCommonJS(telegram_exports));
|
|
21
|
+
const channel = new TelegramChannel(this.config.channels.telegram, this.bus, this.config.providers.groq.apiKey);
|
|
22
|
+
this.channels.set("telegram", channel);
|
|
23
|
+
console.log("Telegram channel enabled");
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.warn("Telegram channel not available:", err);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Start all channels and the outbound dispatcher. */
|
|
29
|
+
async startAll() {
|
|
30
|
+
if (this.channels.size === 0) {
|
|
31
|
+
console.warn("No channels enabled");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.dispatchAbort = new AbortController();
|
|
35
|
+
const dispatchPromise = this.dispatchOutbound(this.dispatchAbort.signal);
|
|
36
|
+
const channelPromises = [];
|
|
37
|
+
for (const [name, channel] of this.channels) {
|
|
38
|
+
console.log(`Starting ${name} channel...`);
|
|
39
|
+
channelPromises.push(channel.start());
|
|
40
|
+
}
|
|
41
|
+
await Promise.all([dispatchPromise, ...channelPromises]);
|
|
42
|
+
}
|
|
43
|
+
/** Stop all channels and the dispatcher. */
|
|
44
|
+
async stopAll() {
|
|
45
|
+
console.log("Stopping all channels...");
|
|
46
|
+
if (this.dispatchAbort) {
|
|
47
|
+
this.dispatchAbort.abort();
|
|
48
|
+
this.dispatchAbort = null;
|
|
49
|
+
}
|
|
50
|
+
for (const [name, channel] of this.channels) try {
|
|
51
|
+
await channel.stop();
|
|
52
|
+
console.log(`Stopped ${name} channel`);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(`Error stopping ${name}:`, err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async dispatchOutbound(signal) {
|
|
58
|
+
console.log("Outbound dispatcher started");
|
|
59
|
+
while (!signal.aborted) try {
|
|
60
|
+
const msg = await withTimeout(this.bus.consumeOutbound(), 1e3);
|
|
61
|
+
const channel = this.channels.get(msg.channel);
|
|
62
|
+
if (channel) try {
|
|
63
|
+
await channel.send(msg);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error(`Error sending to ${msg.channel}:`, err);
|
|
66
|
+
}
|
|
67
|
+
else console.warn(`Unknown channel: ${msg.channel}`);
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|
|
70
|
+
getChannel(name) {
|
|
71
|
+
return this.channels.get(name);
|
|
72
|
+
}
|
|
73
|
+
getStatus() {
|
|
74
|
+
const status = {};
|
|
75
|
+
for (const [name, channel] of this.channels) status[name] = {
|
|
76
|
+
enabled: true,
|
|
77
|
+
running: channel.isRunning
|
|
78
|
+
};
|
|
79
|
+
return status;
|
|
80
|
+
}
|
|
81
|
+
get enabledChannels() {
|
|
82
|
+
return Array.from(this.channels.keys());
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
function withTimeout(promise, ms) {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("timeout")), ms);
|
|
88
|
+
promise.then((val) => {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
resolve(val);
|
|
91
|
+
}, (err) => {
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
reject(err);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
export { ChannelManager };
|
|
100
|
+
//# sourceMappingURL=manager.mjs.map
|