@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.
@@ -0,0 +1,158 @@
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 WebSearchConfig {
7
+ /** Search provider: "tavily" or "serpapi". */
8
+ provider: "tavily" | "serpapi";
9
+ /** API key for the search provider. Falls back to TAVILY_API_KEY or SERPAPI_API_KEY env vars. */
10
+ apiKey?: string;
11
+ /** Max results to return per search (default 5). */
12
+ maxResults?: number;
13
+ }
14
+
15
+ /**
16
+ * Web Search Toolkit — search the web from your agent.
17
+ *
18
+ * Supports Tavily and SerpAPI backends.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const search = new WebSearchToolkit({ provider: "tavily" });
23
+ * const agent = new Agent({ tools: [...search.getTools()] });
24
+ * ```
25
+ */
26
+ export class WebSearchToolkit extends Toolkit {
27
+ readonly name = "websearch";
28
+ private config: WebSearchConfig;
29
+
30
+ constructor(config: WebSearchConfig) {
31
+ super();
32
+ this.config = config;
33
+ }
34
+
35
+ private getApiKey(): string {
36
+ if (this.config.apiKey) return this.config.apiKey;
37
+
38
+ const envKey =
39
+ this.config.provider === "tavily"
40
+ ? process.env.TAVILY_API_KEY
41
+ : process.env.SERPAPI_API_KEY;
42
+
43
+ if (!envKey) {
44
+ const envName =
45
+ this.config.provider === "tavily"
46
+ ? "TAVILY_API_KEY"
47
+ : "SERPAPI_API_KEY";
48
+ throw new Error(
49
+ `WebSearchToolkit: No API key provided. Set ${envName} env var or pass apiKey in config.`
50
+ );
51
+ }
52
+ return envKey;
53
+ }
54
+
55
+ getTools(): ToolDef[] {
56
+ const self = this;
57
+
58
+ return [
59
+ {
60
+ name: "web_search",
61
+ description:
62
+ "Search the web for current information. Returns titles, URLs, and snippets from search results.",
63
+ parameters: z.object({
64
+ query: z.string().describe("The search query"),
65
+ maxResults: z
66
+ .number()
67
+ .optional()
68
+ .describe("Maximum number of results (default 5)"),
69
+ }),
70
+ execute: async (
71
+ args: Record<string, unknown>,
72
+ _ctx: RunContext
73
+ ): Promise<string> => {
74
+ const query = args.query as string;
75
+ const max =
76
+ (args.maxResults as number) ?? self.config.maxResults ?? 5;
77
+
78
+ if (self.config.provider === "tavily") {
79
+ return self.searchTavily(query, max);
80
+ }
81
+ return self.searchSerpApi(query, max);
82
+ },
83
+ },
84
+ ];
85
+ }
86
+
87
+ private async searchTavily(
88
+ query: string,
89
+ maxResults: number
90
+ ): Promise<string> {
91
+ const apiKey = this.getApiKey();
92
+ const res = await fetch("https://api.tavily.com/search", {
93
+ method: "POST",
94
+ headers: { "Content-Type": "application/json" },
95
+ body: JSON.stringify({
96
+ api_key: apiKey,
97
+ query,
98
+ max_results: maxResults,
99
+ include_answer: true,
100
+ }),
101
+ });
102
+
103
+ if (!res.ok) {
104
+ throw new Error(`Tavily search failed: ${res.status} ${res.statusText}`);
105
+ }
106
+
107
+ const data = (await res.json()) as any;
108
+ const results: string[] = [];
109
+
110
+ if (data.answer) {
111
+ results.push(`Answer: ${data.answer}\n`);
112
+ }
113
+
114
+ for (const r of data.results ?? []) {
115
+ results.push(`Title: ${r.title}\nURL: ${r.url}\nSnippet: ${r.content}\n`);
116
+ }
117
+
118
+ return results.join("\n---\n") || "No results found.";
119
+ }
120
+
121
+ private async searchSerpApi(
122
+ query: string,
123
+ maxResults: number
124
+ ): Promise<string> {
125
+ const apiKey = this.getApiKey();
126
+ const params = new URLSearchParams({
127
+ q: query,
128
+ api_key: apiKey,
129
+ engine: "google",
130
+ num: String(maxResults),
131
+ });
132
+
133
+ const res = await fetch(
134
+ `https://serpapi.com/search.json?${params.toString()}`
135
+ );
136
+
137
+ if (!res.ok) {
138
+ throw new Error(
139
+ `SerpAPI search failed: ${res.status} ${res.statusText}`
140
+ );
141
+ }
142
+
143
+ const data = (await res.json()) as any;
144
+ const results: string[] = [];
145
+
146
+ if (data.answer_box?.answer) {
147
+ results.push(`Answer: ${data.answer_box.answer}\n`);
148
+ }
149
+
150
+ for (const r of (data.organic_results ?? []).slice(0, maxResults)) {
151
+ results.push(
152
+ `Title: ${r.title}\nURL: ${r.link}\nSnippet: ${r.snippet ?? ""}\n`
153
+ );
154
+ }
155
+
156
+ return results.join("\n---\n") || "No results found.";
157
+ }
158
+ }
@@ -0,0 +1,209 @@
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 WhatsAppConfig {
7
+ /** WhatsApp Business API access token. Falls back to WHATSAPP_ACCESS_TOKEN env var. */
8
+ accessToken?: string;
9
+ /** WhatsApp Business phone number ID. Falls back to WHATSAPP_PHONE_NUMBER_ID env var. */
10
+ phoneNumberId?: string;
11
+ /** API version (default "v22.0"). Falls back to WHATSAPP_VERSION env var. */
12
+ version?: string;
13
+ /** Default recipient WhatsApp ID. Falls back to WHATSAPP_RECIPIENT_WAID env var. */
14
+ recipientWaid?: string;
15
+ }
16
+
17
+ /**
18
+ * WhatsApp Toolkit — send messages via WhatsApp Business Cloud API (Meta).
19
+ *
20
+ * Uses the WhatsApp Cloud API directly (no Twilio).
21
+ * Setup: https://developers.facebook.com/docs/whatsapp/cloud-api/get-started
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const whatsapp = new WhatsAppToolkit({
26
+ * accessToken: "...",
27
+ * phoneNumberId: "...",
28
+ * });
29
+ * const agent = new Agent({ tools: [...whatsapp.getTools()] });
30
+ * ```
31
+ */
32
+ export class WhatsAppToolkit extends Toolkit {
33
+ readonly name = "whatsapp";
34
+ private accessToken: string;
35
+ private phoneNumberId: string;
36
+ private version: string;
37
+ private recipientWaid: string | undefined;
38
+
39
+ constructor(config: WhatsAppConfig = {}) {
40
+ super();
41
+ this.accessToken =
42
+ config.accessToken ?? process.env.WHATSAPP_ACCESS_TOKEN ?? "";
43
+ this.phoneNumberId =
44
+ config.phoneNumberId ?? process.env.WHATSAPP_PHONE_NUMBER_ID ?? "";
45
+ this.version =
46
+ config.version ?? process.env.WHATSAPP_VERSION ?? "v22.0";
47
+ this.recipientWaid =
48
+ config.recipientWaid ?? process.env.WHATSAPP_RECIPIENT_WAID;
49
+ }
50
+
51
+ private getBaseUrl(): string {
52
+ return `https://graph.facebook.com/${this.version}/${this.phoneNumberId}/messages`;
53
+ }
54
+
55
+ private validate(): void {
56
+ if (!this.accessToken) {
57
+ throw new Error(
58
+ "WhatsAppToolkit: accessToken is required. Set WHATSAPP_ACCESS_TOKEN env var or pass in config."
59
+ );
60
+ }
61
+ if (!this.phoneNumberId) {
62
+ throw new Error(
63
+ "WhatsAppToolkit: phoneNumberId is required. Set WHATSAPP_PHONE_NUMBER_ID env var or pass in config."
64
+ );
65
+ }
66
+ }
67
+
68
+ private resolveRecipient(recipient?: string): string {
69
+ const r = recipient ?? this.recipientWaid;
70
+ if (!r) {
71
+ throw new Error(
72
+ "WhatsAppToolkit: recipient is required. Provide it in the tool call or set WHATSAPP_RECIPIENT_WAID env var."
73
+ );
74
+ }
75
+ return r.replace(/[^0-9]/g, "");
76
+ }
77
+
78
+ getTools(): ToolDef[] {
79
+ const self = this;
80
+
81
+ return [
82
+ {
83
+ name: "whatsapp_send_text",
84
+ description:
85
+ "Send a text message to a WhatsApp user via WhatsApp Business Cloud API.",
86
+ parameters: z.object({
87
+ text: z.string().describe("The text message to send"),
88
+ recipient: z
89
+ .string()
90
+ .optional()
91
+ .describe(
92
+ "Recipient WhatsApp number with country code (e.g. 919876543210). Uses default if omitted."
93
+ ),
94
+ previewUrl: z
95
+ .boolean()
96
+ .optional()
97
+ .describe("Enable URL previews in the message (default false)"),
98
+ }),
99
+ execute: async (
100
+ args: Record<string, unknown>,
101
+ _ctx: RunContext
102
+ ): Promise<string> => {
103
+ self.validate();
104
+ const recipient = self.resolveRecipient(args.recipient as string);
105
+
106
+ const res = await fetch(self.getBaseUrl(), {
107
+ method: "POST",
108
+ headers: {
109
+ Authorization: `Bearer ${self.accessToken}`,
110
+ "Content-Type": "application/json",
111
+ },
112
+ body: JSON.stringify({
113
+ messaging_product: "whatsapp",
114
+ recipient_type: "individual",
115
+ to: recipient,
116
+ type: "text",
117
+ text: {
118
+ preview_url: (args.previewUrl as boolean) ?? false,
119
+ body: args.text as string,
120
+ },
121
+ }),
122
+ });
123
+
124
+ if (!res.ok) {
125
+ const err = await res.text();
126
+ throw new Error(
127
+ `WhatsApp send failed: ${res.status} ${err}`
128
+ );
129
+ }
130
+
131
+ const data = (await res.json()) as any;
132
+ const msgId = data.messages?.[0]?.id ?? "unknown";
133
+ return `Message sent successfully to ${recipient}. Message ID: ${msgId}`;
134
+ },
135
+ },
136
+ {
137
+ name: "whatsapp_send_template",
138
+ description:
139
+ "Send a template message to a WhatsApp user. Required for first-time outreach (24-hour messaging window).",
140
+ parameters: z.object({
141
+ templateName: z
142
+ .string()
143
+ .describe(
144
+ 'The pre-approved template name (e.g. "hello_world")'
145
+ ),
146
+ recipient: z
147
+ .string()
148
+ .optional()
149
+ .describe(
150
+ "Recipient WhatsApp number with country code. Uses default if omitted."
151
+ ),
152
+ languageCode: z
153
+ .string()
154
+ .optional()
155
+ .describe('Template language code (default "en_US")'),
156
+ components: z
157
+ .array(z.record(z.any()))
158
+ .optional()
159
+ .describe(
160
+ "Template components for dynamic content (header, body, button parameters)"
161
+ ),
162
+ }),
163
+ execute: async (
164
+ args: Record<string, unknown>,
165
+ _ctx: RunContext
166
+ ): Promise<string> => {
167
+ self.validate();
168
+ const recipient = self.resolveRecipient(args.recipient as string);
169
+ const langCode = (args.languageCode as string) ?? "en_US";
170
+
171
+ const template: Record<string, unknown> = {
172
+ name: args.templateName as string,
173
+ language: { code: langCode },
174
+ };
175
+
176
+ if (args.components) {
177
+ template.components = args.components;
178
+ }
179
+
180
+ const res = await fetch(self.getBaseUrl(), {
181
+ method: "POST",
182
+ headers: {
183
+ Authorization: `Bearer ${self.accessToken}`,
184
+ "Content-Type": "application/json",
185
+ },
186
+ body: JSON.stringify({
187
+ messaging_product: "whatsapp",
188
+ recipient_type: "individual",
189
+ to: recipient,
190
+ type: "template",
191
+ template,
192
+ }),
193
+ });
194
+
195
+ if (!res.ok) {
196
+ const err = await res.text();
197
+ throw new Error(
198
+ `WhatsApp template send failed: ${res.status} ${err}`
199
+ );
200
+ }
201
+
202
+ const data = (await res.json()) as any;
203
+ const msgId = data.messages?.[0]?.id ?? "unknown";
204
+ return `Template message "${args.templateName}" sent to ${recipient}. Message ID: ${msgId}`;
205
+ },
206
+ },
207
+ ];
208
+ }
209
+ }