@elizaos/plugin-web-search 2.0.0-beta.1 → 2.0.3-beta.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shaw Walters and elizaOS Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,247 +1,116 @@
1
1
  # @elizaos/plugin-web-search
2
2
 
3
- A plugin for powerful web search capabilities, providing efficient search query handling and result processing through a customizable API interface.
3
+ Adds live web search to an Eliza agent via the [Tavily](https://tavily.com/) API.
4
4
 
5
- ## Overview
5
+ ## What it does
6
6
 
7
- This plugin provides functionality to:
7
+ Installing this plugin registers a `WebSearchService` (`ServiceType.WEB_SEARCH`) that any other plugin or action can call to search the web. It also registers the `"web"` search category so the elizaOS core search-dispatch layer routes web queries to this service automatically.
8
8
 
9
- - Execute web search queries with customizable parameters
10
- - Process and format search results
11
- - Handle search API authentication
12
- - Manage token limits and response sizes
13
- - Optimize query performance
9
+ Capabilities exposed through the service:
14
10
 
15
- ## Installation
11
+ - **General web search** — ranked results with optional AI-generated answer.
12
+ - **News search** — topic-filtered results with freshness control (day / week / month).
13
+ - **Image search** — includes image URLs in results.
14
+ - **Video search** — delegates to general search (Tavily has no separate video endpoint).
15
+ - **Page info** — fetches a URL and extracts title, description, and raw HTML content.
16
16
 
17
- ```bash
18
- bun install @elizaos/plugin-web-search
19
- ```
17
+ No actions are registered by the plugin itself. Other plugins that rely on web search call `runtime.getService(ServiceType.WEB_SEARCH)` and invoke the service directly.
20
18
 
21
- ## Configuration
19
+ ## Installation
22
20
 
23
- The plugin requires the following environment variables:
21
+ Add the package to your agent:
24
22
 
25
- ```env
26
- TAVILY_API_KEY=your_api_key # Required: API key for search service
23
+ ```bash
24
+ bun add @elizaos/plugin-web-search
27
25
  ```
28
26
 
29
- ## Usage
30
-
31
- Import and register the plugin in your Eliza configuration.
27
+ Then include it in your character config:
32
28
 
33
29
  ```typescript
34
30
  import { webSearchPlugin } from "@elizaos/plugin-web-search";
35
31
 
36
32
  export default {
37
33
  plugins: [webSearchPlugin],
38
- // ... other configuration
34
+ // ...
39
35
  };
40
36
  ```
41
37
 
42
- **Custom Usage**
43
- If you want custom usage, for example, a social media client to search the web before posting, you can also import the webSearchService and use it directly. Here's how you can do it:
44
-
45
- ```typescript
46
- // Example usage in a social media client
47
- const webSearchService = new WebSearchService();
48
- await webSearchService.initialize(runtime);
49
- const latestNews = await webSearchService.search(
50
- "latest news on AI Agents",
51
- // searchOptions
52
- );
53
-
54
- const state = await this.runtime.composeState(
55
- { } // memory,
56
- { // additional keys
57
- latestNews: latestNews,
58
- }
59
- );
60
-
61
- // Then modify the post template to include the {{latestNews}} and however you need
62
- ```
63
-
64
- ## Features
38
+ ## Configuration
65
39
 
66
- ### Web Search
40
+ | Environment variable | Required | Description |
41
+ |---------------------|----------|-------------|
42
+ | `TAVILY_API_KEY` | Yes | API key from [app.tavily.com](https://app.tavily.com). Without it the service starts in a degraded (inert) state and throws a descriptive error on first use. |
67
43
 
68
- The plugin provides comprehensive web search capabilities:
44
+ Set the key in your environment or agent settings:
69
45
 
70
- ```typescript
71
- import { webSearch } from "@elizaos/plugin-web-search";
72
-
73
- // Execute a search query
74
- const result = await webSearch.handler(
75
- runtime,
76
- {
77
- content: { text: "What is the latest news about AI?" },
78
- },
79
- state,
80
- {},
81
- callback
82
- );
46
+ ```env
47
+ TAVILY_API_KEY=tvly-...
83
48
  ```
84
49
 
85
- ### Token Management
50
+ ## Calling the service from another plugin
86
51
 
87
52
  ```typescript
88
- // The plugin automatically handles token limits
89
- const DEFAULT_MAX_WEB_SEARCH_TOKENS = 4000;
53
+ import { ServiceType } from "@elizaos/core";
54
+ import type { IWebSearchService } from "@elizaos/core";
90
55
 
91
- // Example of token-limited response
92
- const response = MaxTokens(searchResult, DEFAULT_MAX_WEB_SEARCH_TOKENS);
93
- ```
56
+ const svc = runtime.getService<IWebSearchService>(ServiceType.WEB_SEARCH);
94
57
 
95
- ## Development
96
-
97
- ### Building
98
-
99
- ```bash
100
- bun run build
101
- ```
58
+ // General search
59
+ const result = await svc.search("latest developments in open-source LLMs", {
60
+ limit: 5,
61
+ searchDepth: "advanced",
62
+ includeAnswer: true,
63
+ });
102
64
 
103
- ### Testing
65
+ // News search
66
+ const news = await svc.searchNews("AI regulation", { freshness: "week" });
104
67
 
105
- ```bash
106
- bun run test
68
+ // Image search (always returns image results; no flag needed)
69
+ const images = await svc.searchImages("northern lights", { limit: 10 });
107
70
  ```
108
71
 
109
- ### Development Mode
110
-
111
- ```bash
112
- bun run dev
113
- ```
114
-
115
- ## Dependencies
116
-
117
- - `@elizaos/core`: Core Eliza functionality
118
- - `js-tiktoken`: Token counting and management
119
- - `tsup`: Build tool
120
- - Other standard dependencies listed in package.json
121
-
122
- ## API Reference
123
-
124
- ### Core Interfaces
72
+ `SearchResponse` shape:
125
73
 
126
74
  ```typescript
127
- interface Action {
128
- name: "WEB_SEARCH";
129
- similes: string[];
130
- description: string;
131
- validate: (runtime: IAgentRuntime, message: Memory) => Promise<boolean>;
132
- handler: (
133
- runtime: IAgentRuntime,
134
- message: Memory,
135
- state: State,
136
- options: any,
137
- callback: HandlerCallback
138
- ) => Promise<void>;
139
- examples: Array<Array<any>>;
140
- }
141
-
142
- interface SearchResult {
143
- title: string;
144
- url: string;
145
- answer?: string;
146
- results?: Array<{
75
+ {
76
+ query: string;
77
+ answer?: string; // AI-generated summary (when includeAnswer is true)
78
+ responseTime?: number;
79
+ results: Array<{
147
80
  title: string;
148
81
  url: string;
82
+ description: string;
83
+ content: string;
84
+ rawContent?: string;
85
+ score: number;
86
+ publishedDate?: Date;
149
87
  }>;
88
+ images: Array<{ url: string; description?: string }>;
150
89
  }
151
90
  ```
152
91
 
153
- ### Plugin Methods
154
-
155
- - `webSearch.handler`: Main method for executing searches
156
- - `generateWebSearch`: Core search generation function
157
- - `MaxTokens`: Token limit management function
158
- - `getTotalTokensFromString`: Token counting utility
159
-
160
- ## Common Issues/Troubleshooting
161
-
162
- ### Issue: API Authentication Failures
163
-
164
- - **Cause**: Invalid or missing Tavily API key
165
- - **Solution**: Verify TAVILY_API_KEY environment variable
166
-
167
- ### Issue: Token Limit Exceeded
168
-
169
- - **Cause**: Search results exceeding maximum token limit
170
- - **Solution**: Results are automatically truncated to fit within limits
171
-
172
- ### Issue: Search Rate Limiting
173
-
174
- - **Cause**: Too many requests in short time
175
- - **Solution**: Implement proper request throttling
176
-
177
- ## Security Best Practices
178
-
179
- - Store API keys securely using environment variables
180
- - Validate all search inputs
181
- - Implement proper error handling
182
- - Keep dependencies updated
183
- - Monitor API usage and rate limits
184
- - Use HTTPS for API communication
185
-
186
- ## Example Usage
187
-
188
- ```typescript
189
- // Basic search
190
- const searchQuery = "Latest developments in quantum computing";
191
- const results = await generateWebSearch(searchQuery, runtime);
192
-
193
- // With formatted response
194
- if (results && results.results.length) {
195
- const formattedResponse = `${results.answer}\n\nFor more details, check out:\n${results.results
196
- .map(
197
- (result, index) => `${index + 1}. [${result.title}](${result.url})`
198
- )
199
- .join("\n")}`;
200
- }
201
- ```
92
+ ## Search options
202
93
 
203
- ## Configuration Options
94
+ | Option | Type | Default | Description |
95
+ |--------|------|---------|-------------|
96
+ | `limit` | number | 3 | Maximum number of results |
97
+ | `topic` / `type` | `"general"` \| `"news"` | `"general"` | Tavily topic filter |
98
+ | `searchDepth` | `"basic"` \| `"advanced"` | `"basic"` | Tavily crawl depth |
99
+ | `includeAnswer` | boolean | true | Request an AI-generated answer |
100
+ | `includeImages` | boolean | false | Include image results |
101
+ | `days` | number | 3 | Freshness window in days (news searches) |
204
102
 
205
- ### Token Management
103
+ ## Development
206
104
 
207
- ```typescript
208
- const DEFAULT_MODEL_ENCODING = "gpt-3.5-turbo";
209
- const DEFAULT_MAX_WEB_SEARCH_TOKENS = 4000;
105
+ ```bash
106
+ bun run --cwd plugins/plugin-web-search build # compile
107
+ bun run --cwd plugins/plugin-web-search dev # watch mode
108
+ bun run --cwd plugins/plugin-web-search lint # biome check
109
+ bun run --cwd plugins/plugin-web-search typecheck # type-check only
210
110
  ```
211
111
 
212
- ### Search Actions
213
-
214
- The plugin includes multiple search action similes:
215
-
216
- - SEARCH_WEB
217
- - INTERNET_SEARCH
218
- - LOOKUP
219
- - QUERY_WEB
220
- - FIND_ONLINE
221
- - And more...
222
-
223
- ## Contributing
224
-
225
- Contributions are welcome! Please see the [CONTRIBUTING.md](CONTRIBUTING.md) file for more information.
226
-
227
- ## Credits
228
-
229
- This plugin integrates with and builds upon several key technologies:
230
-
231
- - [Tavily API](https://tavily.com/): Advanced search and content analysis API
232
- - [js-tiktoken](https://github.com/dqbd/tiktoken): Token counting for API responses
233
- - [Zod](https://github.com/colinhacks/zod): TypeScript-first schema validation
234
-
235
- Special thanks to:
236
-
237
- - The Eliza community for their contributions and feedback
238
-
239
- For more information about the search capabilities and tools:
240
-
241
- - [Tavily API Documentation](https://docs.tavily.com/)
242
- - [Token Management Guide](https://github.com/dqbd/tiktoken#readme)
243
- - [Search API Best Practices](https://docs.tavily.com/docs/guides/best-practices)
112
+ ## Dependencies
244
113
 
245
- ## License
114
+ - [`@elizaos/core`](https://github.com/elizaOS/eliza) — elizaOS runtime interfaces
115
+ - [`@tavily/core`](https://www.npmjs.com/package/@tavily/core) — Tavily search client
246
116
 
247
- This plugin is part of the Eliza project. See the main project repository for license information.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elizaos/plugin-web-search",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.3-beta.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -8,32 +8,50 @@
8
8
  "exports": {
9
9
  "./package.json": "./package.json",
10
10
  ".": {
11
+ "eliza-source": {
12
+ "types": "./src/index.ts",
13
+ "import": "./src/index.ts",
14
+ "default": "./src/index.ts"
15
+ },
11
16
  "import": {
12
17
  "types": "./dist/index.d.ts",
13
18
  "default": "./dist/index.js"
14
19
  }
20
+ },
21
+ "./*.css": "./dist/*.css",
22
+ "./*": {
23
+ "types": "./dist/*.d.ts",
24
+ "eliza-source": {
25
+ "types": "./src/*.ts",
26
+ "import": "./src/*.ts",
27
+ "default": "./src/*.ts"
28
+ },
29
+ "import": "./dist/*.js",
30
+ "default": "./dist/*.js"
15
31
  }
16
32
  },
17
33
  "files": [
18
34
  "dist"
19
35
  ],
20
36
  "dependencies": {
21
- "@elizaos/core": "2.0.0-beta.1",
22
- "@tavily/core": "^0.7.0",
23
- "js-tiktoken": "1.0.21",
24
- "tsup": "^8.5.1"
37
+ "@elizaos/core": "2.0.3-beta.5",
38
+ "@tavily/core": "^0.7.0"
25
39
  },
26
40
  "devDependencies": {
27
- "@biomejs/biome": "2.4.14"
41
+ "@biomejs/biome": "2.5.0",
42
+ "tsup": "^8.5.1",
43
+ "typescript": "^6.0.3",
44
+ "vitest": "^4.1.7"
28
45
  },
29
46
  "scripts": {
47
+ "test": "vitest run --config ./vitest.config.ts",
30
48
  "build": "tsup --format esm --dts",
31
49
  "dev": "tsup --format esm --dts --watch",
32
- "lint": "biome check src/",
33
- "lint:fix": "biome check --write src/",
34
- "format": "biome format src/",
35
- "format:fix": "biome format --write src/",
36
- "typecheck": "tsc --noEmit -p tsconfig.json"
50
+ "lint": "bunx @biomejs/biome check src/",
51
+ "lint:fix": "bunx @biomejs/biome check --write src/",
52
+ "format": "bunx @biomejs/biome format src/",
53
+ "format:fix": "bunx @biomejs/biome format --write src/",
54
+ "typecheck": "tsgo --noEmit -p tsconfig.json"
37
55
  },
38
56
  "agentConfig": {
39
57
  "pluginType": "elizaos:client:1.0.0",
@@ -43,5 +61,9 @@
43
61
  "description": "Tavily API key for accessing news services"
44
62
  }
45
63
  }
46
- }
64
+ },
65
+ "publishConfig": {
66
+ "access": "public"
67
+ },
68
+ "gitHead": "ff6157011c9459670021cc28a6797592a78b8817"
47
69
  }
package/dist/index.d.ts DELETED
@@ -1,7 +0,0 @@
1
- import { SearchCategoryRegistration, Plugin, IAgentRuntime } from '@elizaos/core';
2
-
3
- declare const WEB_SEARCH_CATEGORY: SearchCategoryRegistration;
4
- declare function registerWebSearchCategory(runtime: IAgentRuntime): void;
5
- declare const webSearchPlugin: Plugin;
6
-
7
- export { WEB_SEARCH_CATEGORY, webSearchPlugin as default, registerWebSearchCategory, webSearchPlugin };
package/dist/index.js DELETED
@@ -1,197 +0,0 @@
1
- // src/index.ts
2
- import { ServiceType as ServiceType2 } from "@elizaos/core";
3
-
4
- // src/services/webSearchService.ts
5
- import { IWebSearchService, logger, ServiceType } from "@elizaos/core";
6
- import { tavily } from "@tavily/core";
7
- function parsePublishedDate(value) {
8
- if (!value) return void 0;
9
- const date = new Date(value);
10
- return Number.isNaN(date.getTime()) ? void 0 : date;
11
- }
12
- function normalizeResponse(query, response) {
13
- const results = (response.results ?? []).map((result) => {
14
- const content = result.content ?? "";
15
- return {
16
- title: result.title ?? "Untitled",
17
- url: result.url ?? "",
18
- description: content,
19
- content,
20
- rawContent: result.rawContent,
21
- score: typeof result.score === "number" ? result.score : 0,
22
- publishedDate: parsePublishedDate(result.publishedDate)
23
- };
24
- });
25
- const images = (response.images ?? []).map(
26
- (image) => typeof image === "string" ? { url: image } : { url: image.url ?? "", description: image.description }
27
- ).filter((image) => image.url);
28
- return {
29
- answer: response.answer,
30
- query: response.query ?? query,
31
- responseTime: response.responseTime,
32
- images,
33
- results
34
- };
35
- }
36
- function freshnessToDays(freshness) {
37
- switch (freshness) {
38
- case "day":
39
- return 1;
40
- case "week":
41
- return 7;
42
- case "month":
43
- return 30;
44
- default:
45
- return 3;
46
- }
47
- }
48
- var WebSearchService = class _WebSearchService extends IWebSearchService {
49
- static serviceType = ServiceType.WEB_SEARCH;
50
- capabilityDescription = "Web search and content discovery capabilities";
51
- tavilyClient;
52
- static async start(runtime) {
53
- const service = new _WebSearchService(runtime);
54
- await service.initialize(runtime);
55
- return service;
56
- }
57
- async stop() {
58
- }
59
- async initialize(runtime) {
60
- const apiKey = runtime.getSetting("TAVILY_API_KEY");
61
- if (typeof apiKey !== "string" || apiKey.length === 0) {
62
- throw new Error("TAVILY_API_KEY is not set");
63
- }
64
- this.tavilyClient = tavily({ apiKey });
65
- }
66
- async search(query, options) {
67
- try {
68
- const response = await this.tavilyClient.search(query, {
69
- includeAnswer: (options == null ? void 0 : options.includeAnswer) ?? true,
70
- maxResults: (options == null ? void 0 : options.limit) ?? 3,
71
- topic: (options == null ? void 0 : options.topic) ?? (options == null ? void 0 : options.type) ?? "general",
72
- searchDepth: (options == null ? void 0 : options.searchDepth) ?? "basic",
73
- includeImages: (options == null ? void 0 : options.includeImages) ?? false,
74
- days: (options == null ? void 0 : options.days) ?? 3
75
- });
76
- return normalizeResponse(query, response);
77
- } catch (cause) {
78
- const err = cause instanceof Error ? cause : new Error(String(cause));
79
- logger.error({ src: "plugin-web-search", err }, "Web search error");
80
- throw err;
81
- }
82
- }
83
- async searchNews(query, options) {
84
- return this.search(query, {
85
- ...options,
86
- type: "news",
87
- topic: "news",
88
- days: freshnessToDays(options == null ? void 0 : options.freshness)
89
- });
90
- }
91
- async searchImages(query, options) {
92
- return this.search(query, {
93
- limit: options == null ? void 0 : options.limit,
94
- offset: options == null ? void 0 : options.offset,
95
- language: options == null ? void 0 : options.language,
96
- region: options == null ? void 0 : options.region,
97
- dateRange: options == null ? void 0 : options.dateRange,
98
- fileType: options == null ? void 0 : options.fileType,
99
- site: options == null ? void 0 : options.site,
100
- sortBy: options == null ? void 0 : options.sortBy,
101
- safeSearch: options == null ? void 0 : options.safeSearch,
102
- includeImages: true
103
- });
104
- }
105
- async searchVideos(query, options) {
106
- return this.search(query, options);
107
- }
108
- async getSuggestions(_query) {
109
- return [];
110
- }
111
- async getTrendingSearches(_region) {
112
- return [];
113
- }
114
- async getPageInfo(url) {
115
- var _a, _b;
116
- const response = await fetch(url);
117
- const content = await response.text();
118
- const title = ((_a = content.match(/<title[^>]*>(.*?)<\/title>/i)) == null ? void 0 : _a[1]) ?? url;
119
- const description = ((_b = content.match(/<meta\s+name=["']description["']\s+content=["']([^"']+)/i)) == null ? void 0 : _b[1]) ?? "";
120
- return {
121
- title,
122
- description,
123
- content,
124
- metadata: {},
125
- images: [],
126
- links: []
127
- };
128
- }
129
- };
130
-
131
- // src/index.ts
132
- var WEB_SEARCH_CATEGORY = {
133
- category: "web",
134
- label: "Web",
135
- description: "Search current web pages through plugin-web-search.",
136
- contexts: ["knowledge", "browser"],
137
- filters: [
138
- {
139
- name: "topic",
140
- label: "Topic",
141
- description: "Tavily search topic.",
142
- type: "enum",
143
- options: [
144
- { label: "General", value: "general" },
145
- { label: "News", value: "news" }
146
- ]
147
- },
148
- {
149
- name: "searchDepth",
150
- label: "Search depth",
151
- description: "Tavily search depth.",
152
- type: "enum",
153
- options: [
154
- { label: "Basic", value: "basic" },
155
- { label: "Advanced", value: "advanced" }
156
- ]
157
- },
158
- {
159
- name: "includeImages",
160
- label: "Include images",
161
- description: "Include image results when available.",
162
- type: "boolean"
163
- }
164
- ],
165
- resultSchemaSummary: "SearchResponse with query, answer, results containing title/url/description/content/score, and optional images.",
166
- capabilities: ["web", "news", "current-information"],
167
- source: "plugin-web-search",
168
- serviceType: ServiceType2.WEB_SEARCH
169
- };
170
- function registerWebSearchCategory(runtime) {
171
- try {
172
- runtime.getSearchCategory(WEB_SEARCH_CATEGORY.category, {
173
- includeDisabled: true
174
- });
175
- return;
176
- } catch {
177
- runtime.registerSearchCategory(WEB_SEARCH_CATEGORY);
178
- }
179
- }
180
- var webSearchPlugin = {
181
- name: "webSearch",
182
- description: "Search the web and get news",
183
- init: async (_config, runtime) => {
184
- registerWebSearchCategory(runtime);
185
- },
186
- actions: [],
187
- providers: [],
188
- services: [WebSearchService]
189
- };
190
- var index_default = webSearchPlugin;
191
- export {
192
- WEB_SEARCH_CATEGORY,
193
- index_default as default,
194
- registerWebSearchCategory,
195
- webSearchPlugin
196
- };
197
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts","../src/services/webSearchService.ts"],"sourcesContent":["import type { IAgentRuntime, Plugin, SearchCategoryRegistration } from \"@elizaos/core\";\nimport { ServiceType } from \"@elizaos/core\";\n\nimport { WebSearchService } from \"./services/webSearchService\";\n\nexport const WEB_SEARCH_CATEGORY: SearchCategoryRegistration = {\n category: \"web\",\n label: \"Web\",\n description: \"Search current web pages through plugin-web-search.\",\n contexts: [\"knowledge\", \"browser\"],\n filters: [\n {\n name: \"topic\",\n label: \"Topic\",\n description: \"Tavily search topic.\",\n type: \"enum\",\n options: [\n { label: \"General\", value: \"general\" },\n { label: \"News\", value: \"news\" },\n ],\n },\n {\n name: \"searchDepth\",\n label: \"Search depth\",\n description: \"Tavily search depth.\",\n type: \"enum\",\n options: [\n { label: \"Basic\", value: \"basic\" },\n { label: \"Advanced\", value: \"advanced\" },\n ],\n },\n {\n name: \"includeImages\",\n label: \"Include images\",\n description: \"Include image results when available.\",\n type: \"boolean\",\n },\n ],\n resultSchemaSummary:\n \"SearchResponse with query, answer, results containing title/url/description/content/score, and optional images.\",\n capabilities: [\"web\", \"news\", \"current-information\"],\n source: \"plugin-web-search\",\n serviceType: ServiceType.WEB_SEARCH,\n};\n\nexport function registerWebSearchCategory(runtime: IAgentRuntime): void {\n try {\n runtime.getSearchCategory(WEB_SEARCH_CATEGORY.category, {\n includeDisabled: true,\n });\n return;\n } catch {\n runtime.registerSearchCategory(WEB_SEARCH_CATEGORY);\n }\n}\n\nexport const webSearchPlugin: Plugin = {\n name: \"webSearch\",\n description: \"Search the web and get news\",\n init: async (_config, runtime) => {\n registerWebSearchCategory(runtime);\n },\n actions: [],\n providers: [],\n services: [WebSearchService],\n};\n\nexport default webSearchPlugin;\n","import { type IAgentRuntime, IWebSearchService, logger, ServiceType } from \"@elizaos/core\";\nimport { tavily } from \"@tavily/core\";\n\nimport type {\n ImageSearchOptions,\n NewsSearchOptions,\n SearchOptions,\n SearchResponse,\n VideoSearchOptions,\n} from \"../types\";\n\nexport type TavilyClient = ReturnType<typeof tavily>;\n\ntype TavilySearchResult = {\n title?: string;\n url?: string;\n content?: string;\n rawContent?: string;\n score?: number;\n publishedDate?: string;\n};\n\ntype TavilySearchResponse = {\n answer?: string;\n query?: string;\n responseTime?: number;\n images?: Array<{ url?: string; description?: string } | string>;\n results?: TavilySearchResult[];\n};\n\nfunction parsePublishedDate(value: string | undefined): Date | undefined {\n if (!value) return undefined;\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? undefined : date;\n}\n\nfunction normalizeResponse(query: string, response: TavilySearchResponse): SearchResponse {\n const results = (response.results ?? []).map((result) => {\n const content = result.content ?? \"\";\n return {\n title: result.title ?? \"Untitled\",\n url: result.url ?? \"\",\n description: content,\n content,\n rawContent: result.rawContent,\n score: typeof result.score === \"number\" ? result.score : 0,\n publishedDate: parsePublishedDate(result.publishedDate),\n };\n });\n const images = (response.images ?? [])\n .map((image) =>\n typeof image === \"string\"\n ? { url: image }\n : { url: image.url ?? \"\", description: image.description }\n )\n .filter((image) => image.url);\n\n return {\n answer: response.answer,\n query: response.query ?? query,\n responseTime: response.responseTime,\n images,\n results,\n };\n}\n\nfunction freshnessToDays(freshness: NewsSearchOptions[\"freshness\"]): number {\n switch (freshness) {\n case \"day\":\n return 1;\n case \"week\":\n return 7;\n case \"month\":\n return 30;\n default:\n return 3;\n }\n}\n\nexport class WebSearchService extends IWebSearchService {\n static override serviceType = ServiceType.WEB_SEARCH;\n override capabilityDescription = \"Web search and content discovery capabilities\" as const;\n\n tavilyClient!: TavilyClient;\n\n static override async start(runtime: IAgentRuntime): Promise<WebSearchService> {\n const service = new WebSearchService(runtime);\n await service.initialize(runtime);\n return service;\n }\n\n async stop(): Promise<void> {\n // Tavily client is stateless HTTP; nothing to tear down.\n }\n\n private async initialize(runtime: IAgentRuntime): Promise<void> {\n const apiKey = runtime.getSetting(\"TAVILY_API_KEY\");\n if (typeof apiKey !== \"string\" || apiKey.length === 0) {\n throw new Error(\"TAVILY_API_KEY is not set\");\n }\n this.tavilyClient = tavily({ apiKey });\n }\n\n async search(query: string, options?: SearchOptions): Promise<SearchResponse> {\n try {\n const response = await this.tavilyClient.search(query, {\n includeAnswer: options?.includeAnswer ?? true,\n maxResults: options?.limit ?? 3,\n topic: options?.topic ?? options?.type ?? \"general\",\n searchDepth: options?.searchDepth ?? \"basic\",\n includeImages: options?.includeImages ?? false,\n days: options?.days ?? 3,\n });\n\n return normalizeResponse(query, response as TavilySearchResponse);\n } catch (cause) {\n const err = cause instanceof Error ? cause : new Error(String(cause));\n logger.error({ src: \"plugin-web-search\", err }, \"Web search error\");\n throw err;\n }\n }\n\n async searchNews(query: string, options?: NewsSearchOptions): Promise<SearchResponse> {\n return this.search(query, {\n ...options,\n type: \"news\",\n topic: \"news\",\n days: freshnessToDays(options?.freshness),\n });\n }\n\n async searchImages(query: string, options?: ImageSearchOptions): Promise<SearchResponse> {\n return this.search(query, {\n limit: options?.limit,\n offset: options?.offset,\n language: options?.language,\n region: options?.region,\n dateRange: options?.dateRange,\n fileType: options?.fileType,\n site: options?.site,\n sortBy: options?.sortBy,\n safeSearch: options?.safeSearch,\n includeImages: true,\n });\n }\n\n async searchVideos(query: string, options?: VideoSearchOptions): Promise<SearchResponse> {\n return this.search(query, options);\n }\n\n async getSuggestions(_query: string): Promise<string[]> {\n return [];\n }\n\n async getTrendingSearches(_region?: string): Promise<string[]> {\n return [];\n }\n\n async getPageInfo(url: string): Promise<{\n title: string;\n description: string;\n content: string;\n metadata: Record<string, string>;\n images: string[];\n links: string[];\n }> {\n const response = await fetch(url);\n const content = await response.text();\n const title = content.match(/<title[^>]*>(.*?)<\\/title>/i)?.[1] ?? url;\n const description =\n content.match(/<meta\\s+name=[\"']description[\"']\\s+content=[\"']([^\"']+)/i)?.[1] ?? \"\";\n return {\n title,\n description,\n content,\n metadata: {},\n images: [],\n links: [],\n };\n }\n}\n"],"mappings":"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","names":["ServiceType","ServiceType"]}