@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.
Files changed (60) hide show
  1. package/dist/index.d.ts +1407 -0
  2. package/dist/index.js +5269 -0
  3. package/package.json +6 -2
  4. package/src/a2a/a2a-remote-agent.ts +0 -270
  5. package/src/a2a/types.ts +0 -142
  6. package/src/agent/agent.ts +0 -417
  7. package/src/agent/llm-loop.ts +0 -290
  8. package/src/agent/run-context.ts +0 -35
  9. package/src/agent/types.ts +0 -89
  10. package/src/events/event-bus.ts +0 -45
  11. package/src/events/types.ts +0 -16
  12. package/src/guardrails/types.ts +0 -5
  13. package/src/hooks/types.ts +0 -6
  14. package/src/index.ts +0 -157
  15. package/src/knowledge/knowledge-base.ts +0 -146
  16. package/src/logger/logger.ts +0 -249
  17. package/src/mcp/mcp-client.ts +0 -264
  18. package/src/memory/memory.ts +0 -87
  19. package/src/memory/types.ts +0 -13
  20. package/src/memory/user-memory.ts +0 -211
  21. package/src/models/provider.ts +0 -22
  22. package/src/models/providers/anthropic.ts +0 -360
  23. package/src/models/providers/google.ts +0 -386
  24. package/src/models/providers/ollama.ts +0 -211
  25. package/src/models/providers/openai.ts +0 -345
  26. package/src/models/providers/vertex.ts +0 -427
  27. package/src/models/registry.ts +0 -107
  28. package/src/models/types.ts +0 -124
  29. package/src/session/session-manager.ts +0 -75
  30. package/src/session/types.ts +0 -10
  31. package/src/storage/driver.ts +0 -10
  32. package/src/storage/in-memory.ts +0 -44
  33. package/src/storage/mongodb.ts +0 -70
  34. package/src/storage/postgres.ts +0 -81
  35. package/src/storage/sqlite.ts +0 -81
  36. package/src/team/modes.ts +0 -1
  37. package/src/team/team.ts +0 -323
  38. package/src/team/types.ts +0 -26
  39. package/src/toolkits/base.ts +0 -15
  40. package/src/toolkits/duckduckgo.ts +0 -256
  41. package/src/toolkits/gmail.ts +0 -226
  42. package/src/toolkits/hackernews.ts +0 -121
  43. package/src/toolkits/websearch.ts +0 -158
  44. package/src/toolkits/whatsapp.ts +0 -209
  45. package/src/tools/define-tool.ts +0 -22
  46. package/src/tools/tool-executor.ts +0 -221
  47. package/src/tools/types.ts +0 -36
  48. package/src/utils/retry.ts +0 -56
  49. package/src/vector/base.ts +0 -44
  50. package/src/vector/embeddings/google.ts +0 -64
  51. package/src/vector/embeddings/openai.ts +0 -66
  52. package/src/vector/in-memory.ts +0 -115
  53. package/src/vector/mongodb.ts +0 -241
  54. package/src/vector/pgvector.ts +0 -169
  55. package/src/vector/qdrant.ts +0 -203
  56. package/src/vector/types.ts +0 -55
  57. package/src/workflow/step-runner.ts +0 -303
  58. package/src/workflow/types.ts +0 -55
  59. package/src/workflow/workflow.ts +0 -68
  60. 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 };
@@ -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
- }
@@ -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
- }