@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.d.ts +167 -1
- package/dist/index.js +683 -12
- package/package.json +6 -2
- package/src/agent/agent.ts +4 -0
- package/src/index.ts +13 -0
- package/src/toolkits/base.ts +15 -0
- package/src/toolkits/duckduckgo.ts +256 -0
- package/src/toolkits/gmail.ts +226 -0
- package/src/toolkits/hackernews.ts +121 -0
- package/src/toolkits/websearch.ts +158 -0
- package/src/toolkits/whatsapp.ts +209 -0
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:
|
|
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,
|
|
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,
|
|
3828
|
+
jsonSchemaToZod(schema, z8) {
|
|
3826
3829
|
if (!schema || !schema.properties) {
|
|
3827
|
-
return
|
|
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 =
|
|
3836
|
-
if (prop.enum) field =
|
|
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 =
|
|
3843
|
+
field = z8.number();
|
|
3841
3844
|
break;
|
|
3842
3845
|
case "boolean":
|
|
3843
|
-
field =
|
|
3846
|
+
field = z8.boolean();
|
|
3844
3847
|
break;
|
|
3845
3848
|
case "array":
|
|
3846
|
-
field =
|
|
3849
|
+
field = z8.array(z8.any());
|
|
3847
3850
|
break;
|
|
3848
3851
|
case "object":
|
|
3849
|
-
field =
|
|
3852
|
+
field = z8.record(z8.any());
|
|
3850
3853
|
break;
|
|
3851
3854
|
default:
|
|
3852
|
-
field =
|
|
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
|
|
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,
|