@radaros/core 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1407 -0
- package/dist/index.js +5269 -0
- package/package.json +6 -2
- package/src/a2a/a2a-remote-agent.ts +0 -270
- package/src/a2a/types.ts +0 -142
- package/src/agent/agent.ts +0 -417
- package/src/agent/llm-loop.ts +0 -290
- package/src/agent/run-context.ts +0 -35
- package/src/agent/types.ts +0 -89
- package/src/events/event-bus.ts +0 -45
- package/src/events/types.ts +0 -16
- package/src/guardrails/types.ts +0 -5
- package/src/hooks/types.ts +0 -6
- package/src/index.ts +0 -157
- package/src/knowledge/knowledge-base.ts +0 -146
- package/src/logger/logger.ts +0 -249
- package/src/mcp/mcp-client.ts +0 -264
- package/src/memory/memory.ts +0 -87
- package/src/memory/types.ts +0 -13
- package/src/memory/user-memory.ts +0 -211
- package/src/models/provider.ts +0 -22
- package/src/models/providers/anthropic.ts +0 -360
- package/src/models/providers/google.ts +0 -386
- package/src/models/providers/ollama.ts +0 -211
- package/src/models/providers/openai.ts +0 -345
- package/src/models/providers/vertex.ts +0 -427
- package/src/models/registry.ts +0 -107
- package/src/models/types.ts +0 -124
- package/src/session/session-manager.ts +0 -75
- package/src/session/types.ts +0 -10
- package/src/storage/driver.ts +0 -10
- package/src/storage/in-memory.ts +0 -44
- package/src/storage/mongodb.ts +0 -70
- package/src/storage/postgres.ts +0 -81
- package/src/storage/sqlite.ts +0 -81
- package/src/team/modes.ts +0 -1
- package/src/team/team.ts +0 -323
- package/src/team/types.ts +0 -26
- package/src/toolkits/base.ts +0 -15
- package/src/toolkits/duckduckgo.ts +0 -256
- package/src/toolkits/gmail.ts +0 -226
- package/src/toolkits/hackernews.ts +0 -121
- package/src/toolkits/websearch.ts +0 -158
- package/src/toolkits/whatsapp.ts +0 -209
- package/src/tools/define-tool.ts +0 -22
- package/src/tools/tool-executor.ts +0 -221
- package/src/tools/types.ts +0 -36
- package/src/utils/retry.ts +0 -56
- package/src/vector/base.ts +0 -44
- package/src/vector/embeddings/google.ts +0 -64
- package/src/vector/embeddings/openai.ts +0 -66
- package/src/vector/in-memory.ts +0 -115
- package/src/vector/mongodb.ts +0 -241
- package/src/vector/pgvector.ts +0 -169
- package/src/vector/qdrant.ts +0 -203
- package/src/vector/types.ts +0 -55
- package/src/workflow/step-runner.ts +0 -303
- package/src/workflow/types.ts +0 -55
- package/src/workflow/workflow.ts +0 -68
- package/tsconfig.json +0 -8
package/src/team/types.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { ModelProvider } from "../models/provider.js";
|
|
2
|
-
import type { StorageDriver } from "../storage/driver.js";
|
|
3
|
-
import type { EventBus } from "../events/event-bus.js";
|
|
4
|
-
import type { Agent } from "../agent/agent.js";
|
|
5
|
-
import type { RunOpts, RunOutput, StreamChunk } from "../agent/types.js";
|
|
6
|
-
|
|
7
|
-
export enum TeamMode {
|
|
8
|
-
Coordinate = "coordinate",
|
|
9
|
-
Route = "route",
|
|
10
|
-
Broadcast = "broadcast",
|
|
11
|
-
Collaborate = "collaborate",
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface TeamConfig {
|
|
15
|
-
name: string;
|
|
16
|
-
mode: TeamMode;
|
|
17
|
-
model: ModelProvider;
|
|
18
|
-
members: Agent[];
|
|
19
|
-
instructions?: string;
|
|
20
|
-
sessionState?: Record<string, unknown>;
|
|
21
|
-
storage?: StorageDriver;
|
|
22
|
-
maxRounds?: number;
|
|
23
|
-
eventBus?: EventBus;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type { RunOpts, RunOutput, StreamChunk };
|
package/src/toolkits/base.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ToolDef } from "../tools/types.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Base class for all RadarOS Toolkits.
|
|
5
|
-
* A Toolkit is a collection of related tools that share configuration.
|
|
6
|
-
*/
|
|
7
|
-
export abstract class Toolkit {
|
|
8
|
-
abstract readonly name: string;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Returns all tools provided by this toolkit as ToolDef[].
|
|
12
|
-
* Spread them into an Agent's `tools` array.
|
|
13
|
-
*/
|
|
14
|
-
abstract getTools(): ToolDef[];
|
|
15
|
-
}
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import type { ToolDef } from "../tools/types.js";
|
|
3
|
-
import type { RunContext } from "../agent/run-context.js";
|
|
4
|
-
import { Toolkit } from "./base.js";
|
|
5
|
-
|
|
6
|
-
export interface DuckDuckGoConfig {
|
|
7
|
-
/** Enable web search (default true). */
|
|
8
|
-
enableSearch?: boolean;
|
|
9
|
-
/** Enable news search (default true). */
|
|
10
|
-
enableNews?: boolean;
|
|
11
|
-
/** Fixed max results per query (default 5). */
|
|
12
|
-
maxResults?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* DuckDuckGo Toolkit — search the web and news without any API key.
|
|
17
|
-
*
|
|
18
|
-
* Uses the DuckDuckGo HTML API (no key required).
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const ddg = new DuckDuckGoToolkit();
|
|
23
|
-
* const agent = new Agent({ tools: [...ddg.getTools()] });
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
export class DuckDuckGoToolkit extends Toolkit {
|
|
27
|
-
readonly name = "duckduckgo";
|
|
28
|
-
private config: DuckDuckGoConfig;
|
|
29
|
-
|
|
30
|
-
constructor(config: DuckDuckGoConfig = {}) {
|
|
31
|
-
super();
|
|
32
|
-
this.config = {
|
|
33
|
-
enableSearch: config.enableSearch ?? true,
|
|
34
|
-
enableNews: config.enableNews ?? true,
|
|
35
|
-
maxResults: config.maxResults ?? 5,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
getTools(): ToolDef[] {
|
|
40
|
-
const tools: ToolDef[] = [];
|
|
41
|
-
|
|
42
|
-
if (this.config.enableSearch) {
|
|
43
|
-
tools.push(this.buildSearchTool());
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (this.config.enableNews) {
|
|
47
|
-
tools.push(this.buildNewsTool());
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return tools;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
private buildSearchTool(): ToolDef {
|
|
54
|
-
const self = this;
|
|
55
|
-
return {
|
|
56
|
-
name: "duckduckgo_search",
|
|
57
|
-
description:
|
|
58
|
-
"Search the web using DuckDuckGo. Returns titles, URLs, and snippets. No API key required.",
|
|
59
|
-
parameters: z.object({
|
|
60
|
-
query: z.string().describe("The search query"),
|
|
61
|
-
maxResults: z
|
|
62
|
-
.number()
|
|
63
|
-
.optional()
|
|
64
|
-
.describe("Maximum number of results (default 5)"),
|
|
65
|
-
}),
|
|
66
|
-
async execute(
|
|
67
|
-
args: Record<string, unknown>,
|
|
68
|
-
_ctx: RunContext
|
|
69
|
-
): Promise<string> {
|
|
70
|
-
const query = args.query as string;
|
|
71
|
-
const max = (args.maxResults as number) ?? self.config.maxResults ?? 5;
|
|
72
|
-
return self.search(query, max);
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private buildNewsTool(): ToolDef {
|
|
78
|
-
const self = this;
|
|
79
|
-
return {
|
|
80
|
-
name: "duckduckgo_news",
|
|
81
|
-
description:
|
|
82
|
-
"Get the latest news from DuckDuckGo. Returns headlines, sources, URLs, and dates.",
|
|
83
|
-
parameters: z.object({
|
|
84
|
-
query: z.string().describe("The news search query"),
|
|
85
|
-
maxResults: z
|
|
86
|
-
.number()
|
|
87
|
-
.optional()
|
|
88
|
-
.describe("Maximum number of results (default 5)"),
|
|
89
|
-
}),
|
|
90
|
-
async execute(
|
|
91
|
-
args: Record<string, unknown>,
|
|
92
|
-
_ctx: RunContext
|
|
93
|
-
): Promise<string> {
|
|
94
|
-
const query = args.query as string;
|
|
95
|
-
const max = (args.maxResults as number) ?? self.config.maxResults ?? 5;
|
|
96
|
-
return self.searchNews(query, max);
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private async search(query: string, maxResults: number): Promise<string> {
|
|
102
|
-
const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_redirect=1&no_html=1&skip_disambig=1`;
|
|
103
|
-
|
|
104
|
-
const res = await fetch(url, {
|
|
105
|
-
headers: { "User-Agent": "RadarOS/1.0" },
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
if (!res.ok) {
|
|
109
|
-
throw new Error(`DuckDuckGo search failed: ${res.status}`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const data = (await res.json()) as any;
|
|
113
|
-
const results: string[] = [];
|
|
114
|
-
|
|
115
|
-
if (data.Abstract) {
|
|
116
|
-
results.push(
|
|
117
|
-
`Answer: ${data.Abstract}\nSource: ${data.AbstractSource}\nURL: ${data.AbstractURL}`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const topics = data.RelatedTopics ?? [];
|
|
122
|
-
for (const topic of topics.slice(0, maxResults)) {
|
|
123
|
-
if (topic.Text && topic.FirstURL) {
|
|
124
|
-
results.push(`${topic.Text}\nURL: ${topic.FirstURL}`);
|
|
125
|
-
}
|
|
126
|
-
if (topic.Topics) {
|
|
127
|
-
for (const sub of topic.Topics.slice(0, 2)) {
|
|
128
|
-
if (sub.Text && sub.FirstURL) {
|
|
129
|
-
results.push(`${sub.Text}\nURL: ${sub.FirstURL}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (results.length === 0 && data.Redirect) {
|
|
136
|
-
return `Redirect: ${data.Redirect}`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (results.length === 0) {
|
|
140
|
-
const htmlResults = await this.scrapeHtmlSearch(query, maxResults);
|
|
141
|
-
if (htmlResults) return htmlResults;
|
|
142
|
-
return "No results found.";
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return results.join("\n\n---\n\n");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private async scrapeHtmlSearch(
|
|
149
|
-
query: string,
|
|
150
|
-
maxResults: number
|
|
151
|
-
): Promise<string | null> {
|
|
152
|
-
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
153
|
-
|
|
154
|
-
const res = await fetch(url, {
|
|
155
|
-
headers: {
|
|
156
|
-
"User-Agent":
|
|
157
|
-
"Mozilla/5.0 (compatible; RadarOS/1.0; +https://radaros.dev)",
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
if (!res.ok) return null;
|
|
162
|
-
|
|
163
|
-
const html = await res.text();
|
|
164
|
-
|
|
165
|
-
const results: string[] = [];
|
|
166
|
-
const linkRegex =
|
|
167
|
-
/<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
168
|
-
const snippetRegex =
|
|
169
|
-
/<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
170
|
-
|
|
171
|
-
const links: { url: string; title: string }[] = [];
|
|
172
|
-
let match;
|
|
173
|
-
while ((match = linkRegex.exec(html)) !== null) {
|
|
174
|
-
const rawUrl = match[1];
|
|
175
|
-
const title = match[2].replace(/<[^>]*>/g, "").trim();
|
|
176
|
-
const decoded = this.decodeDdgUrl(rawUrl);
|
|
177
|
-
if (title && decoded) {
|
|
178
|
-
links.push({ url: decoded, title });
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const snippets: string[] = [];
|
|
183
|
-
while ((match = snippetRegex.exec(html)) !== null) {
|
|
184
|
-
snippets.push(match[1].replace(/<[^>]*>/g, "").trim());
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
for (let i = 0; i < Math.min(links.length, maxResults); i++) {
|
|
188
|
-
results.push(
|
|
189
|
-
`Title: ${links[i].title}\nURL: ${links[i].url}\nSnippet: ${snippets[i] ?? ""}`
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return results.length > 0 ? results.join("\n\n---\n\n") : null;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
private decodeDdgUrl(url: string): string | null {
|
|
197
|
-
if (url.startsWith("//duckduckgo.com/l/?uddg=")) {
|
|
198
|
-
const match = url.match(/uddg=([^&]*)/);
|
|
199
|
-
if (match) return decodeURIComponent(match[1]);
|
|
200
|
-
}
|
|
201
|
-
if (url.startsWith("http")) return url;
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private async searchNews(
|
|
206
|
-
query: string,
|
|
207
|
-
maxResults: number
|
|
208
|
-
): Promise<string> {
|
|
209
|
-
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query + " news")}&iar=news`;
|
|
210
|
-
|
|
211
|
-
const res = await fetch(url, {
|
|
212
|
-
headers: {
|
|
213
|
-
"User-Agent":
|
|
214
|
-
"Mozilla/5.0 (compatible; RadarOS/1.0; +https://radaros.dev)",
|
|
215
|
-
},
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
if (!res.ok) {
|
|
219
|
-
throw new Error(`DuckDuckGo news search failed: ${res.status}`);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const html = await res.text();
|
|
223
|
-
const results: string[] = [];
|
|
224
|
-
|
|
225
|
-
const linkRegex =
|
|
226
|
-
/<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
227
|
-
const snippetRegex =
|
|
228
|
-
/<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
229
|
-
|
|
230
|
-
const links: { url: string; title: string }[] = [];
|
|
231
|
-
let match;
|
|
232
|
-
while ((match = linkRegex.exec(html)) !== null) {
|
|
233
|
-
const rawUrl = match[1];
|
|
234
|
-
const title = match[2].replace(/<[^>]*>/g, "").trim();
|
|
235
|
-
const decoded = this.decodeDdgUrl(rawUrl);
|
|
236
|
-
if (title && decoded) {
|
|
237
|
-
links.push({ url: decoded, title });
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const snippets: string[] = [];
|
|
242
|
-
while ((match = snippetRegex.exec(html)) !== null) {
|
|
243
|
-
snippets.push(match[1].replace(/<[^>]*>/g, "").trim());
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
for (let i = 0; i < Math.min(links.length, maxResults); i++) {
|
|
247
|
-
results.push(
|
|
248
|
-
`Title: ${links[i].title}\nURL: ${links[i].url}\nSnippet: ${snippets[i] ?? ""}`
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return results.length > 0
|
|
253
|
-
? results.join("\n\n---\n\n")
|
|
254
|
-
: "No news results found.";
|
|
255
|
-
}
|
|
256
|
-
}
|
package/src/toolkits/gmail.ts
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import type { ToolDef } from "../tools/types.js";
|
|
4
|
-
import type { RunContext } from "../agent/run-context.js";
|
|
5
|
-
import { Toolkit } from "./base.js";
|
|
6
|
-
|
|
7
|
-
const _require = createRequire(import.meta.url);
|
|
8
|
-
|
|
9
|
-
export interface GmailConfig {
|
|
10
|
-
/** Path to OAuth2 credentials JSON file. Falls back to GMAIL_CREDENTIALS_PATH env var. */
|
|
11
|
-
credentialsPath?: string;
|
|
12
|
-
/** Path to saved token JSON file. Falls back to GMAIL_TOKEN_PATH env var. */
|
|
13
|
-
tokenPath?: string;
|
|
14
|
-
/** Pre-authenticated OAuth2 client (if you handle auth yourself). */
|
|
15
|
-
authClient?: any;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Gmail Toolkit — send, search, and read emails from your agent.
|
|
20
|
-
*
|
|
21
|
-
* Requires: npm install googleapis
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* const gmail = new GmailToolkit({ credentialsPath: "./credentials.json", tokenPath: "./token.json" });
|
|
26
|
-
* const agent = new Agent({ tools: [...gmail.getTools()] });
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export class GmailToolkit extends Toolkit {
|
|
30
|
-
readonly name = "gmail";
|
|
31
|
-
private config: GmailConfig;
|
|
32
|
-
private gmail: any = null;
|
|
33
|
-
|
|
34
|
-
constructor(config: GmailConfig = {}) {
|
|
35
|
-
super();
|
|
36
|
-
this.config = config;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private async getGmailClient(): Promise<any> {
|
|
40
|
-
if (this.gmail) return this.gmail;
|
|
41
|
-
|
|
42
|
-
if (this.config.authClient) {
|
|
43
|
-
const { google } = _require("googleapis");
|
|
44
|
-
this.gmail = google.gmail({ version: "v1", auth: this.config.authClient });
|
|
45
|
-
return this.gmail;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const credPath =
|
|
49
|
-
this.config.credentialsPath ?? process.env.GMAIL_CREDENTIALS_PATH;
|
|
50
|
-
const tokenPath =
|
|
51
|
-
this.config.tokenPath ?? process.env.GMAIL_TOKEN_PATH;
|
|
52
|
-
|
|
53
|
-
if (!credPath || !tokenPath) {
|
|
54
|
-
throw new Error(
|
|
55
|
-
"GmailToolkit: Provide credentialsPath + tokenPath, or an authClient. " +
|
|
56
|
-
"Set GMAIL_CREDENTIALS_PATH and GMAIL_TOKEN_PATH env vars, or pass them in config."
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const { google } = _require("googleapis");
|
|
61
|
-
const fs = await import("node:fs");
|
|
62
|
-
const creds = JSON.parse(fs.readFileSync(credPath, "utf-8"));
|
|
63
|
-
const token = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
|
|
64
|
-
|
|
65
|
-
const { client_id, client_secret, redirect_uris } =
|
|
66
|
-
creds.installed || creds.web;
|
|
67
|
-
const oAuth2 = new google.auth.OAuth2(
|
|
68
|
-
client_id,
|
|
69
|
-
client_secret,
|
|
70
|
-
redirect_uris?.[0]
|
|
71
|
-
);
|
|
72
|
-
oAuth2.setCredentials(token);
|
|
73
|
-
|
|
74
|
-
this.gmail = google.gmail({ version: "v1", auth: oAuth2 });
|
|
75
|
-
return this.gmail;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
getTools(): ToolDef[] {
|
|
79
|
-
const self = this;
|
|
80
|
-
|
|
81
|
-
return [
|
|
82
|
-
{
|
|
83
|
-
name: "gmail_send",
|
|
84
|
-
description:
|
|
85
|
-
"Send an email via Gmail. Provide recipient, subject, and body.",
|
|
86
|
-
parameters: z.object({
|
|
87
|
-
to: z.string().describe("Recipient email address"),
|
|
88
|
-
subject: z.string().describe("Email subject line"),
|
|
89
|
-
body: z.string().describe("Email body (plain text)"),
|
|
90
|
-
}),
|
|
91
|
-
execute: async (
|
|
92
|
-
args: Record<string, unknown>,
|
|
93
|
-
_ctx: RunContext
|
|
94
|
-
): Promise<string> => {
|
|
95
|
-
const gmail = await self.getGmailClient();
|
|
96
|
-
const rawMessage = [
|
|
97
|
-
`To: ${args.to}`,
|
|
98
|
-
`Subject: ${args.subject}`,
|
|
99
|
-
"Content-Type: text/plain; charset=utf-8",
|
|
100
|
-
"",
|
|
101
|
-
args.body,
|
|
102
|
-
].join("\n");
|
|
103
|
-
|
|
104
|
-
const encoded = Buffer.from(rawMessage)
|
|
105
|
-
.toString("base64")
|
|
106
|
-
.replace(/\+/g, "-")
|
|
107
|
-
.replace(/\//g, "_")
|
|
108
|
-
.replace(/=+$/, "");
|
|
109
|
-
|
|
110
|
-
const res = await gmail.users.messages.send({
|
|
111
|
-
userId: "me",
|
|
112
|
-
requestBody: { raw: encoded },
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return `Email sent successfully. Message ID: ${res.data.id}`;
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
name: "gmail_search",
|
|
120
|
-
description:
|
|
121
|
-
"Search emails in Gmail. Returns subject, from, date, and snippet for matching messages.",
|
|
122
|
-
parameters: z.object({
|
|
123
|
-
query: z
|
|
124
|
-
.string()
|
|
125
|
-
.describe(
|
|
126
|
-
'Gmail search query (e.g. "from:john subject:meeting is:unread")'
|
|
127
|
-
),
|
|
128
|
-
maxResults: z
|
|
129
|
-
.number()
|
|
130
|
-
.optional()
|
|
131
|
-
.describe("Maximum number of results (default 10)"),
|
|
132
|
-
}),
|
|
133
|
-
execute: async (
|
|
134
|
-
args: Record<string, unknown>,
|
|
135
|
-
_ctx: RunContext
|
|
136
|
-
): Promise<string> => {
|
|
137
|
-
const gmail = await self.getGmailClient();
|
|
138
|
-
const max = (args.maxResults as number) ?? 10;
|
|
139
|
-
|
|
140
|
-
const list = await gmail.users.messages.list({
|
|
141
|
-
userId: "me",
|
|
142
|
-
q: args.query as string,
|
|
143
|
-
maxResults: max,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const messages = list.data.messages ?? [];
|
|
147
|
-
if (messages.length === 0) return "No emails found.";
|
|
148
|
-
|
|
149
|
-
const results: string[] = [];
|
|
150
|
-
for (const msg of messages) {
|
|
151
|
-
const detail = await gmail.users.messages.get({
|
|
152
|
-
userId: "me",
|
|
153
|
-
id: msg.id,
|
|
154
|
-
format: "metadata",
|
|
155
|
-
metadataHeaders: ["From", "Subject", "Date"],
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const headers = detail.data.payload?.headers ?? [];
|
|
159
|
-
const getHeader = (name: string) =>
|
|
160
|
-
headers.find(
|
|
161
|
-
(h: any) => h.name.toLowerCase() === name.toLowerCase()
|
|
162
|
-
)?.value ?? "";
|
|
163
|
-
|
|
164
|
-
results.push(
|
|
165
|
-
`ID: ${msg.id}\nFrom: ${getHeader("From")}\nSubject: ${getHeader("Subject")}\nDate: ${getHeader("Date")}\nSnippet: ${detail.data.snippet ?? ""}`
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return results.join("\n\n---\n\n");
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
name: "gmail_read",
|
|
174
|
-
description:
|
|
175
|
-
"Read the full content of an email by its message ID.",
|
|
176
|
-
parameters: z.object({
|
|
177
|
-
messageId: z.string().describe("The Gmail message ID to read"),
|
|
178
|
-
}),
|
|
179
|
-
execute: async (
|
|
180
|
-
args: Record<string, unknown>,
|
|
181
|
-
_ctx: RunContext
|
|
182
|
-
): Promise<string> => {
|
|
183
|
-
const gmail = await self.getGmailClient();
|
|
184
|
-
|
|
185
|
-
const detail = await gmail.users.messages.get({
|
|
186
|
-
userId: "me",
|
|
187
|
-
id: args.messageId as string,
|
|
188
|
-
format: "full",
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const headers = detail.data.payload?.headers ?? [];
|
|
192
|
-
const getHeader = (name: string) =>
|
|
193
|
-
headers.find(
|
|
194
|
-
(h: any) => h.name.toLowerCase() === name.toLowerCase()
|
|
195
|
-
)?.value ?? "";
|
|
196
|
-
|
|
197
|
-
let body = "";
|
|
198
|
-
const payload = detail.data.payload;
|
|
199
|
-
if (payload?.body?.data) {
|
|
200
|
-
body = Buffer.from(payload.body.data, "base64").toString("utf-8");
|
|
201
|
-
} else if (payload?.parts) {
|
|
202
|
-
const textPart = payload.parts.find(
|
|
203
|
-
(p: any) => p.mimeType === "text/plain"
|
|
204
|
-
);
|
|
205
|
-
if (textPart?.body?.data) {
|
|
206
|
-
body = Buffer.from(textPart.body.data, "base64").toString(
|
|
207
|
-
"utf-8"
|
|
208
|
-
);
|
|
209
|
-
} else {
|
|
210
|
-
const htmlPart = payload.parts.find(
|
|
211
|
-
(p: any) => p.mimeType === "text/html"
|
|
212
|
-
);
|
|
213
|
-
if (htmlPart?.body?.data) {
|
|
214
|
-
body = Buffer.from(htmlPart.body.data, "base64").toString(
|
|
215
|
-
"utf-8"
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return `From: ${getHeader("From")}\nTo: ${getHeader("To")}\nSubject: ${getHeader("Subject")}\nDate: ${getHeader("Date")}\n\n${body || "(no body)"}`;
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
];
|
|
225
|
-
}
|
|
226
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import type { ToolDef } from "../tools/types.js";
|
|
3
|
-
import type { RunContext } from "../agent/run-context.js";
|
|
4
|
-
import { Toolkit } from "./base.js";
|
|
5
|
-
|
|
6
|
-
export interface HackerNewsConfig {
|
|
7
|
-
/** Enable fetching top stories (default true). */
|
|
8
|
-
enableGetTopStories?: boolean;
|
|
9
|
-
/** Enable fetching user details (default true). */
|
|
10
|
-
enableGetUserDetails?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Hacker News Toolkit — search top stories and user details from HN.
|
|
15
|
-
*
|
|
16
|
-
* No API key required — uses the public Hacker News API.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```ts
|
|
20
|
-
* const hn = new HackerNewsToolkit();
|
|
21
|
-
* const agent = new Agent({ tools: [...hn.getTools()] });
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
export class HackerNewsToolkit extends Toolkit {
|
|
25
|
-
readonly name = "hackernews";
|
|
26
|
-
private config: HackerNewsConfig;
|
|
27
|
-
|
|
28
|
-
constructor(config: HackerNewsConfig = {}) {
|
|
29
|
-
super();
|
|
30
|
-
this.config = {
|
|
31
|
-
enableGetTopStories: config.enableGetTopStories ?? true,
|
|
32
|
-
enableGetUserDetails: config.enableGetUserDetails ?? true,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getTools(): ToolDef[] {
|
|
37
|
-
const tools: ToolDef[] = [];
|
|
38
|
-
|
|
39
|
-
if (this.config.enableGetTopStories) {
|
|
40
|
-
tools.push({
|
|
41
|
-
name: "hackernews_top_stories",
|
|
42
|
-
description:
|
|
43
|
-
"Get the top stories from Hacker News. Returns title, URL, score, author, and comment count.",
|
|
44
|
-
parameters: z.object({
|
|
45
|
-
numStories: z
|
|
46
|
-
.number()
|
|
47
|
-
.optional()
|
|
48
|
-
.describe("Number of top stories to fetch (default 10, max 30)"),
|
|
49
|
-
}),
|
|
50
|
-
execute: async (
|
|
51
|
-
args: Record<string, unknown>,
|
|
52
|
-
_ctx: RunContext
|
|
53
|
-
): Promise<string> => {
|
|
54
|
-
const num = Math.min((args.numStories as number) ?? 10, 30);
|
|
55
|
-
|
|
56
|
-
const idsRes = await fetch(
|
|
57
|
-
"https://hacker-news.firebaseio.com/v0/topstories.json"
|
|
58
|
-
);
|
|
59
|
-
if (!idsRes.ok) throw new Error(`HN API failed: ${idsRes.status}`);
|
|
60
|
-
const ids = (await idsRes.json()) as number[];
|
|
61
|
-
|
|
62
|
-
const stories = await Promise.all(
|
|
63
|
-
ids.slice(0, num).map(async (id) => {
|
|
64
|
-
const res = await fetch(
|
|
65
|
-
`https://hacker-news.firebaseio.com/v0/item/${id}.json`
|
|
66
|
-
);
|
|
67
|
-
return res.json() as Promise<any>;
|
|
68
|
-
})
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
return stories
|
|
72
|
-
.map(
|
|
73
|
-
(s, i) =>
|
|
74
|
-
`${i + 1}. ${s.title}\n URL: ${s.url ?? `https://news.ycombinator.com/item?id=${s.id}`}\n Score: ${s.score} | By: ${s.by} | Comments: ${s.descendants ?? 0}`
|
|
75
|
-
)
|
|
76
|
-
.join("\n\n");
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (this.config.enableGetUserDetails) {
|
|
82
|
-
tools.push({
|
|
83
|
-
name: "hackernews_user",
|
|
84
|
-
description:
|
|
85
|
-
"Get details about a Hacker News user by username. Returns karma, about, and account creation date.",
|
|
86
|
-
parameters: z.object({
|
|
87
|
-
username: z.string().describe("The HN username to look up"),
|
|
88
|
-
}),
|
|
89
|
-
execute: async (
|
|
90
|
-
args: Record<string, unknown>,
|
|
91
|
-
_ctx: RunContext
|
|
92
|
-
): Promise<string> => {
|
|
93
|
-
const username = args.username as string;
|
|
94
|
-
const res = await fetch(
|
|
95
|
-
`https://hacker-news.firebaseio.com/v0/user/${username}.json`
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
if (!res.ok)
|
|
99
|
-
throw new Error(`HN user API failed: ${res.status}`);
|
|
100
|
-
|
|
101
|
-
const user = (await res.json()) as any;
|
|
102
|
-
if (!user) return `User "${username}" not found.`;
|
|
103
|
-
|
|
104
|
-
const created = new Date(user.created * 1000).toISOString().split("T")[0];
|
|
105
|
-
|
|
106
|
-
return [
|
|
107
|
-
`Username: ${user.id}`,
|
|
108
|
-
`Karma: ${user.karma}`,
|
|
109
|
-
`Created: ${created}`,
|
|
110
|
-
user.about ? `About: ${user.about}` : null,
|
|
111
|
-
`Submitted: ${user.submitted?.length ?? 0} items`,
|
|
112
|
-
]
|
|
113
|
-
.filter(Boolean)
|
|
114
|
-
.join("\n");
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return tools;
|
|
120
|
-
}
|
|
121
|
-
}
|