@radaros/core 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -639,6 +639,9 @@ var Agent = class {
639
639
  get hasStructuredOutput() {
640
640
  return !!this.config.structuredOutput;
641
641
  }
642
+ get structuredOutputSchema() {
643
+ return this.config.structuredOutput;
644
+ }
642
645
  constructor(config) {
643
646
  this.config = config;
644
647
  this.name = config.name;
@@ -3787,14 +3790,14 @@ var MCPToolProvider = class {
3787
3790
  await this.discoverTools();
3788
3791
  }
3789
3792
  async discoverTools() {
3790
- const { z: z3 } = await import("zod");
3793
+ const { z: z8 } = await import("zod");
3791
3794
  const result = await this.client.listTools();
3792
3795
  const mcpTools = result.tools ?? [];
3793
3796
  this.tools = mcpTools.map((mcpTool) => {
3794
3797
  const toolName = mcpTool.name;
3795
3798
  const description = mcpTool.description ?? "";
3796
3799
  const inputSchema = mcpTool.inputSchema ?? { type: "object", properties: {} };
3797
- const parameters = this.jsonSchemaToZod(inputSchema, z3);
3800
+ const parameters = this.jsonSchemaToZod(inputSchema, z8);
3798
3801
  const execute = async (args, _ctx) => {
3799
3802
  const callResult = await this.client.callTool({
3800
3803
  name: toolName,
@@ -3822,9 +3825,9 @@ var MCPToolProvider = class {
3822
3825
  };
3823
3826
  });
3824
3827
  }
3825
- jsonSchemaToZod(schema, z3) {
3828
+ jsonSchemaToZod(schema, z8) {
3826
3829
  if (!schema || !schema.properties) {
3827
- return z3.object({}).passthrough();
3830
+ return z8.object({}).passthrough();
3828
3831
  }
3829
3832
  const shape = {};
3830
3833
  const required = schema.required ?? [];
@@ -3832,24 +3835,24 @@ var MCPToolProvider = class {
3832
3835
  let field;
3833
3836
  switch (prop.type) {
3834
3837
  case "string":
3835
- field = z3.string();
3836
- if (prop.enum) field = z3.enum(prop.enum);
3838
+ field = z8.string();
3839
+ if (prop.enum) field = z8.enum(prop.enum);
3837
3840
  break;
3838
3841
  case "number":
3839
3842
  case "integer":
3840
- field = z3.number();
3843
+ field = z8.number();
3841
3844
  break;
3842
3845
  case "boolean":
3843
- field = z3.boolean();
3846
+ field = z8.boolean();
3844
3847
  break;
3845
3848
  case "array":
3846
- field = z3.array(z3.any());
3849
+ field = z8.array(z8.any());
3847
3850
  break;
3848
3851
  case "object":
3849
- field = z3.record(z3.any());
3852
+ field = z8.record(z8.any());
3850
3853
  break;
3851
3854
  default:
3852
- field = z3.any();
3855
+ field = z8.any();
3853
3856
  }
3854
3857
  if (prop.description) {
3855
3858
  field = field.describe(prop.description);
@@ -3859,7 +3862,7 @@ var MCPToolProvider = class {
3859
3862
  }
3860
3863
  shape[key] = field;
3861
3864
  }
3862
- return z3.object(shape);
3865
+ return z8.object(shape);
3863
3866
  }
3864
3867
  /**
3865
3868
  * Returns tools from this MCP server as RadarOS ToolDef[].
@@ -4108,14 +4111,679 @@ var A2ARemoteAgent = class {
4108
4111
  ).map((p) => p.text).join("\n");
4109
4112
  }
4110
4113
  };
4114
+
4115
+ // src/toolkits/base.ts
4116
+ var Toolkit = class {
4117
+ };
4118
+
4119
+ // src/toolkits/websearch.ts
4120
+ import { z as z3 } from "zod";
4121
+ var WebSearchToolkit = class extends Toolkit {
4122
+ name = "websearch";
4123
+ config;
4124
+ constructor(config) {
4125
+ super();
4126
+ this.config = config;
4127
+ }
4128
+ getApiKey() {
4129
+ if (this.config.apiKey) return this.config.apiKey;
4130
+ const envKey = this.config.provider === "tavily" ? process.env.TAVILY_API_KEY : process.env.SERPAPI_API_KEY;
4131
+ if (!envKey) {
4132
+ const envName = this.config.provider === "tavily" ? "TAVILY_API_KEY" : "SERPAPI_API_KEY";
4133
+ throw new Error(
4134
+ `WebSearchToolkit: No API key provided. Set ${envName} env var or pass apiKey in config.`
4135
+ );
4136
+ }
4137
+ return envKey;
4138
+ }
4139
+ getTools() {
4140
+ const self = this;
4141
+ return [
4142
+ {
4143
+ name: "web_search",
4144
+ description: "Search the web for current information. Returns titles, URLs, and snippets from search results.",
4145
+ parameters: z3.object({
4146
+ query: z3.string().describe("The search query"),
4147
+ maxResults: z3.number().optional().describe("Maximum number of results (default 5)")
4148
+ }),
4149
+ execute: async (args, _ctx) => {
4150
+ const query = args.query;
4151
+ const max = args.maxResults ?? self.config.maxResults ?? 5;
4152
+ if (self.config.provider === "tavily") {
4153
+ return self.searchTavily(query, max);
4154
+ }
4155
+ return self.searchSerpApi(query, max);
4156
+ }
4157
+ }
4158
+ ];
4159
+ }
4160
+ async searchTavily(query, maxResults) {
4161
+ const apiKey = this.getApiKey();
4162
+ const res = await fetch("https://api.tavily.com/search", {
4163
+ method: "POST",
4164
+ headers: { "Content-Type": "application/json" },
4165
+ body: JSON.stringify({
4166
+ api_key: apiKey,
4167
+ query,
4168
+ max_results: maxResults,
4169
+ include_answer: true
4170
+ })
4171
+ });
4172
+ if (!res.ok) {
4173
+ throw new Error(`Tavily search failed: ${res.status} ${res.statusText}`);
4174
+ }
4175
+ const data = await res.json();
4176
+ const results = [];
4177
+ if (data.answer) {
4178
+ results.push(`Answer: ${data.answer}
4179
+ `);
4180
+ }
4181
+ for (const r of data.results ?? []) {
4182
+ results.push(`Title: ${r.title}
4183
+ URL: ${r.url}
4184
+ Snippet: ${r.content}
4185
+ `);
4186
+ }
4187
+ return results.join("\n---\n") || "No results found.";
4188
+ }
4189
+ async searchSerpApi(query, maxResults) {
4190
+ const apiKey = this.getApiKey();
4191
+ const params = new URLSearchParams({
4192
+ q: query,
4193
+ api_key: apiKey,
4194
+ engine: "google",
4195
+ num: String(maxResults)
4196
+ });
4197
+ const res = await fetch(
4198
+ `https://serpapi.com/search.json?${params.toString()}`
4199
+ );
4200
+ if (!res.ok) {
4201
+ throw new Error(
4202
+ `SerpAPI search failed: ${res.status} ${res.statusText}`
4203
+ );
4204
+ }
4205
+ const data = await res.json();
4206
+ const results = [];
4207
+ if (data.answer_box?.answer) {
4208
+ results.push(`Answer: ${data.answer_box.answer}
4209
+ `);
4210
+ }
4211
+ for (const r of (data.organic_results ?? []).slice(0, maxResults)) {
4212
+ results.push(
4213
+ `Title: ${r.title}
4214
+ URL: ${r.link}
4215
+ Snippet: ${r.snippet ?? ""}
4216
+ `
4217
+ );
4218
+ }
4219
+ return results.join("\n---\n") || "No results found.";
4220
+ }
4221
+ };
4222
+
4223
+ // src/toolkits/gmail.ts
4224
+ import { z as z4 } from "zod";
4225
+ import { createRequire as createRequire16 } from "module";
4226
+ var _require16 = createRequire16(import.meta.url);
4227
+ var GmailToolkit = class extends Toolkit {
4228
+ name = "gmail";
4229
+ config;
4230
+ gmail = null;
4231
+ constructor(config = {}) {
4232
+ super();
4233
+ this.config = config;
4234
+ }
4235
+ async getGmailClient() {
4236
+ if (this.gmail) return this.gmail;
4237
+ if (this.config.authClient) {
4238
+ const { google: google3 } = _require16("googleapis");
4239
+ this.gmail = google3.gmail({ version: "v1", auth: this.config.authClient });
4240
+ return this.gmail;
4241
+ }
4242
+ const credPath = this.config.credentialsPath ?? process.env.GMAIL_CREDENTIALS_PATH;
4243
+ const tokenPath = this.config.tokenPath ?? process.env.GMAIL_TOKEN_PATH;
4244
+ if (!credPath || !tokenPath) {
4245
+ throw new Error(
4246
+ "GmailToolkit: Provide credentialsPath + tokenPath, or an authClient. Set GMAIL_CREDENTIALS_PATH and GMAIL_TOKEN_PATH env vars, or pass them in config."
4247
+ );
4248
+ }
4249
+ const { google: google2 } = _require16("googleapis");
4250
+ const fs = await import("fs");
4251
+ const creds = JSON.parse(fs.readFileSync(credPath, "utf-8"));
4252
+ const token = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
4253
+ const { client_id, client_secret, redirect_uris } = creds.installed || creds.web;
4254
+ const oAuth2 = new google2.auth.OAuth2(
4255
+ client_id,
4256
+ client_secret,
4257
+ redirect_uris?.[0]
4258
+ );
4259
+ oAuth2.setCredentials(token);
4260
+ this.gmail = google2.gmail({ version: "v1", auth: oAuth2 });
4261
+ return this.gmail;
4262
+ }
4263
+ getTools() {
4264
+ const self = this;
4265
+ return [
4266
+ {
4267
+ name: "gmail_send",
4268
+ description: "Send an email via Gmail. Provide recipient, subject, and body.",
4269
+ parameters: z4.object({
4270
+ to: z4.string().describe("Recipient email address"),
4271
+ subject: z4.string().describe("Email subject line"),
4272
+ body: z4.string().describe("Email body (plain text)")
4273
+ }),
4274
+ execute: async (args, _ctx) => {
4275
+ const gmail = await self.getGmailClient();
4276
+ const rawMessage = [
4277
+ `To: ${args.to}`,
4278
+ `Subject: ${args.subject}`,
4279
+ "Content-Type: text/plain; charset=utf-8",
4280
+ "",
4281
+ args.body
4282
+ ].join("\n");
4283
+ const encoded = Buffer.from(rawMessage).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
4284
+ const res = await gmail.users.messages.send({
4285
+ userId: "me",
4286
+ requestBody: { raw: encoded }
4287
+ });
4288
+ return `Email sent successfully. Message ID: ${res.data.id}`;
4289
+ }
4290
+ },
4291
+ {
4292
+ name: "gmail_search",
4293
+ description: "Search emails in Gmail. Returns subject, from, date, and snippet for matching messages.",
4294
+ parameters: z4.object({
4295
+ query: z4.string().describe(
4296
+ 'Gmail search query (e.g. "from:john subject:meeting is:unread")'
4297
+ ),
4298
+ maxResults: z4.number().optional().describe("Maximum number of results (default 10)")
4299
+ }),
4300
+ execute: async (args, _ctx) => {
4301
+ const gmail = await self.getGmailClient();
4302
+ const max = args.maxResults ?? 10;
4303
+ const list = await gmail.users.messages.list({
4304
+ userId: "me",
4305
+ q: args.query,
4306
+ maxResults: max
4307
+ });
4308
+ const messages = list.data.messages ?? [];
4309
+ if (messages.length === 0) return "No emails found.";
4310
+ const results = [];
4311
+ for (const msg of messages) {
4312
+ const detail = await gmail.users.messages.get({
4313
+ userId: "me",
4314
+ id: msg.id,
4315
+ format: "metadata",
4316
+ metadataHeaders: ["From", "Subject", "Date"]
4317
+ });
4318
+ const headers = detail.data.payload?.headers ?? [];
4319
+ const getHeader = (name) => headers.find(
4320
+ (h) => h.name.toLowerCase() === name.toLowerCase()
4321
+ )?.value ?? "";
4322
+ results.push(
4323
+ `ID: ${msg.id}
4324
+ From: ${getHeader("From")}
4325
+ Subject: ${getHeader("Subject")}
4326
+ Date: ${getHeader("Date")}
4327
+ Snippet: ${detail.data.snippet ?? ""}`
4328
+ );
4329
+ }
4330
+ return results.join("\n\n---\n\n");
4331
+ }
4332
+ },
4333
+ {
4334
+ name: "gmail_read",
4335
+ description: "Read the full content of an email by its message ID.",
4336
+ parameters: z4.object({
4337
+ messageId: z4.string().describe("The Gmail message ID to read")
4338
+ }),
4339
+ execute: async (args, _ctx) => {
4340
+ const gmail = await self.getGmailClient();
4341
+ const detail = await gmail.users.messages.get({
4342
+ userId: "me",
4343
+ id: args.messageId,
4344
+ format: "full"
4345
+ });
4346
+ const headers = detail.data.payload?.headers ?? [];
4347
+ const getHeader = (name) => headers.find(
4348
+ (h) => h.name.toLowerCase() === name.toLowerCase()
4349
+ )?.value ?? "";
4350
+ let body = "";
4351
+ const payload = detail.data.payload;
4352
+ if (payload?.body?.data) {
4353
+ body = Buffer.from(payload.body.data, "base64").toString("utf-8");
4354
+ } else if (payload?.parts) {
4355
+ const textPart = payload.parts.find(
4356
+ (p) => p.mimeType === "text/plain"
4357
+ );
4358
+ if (textPart?.body?.data) {
4359
+ body = Buffer.from(textPart.body.data, "base64").toString(
4360
+ "utf-8"
4361
+ );
4362
+ } else {
4363
+ const htmlPart = payload.parts.find(
4364
+ (p) => p.mimeType === "text/html"
4365
+ );
4366
+ if (htmlPart?.body?.data) {
4367
+ body = Buffer.from(htmlPart.body.data, "base64").toString(
4368
+ "utf-8"
4369
+ );
4370
+ }
4371
+ }
4372
+ }
4373
+ return `From: ${getHeader("From")}
4374
+ To: ${getHeader("To")}
4375
+ Subject: ${getHeader("Subject")}
4376
+ Date: ${getHeader("Date")}
4377
+
4378
+ ${body || "(no body)"}`;
4379
+ }
4380
+ }
4381
+ ];
4382
+ }
4383
+ };
4384
+
4385
+ // src/toolkits/whatsapp.ts
4386
+ import { z as z5 } from "zod";
4387
+ var WhatsAppToolkit = class extends Toolkit {
4388
+ name = "whatsapp";
4389
+ accessToken;
4390
+ phoneNumberId;
4391
+ version;
4392
+ recipientWaid;
4393
+ constructor(config = {}) {
4394
+ super();
4395
+ this.accessToken = config.accessToken ?? process.env.WHATSAPP_ACCESS_TOKEN ?? "";
4396
+ this.phoneNumberId = config.phoneNumberId ?? process.env.WHATSAPP_PHONE_NUMBER_ID ?? "";
4397
+ this.version = config.version ?? process.env.WHATSAPP_VERSION ?? "v22.0";
4398
+ this.recipientWaid = config.recipientWaid ?? process.env.WHATSAPP_RECIPIENT_WAID;
4399
+ }
4400
+ getBaseUrl() {
4401
+ return `https://graph.facebook.com/${this.version}/${this.phoneNumberId}/messages`;
4402
+ }
4403
+ validate() {
4404
+ if (!this.accessToken) {
4405
+ throw new Error(
4406
+ "WhatsAppToolkit: accessToken is required. Set WHATSAPP_ACCESS_TOKEN env var or pass in config."
4407
+ );
4408
+ }
4409
+ if (!this.phoneNumberId) {
4410
+ throw new Error(
4411
+ "WhatsAppToolkit: phoneNumberId is required. Set WHATSAPP_PHONE_NUMBER_ID env var or pass in config."
4412
+ );
4413
+ }
4414
+ }
4415
+ resolveRecipient(recipient) {
4416
+ const r = recipient ?? this.recipientWaid;
4417
+ if (!r) {
4418
+ throw new Error(
4419
+ "WhatsAppToolkit: recipient is required. Provide it in the tool call or set WHATSAPP_RECIPIENT_WAID env var."
4420
+ );
4421
+ }
4422
+ return r.replace(/[^0-9]/g, "");
4423
+ }
4424
+ getTools() {
4425
+ const self = this;
4426
+ return [
4427
+ {
4428
+ name: "whatsapp_send_text",
4429
+ description: "Send a text message to a WhatsApp user via WhatsApp Business Cloud API.",
4430
+ parameters: z5.object({
4431
+ text: z5.string().describe("The text message to send"),
4432
+ recipient: z5.string().optional().describe(
4433
+ "Recipient WhatsApp number with country code (e.g. 919876543210). Uses default if omitted."
4434
+ ),
4435
+ previewUrl: z5.boolean().optional().describe("Enable URL previews in the message (default false)")
4436
+ }),
4437
+ execute: async (args, _ctx) => {
4438
+ self.validate();
4439
+ const recipient = self.resolveRecipient(args.recipient);
4440
+ const res = await fetch(self.getBaseUrl(), {
4441
+ method: "POST",
4442
+ headers: {
4443
+ Authorization: `Bearer ${self.accessToken}`,
4444
+ "Content-Type": "application/json"
4445
+ },
4446
+ body: JSON.stringify({
4447
+ messaging_product: "whatsapp",
4448
+ recipient_type: "individual",
4449
+ to: recipient,
4450
+ type: "text",
4451
+ text: {
4452
+ preview_url: args.previewUrl ?? false,
4453
+ body: args.text
4454
+ }
4455
+ })
4456
+ });
4457
+ if (!res.ok) {
4458
+ const err = await res.text();
4459
+ throw new Error(
4460
+ `WhatsApp send failed: ${res.status} ${err}`
4461
+ );
4462
+ }
4463
+ const data = await res.json();
4464
+ const msgId = data.messages?.[0]?.id ?? "unknown";
4465
+ return `Message sent successfully to ${recipient}. Message ID: ${msgId}`;
4466
+ }
4467
+ },
4468
+ {
4469
+ name: "whatsapp_send_template",
4470
+ description: "Send a template message to a WhatsApp user. Required for first-time outreach (24-hour messaging window).",
4471
+ parameters: z5.object({
4472
+ templateName: z5.string().describe(
4473
+ 'The pre-approved template name (e.g. "hello_world")'
4474
+ ),
4475
+ recipient: z5.string().optional().describe(
4476
+ "Recipient WhatsApp number with country code. Uses default if omitted."
4477
+ ),
4478
+ languageCode: z5.string().optional().describe('Template language code (default "en_US")'),
4479
+ components: z5.array(z5.record(z5.any())).optional().describe(
4480
+ "Template components for dynamic content (header, body, button parameters)"
4481
+ )
4482
+ }),
4483
+ execute: async (args, _ctx) => {
4484
+ self.validate();
4485
+ const recipient = self.resolveRecipient(args.recipient);
4486
+ const langCode = args.languageCode ?? "en_US";
4487
+ const template = {
4488
+ name: args.templateName,
4489
+ language: { code: langCode }
4490
+ };
4491
+ if (args.components) {
4492
+ template.components = args.components;
4493
+ }
4494
+ const res = await fetch(self.getBaseUrl(), {
4495
+ method: "POST",
4496
+ headers: {
4497
+ Authorization: `Bearer ${self.accessToken}`,
4498
+ "Content-Type": "application/json"
4499
+ },
4500
+ body: JSON.stringify({
4501
+ messaging_product: "whatsapp",
4502
+ recipient_type: "individual",
4503
+ to: recipient,
4504
+ type: "template",
4505
+ template
4506
+ })
4507
+ });
4508
+ if (!res.ok) {
4509
+ const err = await res.text();
4510
+ throw new Error(
4511
+ `WhatsApp template send failed: ${res.status} ${err}`
4512
+ );
4513
+ }
4514
+ const data = await res.json();
4515
+ const msgId = data.messages?.[0]?.id ?? "unknown";
4516
+ return `Template message "${args.templateName}" sent to ${recipient}. Message ID: ${msgId}`;
4517
+ }
4518
+ }
4519
+ ];
4520
+ }
4521
+ };
4522
+
4523
+ // src/toolkits/hackernews.ts
4524
+ import { z as z6 } from "zod";
4525
+ var HackerNewsToolkit = class extends Toolkit {
4526
+ name = "hackernews";
4527
+ config;
4528
+ constructor(config = {}) {
4529
+ super();
4530
+ this.config = {
4531
+ enableGetTopStories: config.enableGetTopStories ?? true,
4532
+ enableGetUserDetails: config.enableGetUserDetails ?? true
4533
+ };
4534
+ }
4535
+ getTools() {
4536
+ const tools = [];
4537
+ if (this.config.enableGetTopStories) {
4538
+ tools.push({
4539
+ name: "hackernews_top_stories",
4540
+ description: "Get the top stories from Hacker News. Returns title, URL, score, author, and comment count.",
4541
+ parameters: z6.object({
4542
+ numStories: z6.number().optional().describe("Number of top stories to fetch (default 10, max 30)")
4543
+ }),
4544
+ execute: async (args, _ctx) => {
4545
+ const num = Math.min(args.numStories ?? 10, 30);
4546
+ const idsRes = await fetch(
4547
+ "https://hacker-news.firebaseio.com/v0/topstories.json"
4548
+ );
4549
+ if (!idsRes.ok) throw new Error(`HN API failed: ${idsRes.status}`);
4550
+ const ids = await idsRes.json();
4551
+ const stories = await Promise.all(
4552
+ ids.slice(0, num).map(async (id) => {
4553
+ const res = await fetch(
4554
+ `https://hacker-news.firebaseio.com/v0/item/${id}.json`
4555
+ );
4556
+ return res.json();
4557
+ })
4558
+ );
4559
+ return stories.map(
4560
+ (s, i) => `${i + 1}. ${s.title}
4561
+ URL: ${s.url ?? `https://news.ycombinator.com/item?id=${s.id}`}
4562
+ Score: ${s.score} | By: ${s.by} | Comments: ${s.descendants ?? 0}`
4563
+ ).join("\n\n");
4564
+ }
4565
+ });
4566
+ }
4567
+ if (this.config.enableGetUserDetails) {
4568
+ tools.push({
4569
+ name: "hackernews_user",
4570
+ description: "Get details about a Hacker News user by username. Returns karma, about, and account creation date.",
4571
+ parameters: z6.object({
4572
+ username: z6.string().describe("The HN username to look up")
4573
+ }),
4574
+ execute: async (args, _ctx) => {
4575
+ const username = args.username;
4576
+ const res = await fetch(
4577
+ `https://hacker-news.firebaseio.com/v0/user/${username}.json`
4578
+ );
4579
+ if (!res.ok)
4580
+ throw new Error(`HN user API failed: ${res.status}`);
4581
+ const user = await res.json();
4582
+ if (!user) return `User "${username}" not found.`;
4583
+ const created = new Date(user.created * 1e3).toISOString().split("T")[0];
4584
+ return [
4585
+ `Username: ${user.id}`,
4586
+ `Karma: ${user.karma}`,
4587
+ `Created: ${created}`,
4588
+ user.about ? `About: ${user.about}` : null,
4589
+ `Submitted: ${user.submitted?.length ?? 0} items`
4590
+ ].filter(Boolean).join("\n");
4591
+ }
4592
+ });
4593
+ }
4594
+ return tools;
4595
+ }
4596
+ };
4597
+
4598
+ // src/toolkits/duckduckgo.ts
4599
+ import { z as z7 } from "zod";
4600
+ var DuckDuckGoToolkit = class extends Toolkit {
4601
+ name = "duckduckgo";
4602
+ config;
4603
+ constructor(config = {}) {
4604
+ super();
4605
+ this.config = {
4606
+ enableSearch: config.enableSearch ?? true,
4607
+ enableNews: config.enableNews ?? true,
4608
+ maxResults: config.maxResults ?? 5
4609
+ };
4610
+ }
4611
+ getTools() {
4612
+ const tools = [];
4613
+ if (this.config.enableSearch) {
4614
+ tools.push(this.buildSearchTool());
4615
+ }
4616
+ if (this.config.enableNews) {
4617
+ tools.push(this.buildNewsTool());
4618
+ }
4619
+ return tools;
4620
+ }
4621
+ buildSearchTool() {
4622
+ const self = this;
4623
+ return {
4624
+ name: "duckduckgo_search",
4625
+ description: "Search the web using DuckDuckGo. Returns titles, URLs, and snippets. No API key required.",
4626
+ parameters: z7.object({
4627
+ query: z7.string().describe("The search query"),
4628
+ maxResults: z7.number().optional().describe("Maximum number of results (default 5)")
4629
+ }),
4630
+ async execute(args, _ctx) {
4631
+ const query = args.query;
4632
+ const max = args.maxResults ?? self.config.maxResults ?? 5;
4633
+ return self.search(query, max);
4634
+ }
4635
+ };
4636
+ }
4637
+ buildNewsTool() {
4638
+ const self = this;
4639
+ return {
4640
+ name: "duckduckgo_news",
4641
+ description: "Get the latest news from DuckDuckGo. Returns headlines, sources, URLs, and dates.",
4642
+ parameters: z7.object({
4643
+ query: z7.string().describe("The news search query"),
4644
+ maxResults: z7.number().optional().describe("Maximum number of results (default 5)")
4645
+ }),
4646
+ async execute(args, _ctx) {
4647
+ const query = args.query;
4648
+ const max = args.maxResults ?? self.config.maxResults ?? 5;
4649
+ return self.searchNews(query, max);
4650
+ }
4651
+ };
4652
+ }
4653
+ async search(query, maxResults) {
4654
+ const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_redirect=1&no_html=1&skip_disambig=1`;
4655
+ const res = await fetch(url, {
4656
+ headers: { "User-Agent": "RadarOS/1.0" }
4657
+ });
4658
+ if (!res.ok) {
4659
+ throw new Error(`DuckDuckGo search failed: ${res.status}`);
4660
+ }
4661
+ const data = await res.json();
4662
+ const results = [];
4663
+ if (data.Abstract) {
4664
+ results.push(
4665
+ `Answer: ${data.Abstract}
4666
+ Source: ${data.AbstractSource}
4667
+ URL: ${data.AbstractURL}`
4668
+ );
4669
+ }
4670
+ const topics = data.RelatedTopics ?? [];
4671
+ for (const topic of topics.slice(0, maxResults)) {
4672
+ if (topic.Text && topic.FirstURL) {
4673
+ results.push(`${topic.Text}
4674
+ URL: ${topic.FirstURL}`);
4675
+ }
4676
+ if (topic.Topics) {
4677
+ for (const sub of topic.Topics.slice(0, 2)) {
4678
+ if (sub.Text && sub.FirstURL) {
4679
+ results.push(`${sub.Text}
4680
+ URL: ${sub.FirstURL}`);
4681
+ }
4682
+ }
4683
+ }
4684
+ }
4685
+ if (results.length === 0 && data.Redirect) {
4686
+ return `Redirect: ${data.Redirect}`;
4687
+ }
4688
+ if (results.length === 0) {
4689
+ const htmlResults = await this.scrapeHtmlSearch(query, maxResults);
4690
+ if (htmlResults) return htmlResults;
4691
+ return "No results found.";
4692
+ }
4693
+ return results.join("\n\n---\n\n");
4694
+ }
4695
+ async scrapeHtmlSearch(query, maxResults) {
4696
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
4697
+ const res = await fetch(url, {
4698
+ headers: {
4699
+ "User-Agent": "Mozilla/5.0 (compatible; RadarOS/1.0; +https://radaros.dev)"
4700
+ }
4701
+ });
4702
+ if (!res.ok) return null;
4703
+ const html = await res.text();
4704
+ const results = [];
4705
+ const linkRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
4706
+ const snippetRegex = /<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
4707
+ const links = [];
4708
+ let match;
4709
+ while ((match = linkRegex.exec(html)) !== null) {
4710
+ const rawUrl = match[1];
4711
+ const title = match[2].replace(/<[^>]*>/g, "").trim();
4712
+ const decoded = this.decodeDdgUrl(rawUrl);
4713
+ if (title && decoded) {
4714
+ links.push({ url: decoded, title });
4715
+ }
4716
+ }
4717
+ const snippets = [];
4718
+ while ((match = snippetRegex.exec(html)) !== null) {
4719
+ snippets.push(match[1].replace(/<[^>]*>/g, "").trim());
4720
+ }
4721
+ for (let i = 0; i < Math.min(links.length, maxResults); i++) {
4722
+ results.push(
4723
+ `Title: ${links[i].title}
4724
+ URL: ${links[i].url}
4725
+ Snippet: ${snippets[i] ?? ""}`
4726
+ );
4727
+ }
4728
+ return results.length > 0 ? results.join("\n\n---\n\n") : null;
4729
+ }
4730
+ decodeDdgUrl(url) {
4731
+ if (url.startsWith("//duckduckgo.com/l/?uddg=")) {
4732
+ const match = url.match(/uddg=([^&]*)/);
4733
+ if (match) return decodeURIComponent(match[1]);
4734
+ }
4735
+ if (url.startsWith("http")) return url;
4736
+ return null;
4737
+ }
4738
+ async searchNews(query, maxResults) {
4739
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query + " news")}&iar=news`;
4740
+ const res = await fetch(url, {
4741
+ headers: {
4742
+ "User-Agent": "Mozilla/5.0 (compatible; RadarOS/1.0; +https://radaros.dev)"
4743
+ }
4744
+ });
4745
+ if (!res.ok) {
4746
+ throw new Error(`DuckDuckGo news search failed: ${res.status}`);
4747
+ }
4748
+ const html = await res.text();
4749
+ const results = [];
4750
+ const linkRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
4751
+ const snippetRegex = /<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
4752
+ const links = [];
4753
+ let match;
4754
+ while ((match = linkRegex.exec(html)) !== null) {
4755
+ const rawUrl = match[1];
4756
+ const title = match[2].replace(/<[^>]*>/g, "").trim();
4757
+ const decoded = this.decodeDdgUrl(rawUrl);
4758
+ if (title && decoded) {
4759
+ links.push({ url: decoded, title });
4760
+ }
4761
+ }
4762
+ const snippets = [];
4763
+ while ((match = snippetRegex.exec(html)) !== null) {
4764
+ snippets.push(match[1].replace(/<[^>]*>/g, "").trim());
4765
+ }
4766
+ for (let i = 0; i < Math.min(links.length, maxResults); i++) {
4767
+ results.push(
4768
+ `Title: ${links[i].title}
4769
+ URL: ${links[i].url}
4770
+ Snippet: ${snippets[i] ?? ""}`
4771
+ );
4772
+ }
4773
+ return results.length > 0 ? results.join("\n\n---\n\n") : "No news results found.";
4774
+ }
4775
+ };
4111
4776
  export {
4112
4777
  A2ARemoteAgent,
4113
4778
  Agent,
4114
4779
  AnthropicProvider,
4115
4780
  BaseVectorStore,
4781
+ DuckDuckGoToolkit,
4116
4782
  EventBus,
4783
+ GmailToolkit,
4117
4784
  GoogleEmbedding,
4118
4785
  GoogleProvider,
4786
+ HackerNewsToolkit,
4119
4787
  InMemoryStorage,
4120
4788
  InMemoryVectorStore,
4121
4789
  KnowledgeBase,
@@ -4138,7 +4806,10 @@ export {
4138
4806
  Team,
4139
4807
  TeamMode,
4140
4808
  ToolExecutor,
4809
+ Toolkit,
4141
4810
  VertexAIProvider,
4811
+ WebSearchToolkit,
4812
+ WhatsAppToolkit,
4142
4813
  Workflow,
4143
4814
  anthropic,
4144
4815
  defineTool,