deepagentsdk 0.9.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/LICENSE +21 -0
- package/README.md +159 -0
- package/package.json +95 -0
- package/src/agent.ts +1230 -0
- package/src/backends/composite.ts +273 -0
- package/src/backends/filesystem.ts +692 -0
- package/src/backends/index.ts +22 -0
- package/src/backends/local-sandbox.ts +175 -0
- package/src/backends/persistent.ts +593 -0
- package/src/backends/sandbox.ts +510 -0
- package/src/backends/state.ts +244 -0
- package/src/backends/utils.ts +287 -0
- package/src/checkpointer/file-saver.ts +98 -0
- package/src/checkpointer/index.ts +5 -0
- package/src/checkpointer/kv-saver.ts +82 -0
- package/src/checkpointer/memory-saver.ts +82 -0
- package/src/checkpointer/types.ts +125 -0
- package/src/cli/components/ApiKeyInput.tsx +300 -0
- package/src/cli/components/FilePreview.tsx +237 -0
- package/src/cli/components/Input.tsx +277 -0
- package/src/cli/components/Message.tsx +93 -0
- package/src/cli/components/ModelSelection.tsx +338 -0
- package/src/cli/components/SlashMenu.tsx +101 -0
- package/src/cli/components/StatusBar.tsx +89 -0
- package/src/cli/components/Subagent.tsx +91 -0
- package/src/cli/components/TodoList.tsx +133 -0
- package/src/cli/components/ToolApproval.tsx +70 -0
- package/src/cli/components/ToolCall.tsx +144 -0
- package/src/cli/components/ToolCallSummary.tsx +175 -0
- package/src/cli/components/Welcome.tsx +75 -0
- package/src/cli/components/index.ts +24 -0
- package/src/cli/hooks/index.ts +12 -0
- package/src/cli/hooks/useAgent.ts +933 -0
- package/src/cli/index.tsx +1066 -0
- package/src/cli/theme.ts +205 -0
- package/src/cli/utils/model-list.ts +365 -0
- package/src/constants/errors.ts +29 -0
- package/src/constants/limits.ts +195 -0
- package/src/index.ts +176 -0
- package/src/middleware/agent-memory.ts +330 -0
- package/src/prompts.ts +196 -0
- package/src/skills/index.ts +2 -0
- package/src/skills/load.ts +191 -0
- package/src/skills/types.ts +53 -0
- package/src/tools/execute.ts +167 -0
- package/src/tools/filesystem.ts +418 -0
- package/src/tools/index.ts +39 -0
- package/src/tools/subagent.ts +443 -0
- package/src/tools/todos.ts +101 -0
- package/src/tools/web.ts +567 -0
- package/src/types/backend.ts +177 -0
- package/src/types/core.ts +220 -0
- package/src/types/events.ts +429 -0
- package/src/types/index.ts +94 -0
- package/src/types/structured-output.ts +43 -0
- package/src/types/subagent.ts +96 -0
- package/src/types.ts +22 -0
- package/src/utils/approval.ts +213 -0
- package/src/utils/events.ts +416 -0
- package/src/utils/eviction.ts +181 -0
- package/src/utils/index.ts +34 -0
- package/src/utils/model-parser.ts +38 -0
- package/src/utils/patch-tool-calls.ts +233 -0
- package/src/utils/project-detection.ts +32 -0
- package/src/utils/summarization.ts +254 -0
package/src/tools/web.ts
ADDED
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web tools for search and HTTP requests.
|
|
3
|
+
* Based on LangChain DeepAgents implementation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { tool } from "ai";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { tavily } from "@tavily/core";
|
|
9
|
+
import TurndownService from "turndown";
|
|
10
|
+
import { Readability } from "@mozilla/readability";
|
|
11
|
+
import { JSDOM } from "jsdom";
|
|
12
|
+
import type {
|
|
13
|
+
BackendProtocol,
|
|
14
|
+
BackendFactory,
|
|
15
|
+
DeepAgentState,
|
|
16
|
+
EventCallback,
|
|
17
|
+
} from "../types";
|
|
18
|
+
import { evictToolResult } from "../utils/eviction";
|
|
19
|
+
import {
|
|
20
|
+
WEB_SEARCH_ERROR,
|
|
21
|
+
REQUEST_TIMEOUT,
|
|
22
|
+
} from "../constants/errors";
|
|
23
|
+
import { DEFAULT_TIMEOUT_SECONDS } from "../constants/limits";
|
|
24
|
+
import {
|
|
25
|
+
createWebSearchStartEvent,
|
|
26
|
+
createWebSearchFinishEvent,
|
|
27
|
+
createHttpRequestStartEvent,
|
|
28
|
+
createHttpRequestFinishEvent,
|
|
29
|
+
createFetchUrlStartEvent,
|
|
30
|
+
createFetchUrlFinishEvent,
|
|
31
|
+
} from "../utils/events";
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Helper Functions
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Helper to resolve backend from factory or instance.
|
|
39
|
+
*/
|
|
40
|
+
function getBackend(
|
|
41
|
+
backend: BackendProtocol | BackendFactory | undefined,
|
|
42
|
+
state: DeepAgentState
|
|
43
|
+
): BackendProtocol | null {
|
|
44
|
+
if (!backend) return null;
|
|
45
|
+
if (typeof backend === "function") {
|
|
46
|
+
return backend(state);
|
|
47
|
+
}
|
|
48
|
+
return backend;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// HTML to Markdown Utilities
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Convert HTML to Markdown with article extraction.
|
|
57
|
+
* Uses Mozilla Readability to extract main content, then converts to Markdown.
|
|
58
|
+
*/
|
|
59
|
+
function htmlToMarkdown(html: string, url: string): string {
|
|
60
|
+
try {
|
|
61
|
+
// Parse HTML with JSDOM
|
|
62
|
+
const dom = new JSDOM(html, { url });
|
|
63
|
+
|
|
64
|
+
// Extract article content with Readability
|
|
65
|
+
const reader = new Readability(dom.window.document);
|
|
66
|
+
const article = reader.parse();
|
|
67
|
+
|
|
68
|
+
if (!article) {
|
|
69
|
+
// If Readability fails, fall back to body content
|
|
70
|
+
const bodyContent = dom.window.document.body?.textContent || "";
|
|
71
|
+
return bodyContent.trim();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Convert extracted HTML to Markdown
|
|
75
|
+
const turndown = new TurndownService({
|
|
76
|
+
headingStyle: "atx",
|
|
77
|
+
codeBlockStyle: "fenced",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const markdown = turndown.turndown(article.content || "");
|
|
81
|
+
|
|
82
|
+
// Prepend title if available
|
|
83
|
+
if (article.title) {
|
|
84
|
+
return `# ${article.title}\n\n${markdown}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return markdown;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// On error, return error message
|
|
90
|
+
return `Error converting HTML to Markdown: ${error instanceof Error ? error.message : String(error)}`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Tool Implementations
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Tool description for web_search.
|
|
100
|
+
*/
|
|
101
|
+
const WEB_SEARCH_TOOL_DESCRIPTION = `Search the web using Tavily API for current information, news, and documentation.
|
|
102
|
+
|
|
103
|
+
Returns an array of search results with titles, URLs, relevant excerpts, and relevance scores.
|
|
104
|
+
|
|
105
|
+
IMPORTANT AGENT INSTRUCTIONS:
|
|
106
|
+
- You MUST synthesize information from search results into a coherent answer
|
|
107
|
+
- NEVER show raw JSON or result objects to the user
|
|
108
|
+
- Cite sources by including URLs in your response
|
|
109
|
+
- If search fails or returns no results, explain this clearly to the user`;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create the web_search tool.
|
|
113
|
+
*/
|
|
114
|
+
export function createWebSearchTool(
|
|
115
|
+
state: DeepAgentState,
|
|
116
|
+
options: {
|
|
117
|
+
backend?: BackendProtocol | BackendFactory;
|
|
118
|
+
onEvent?: EventCallback;
|
|
119
|
+
toolResultEvictionLimit?: number;
|
|
120
|
+
tavilyApiKey: string;
|
|
121
|
+
}
|
|
122
|
+
) {
|
|
123
|
+
const { backend, onEvent, toolResultEvictionLimit, tavilyApiKey } = options;
|
|
124
|
+
|
|
125
|
+
return tool({
|
|
126
|
+
description: WEB_SEARCH_TOOL_DESCRIPTION,
|
|
127
|
+
inputSchema: z.object({
|
|
128
|
+
query: z.string().describe(
|
|
129
|
+
"The search query (be specific and detailed for best results)"
|
|
130
|
+
),
|
|
131
|
+
max_results: z
|
|
132
|
+
.number()
|
|
133
|
+
.default(5)
|
|
134
|
+
.describe("Number of results to return (1-20)"),
|
|
135
|
+
topic: z
|
|
136
|
+
.enum(["general", "news", "finance"])
|
|
137
|
+
.default("general")
|
|
138
|
+
.describe("Search topic category"),
|
|
139
|
+
include_raw_content: z
|
|
140
|
+
.boolean()
|
|
141
|
+
.default(false)
|
|
142
|
+
.describe("Include full page content (warning: uses more tokens)"),
|
|
143
|
+
}),
|
|
144
|
+
execute: async ({ query, max_results, topic, include_raw_content }, { toolCallId }) => {
|
|
145
|
+
// Emit start event
|
|
146
|
+
if (onEvent) {
|
|
147
|
+
onEvent(createWebSearchStartEvent(query));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// Initialize Tavily client
|
|
152
|
+
const tvly = tavily({ apiKey: tavilyApiKey });
|
|
153
|
+
|
|
154
|
+
// Perform search
|
|
155
|
+
const response = await tvly.search(query, {
|
|
156
|
+
maxResults: max_results,
|
|
157
|
+
topic,
|
|
158
|
+
includeRawContent: include_raw_content ? "text" : false,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Format results
|
|
162
|
+
const results = response.results || [];
|
|
163
|
+
const formattedResults = results
|
|
164
|
+
.map(
|
|
165
|
+
(r: any, i: number) =>
|
|
166
|
+
`## Result ${i + 1}: ${r.title}\n` +
|
|
167
|
+
`URL: ${r.url}\n` +
|
|
168
|
+
`Score: ${r.score?.toFixed(2) || "N/A"}\n` +
|
|
169
|
+
`Content: ${r.content}\n`
|
|
170
|
+
)
|
|
171
|
+
.join("\n---\n\n");
|
|
172
|
+
|
|
173
|
+
const output = `Found ${results.length} results for query: "${query}"\n\n${formattedResults}`;
|
|
174
|
+
|
|
175
|
+
// Emit finish event
|
|
176
|
+
if (onEvent) {
|
|
177
|
+
onEvent(createWebSearchFinishEvent(query, results.length));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Evict if needed
|
|
181
|
+
if (toolResultEvictionLimit && toolResultEvictionLimit > 0 && backend) {
|
|
182
|
+
const resolvedBackend = getBackend(backend, state);
|
|
183
|
+
if (resolvedBackend) {
|
|
184
|
+
const evictResult = await evictToolResult({
|
|
185
|
+
result: output,
|
|
186
|
+
toolCallId: toolCallId || `web_search_${Date.now()}`,
|
|
187
|
+
toolName: "web_search",
|
|
188
|
+
backend: resolvedBackend,
|
|
189
|
+
tokenLimit: toolResultEvictionLimit,
|
|
190
|
+
});
|
|
191
|
+
return evictResult.content;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return output;
|
|
196
|
+
} catch (error: unknown) {
|
|
197
|
+
const err = error as Error;
|
|
198
|
+
const errorMessage = WEB_SEARCH_ERROR(err.message);
|
|
199
|
+
|
|
200
|
+
// Emit finish event with 0 results (error case)
|
|
201
|
+
if (onEvent) {
|
|
202
|
+
onEvent(createWebSearchFinishEvent(query, 0));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return errorMessage;
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Tool description for http_request.
|
|
213
|
+
*/
|
|
214
|
+
const HTTP_REQUEST_TOOL_DESCRIPTION = `Make HTTP requests to APIs and web services.
|
|
215
|
+
|
|
216
|
+
Supports GET, POST, PUT, DELETE, PATCH methods with custom headers, query parameters, and request bodies.
|
|
217
|
+
|
|
218
|
+
Returns structured response with status code, headers, and parsed content (JSON or text).`;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Create the http_request tool.
|
|
222
|
+
*/
|
|
223
|
+
export function createHttpRequestTool(
|
|
224
|
+
state: DeepAgentState,
|
|
225
|
+
options: {
|
|
226
|
+
backend?: BackendProtocol | BackendFactory;
|
|
227
|
+
onEvent?: EventCallback;
|
|
228
|
+
toolResultEvictionLimit?: number;
|
|
229
|
+
defaultTimeout: number;
|
|
230
|
+
}
|
|
231
|
+
) {
|
|
232
|
+
const { backend, onEvent, toolResultEvictionLimit, defaultTimeout } = options;
|
|
233
|
+
|
|
234
|
+
return tool({
|
|
235
|
+
description: HTTP_REQUEST_TOOL_DESCRIPTION,
|
|
236
|
+
inputSchema: z.object({
|
|
237
|
+
url: z.string().url().describe("Target URL (must be valid HTTP/HTTPS URL)"),
|
|
238
|
+
method: z
|
|
239
|
+
.enum(["GET", "POST", "PUT", "DELETE", "PATCH"])
|
|
240
|
+
.default("GET")
|
|
241
|
+
.describe("HTTP method"),
|
|
242
|
+
headers: z
|
|
243
|
+
.record(z.string())
|
|
244
|
+
.optional()
|
|
245
|
+
.describe("HTTP headers as key-value pairs"),
|
|
246
|
+
body: z
|
|
247
|
+
.union([z.string(), z.record(z.any())])
|
|
248
|
+
.optional()
|
|
249
|
+
.describe("Request body (string or JSON object)"),
|
|
250
|
+
params: z
|
|
251
|
+
.record(z.string())
|
|
252
|
+
.optional()
|
|
253
|
+
.describe("URL query parameters as key-value pairs"),
|
|
254
|
+
timeout: z
|
|
255
|
+
.number()
|
|
256
|
+
.default(defaultTimeout)
|
|
257
|
+
.describe("Request timeout in seconds"),
|
|
258
|
+
}),
|
|
259
|
+
execute: async ({ url, method, headers, body, params, timeout }, { toolCallId }) => {
|
|
260
|
+
// Emit start event
|
|
261
|
+
if (onEvent) {
|
|
262
|
+
onEvent(createHttpRequestStartEvent(url, method));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
// Build URL with query params
|
|
267
|
+
const urlObj = new URL(url);
|
|
268
|
+
if (params) {
|
|
269
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
270
|
+
urlObj.searchParams.append(key, value);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Build request options
|
|
275
|
+
const requestOptions: RequestInit = {
|
|
276
|
+
method,
|
|
277
|
+
headers: headers || {},
|
|
278
|
+
signal: AbortSignal.timeout(timeout * 1000),
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// Add body if provided
|
|
282
|
+
if (body) {
|
|
283
|
+
if (typeof body === "string") {
|
|
284
|
+
requestOptions.body = body;
|
|
285
|
+
} else {
|
|
286
|
+
requestOptions.body = JSON.stringify(body);
|
|
287
|
+
(requestOptions.headers as Record<string, string>)["Content-Type"] =
|
|
288
|
+
"application/json";
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Execute request
|
|
293
|
+
const response = await fetch(urlObj.toString(), requestOptions);
|
|
294
|
+
|
|
295
|
+
// Parse response
|
|
296
|
+
const contentType = response.headers.get("content-type") || "";
|
|
297
|
+
let content: any;
|
|
298
|
+
|
|
299
|
+
if (contentType.includes("application/json")) {
|
|
300
|
+
try {
|
|
301
|
+
content = await response.json();
|
|
302
|
+
} catch {
|
|
303
|
+
content = await response.text();
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
content = await response.text();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Format response
|
|
310
|
+
const formattedOutput =
|
|
311
|
+
`HTTP ${method} ${url}\n` +
|
|
312
|
+
`Status: ${response.status}\n` +
|
|
313
|
+
`Success: ${response.ok}\n` +
|
|
314
|
+
`Content:\n${typeof content === "string" ? content : JSON.stringify(content, null, 2)}`;
|
|
315
|
+
|
|
316
|
+
// Emit finish event
|
|
317
|
+
if (onEvent) {
|
|
318
|
+
onEvent(createHttpRequestFinishEvent(response.url, response.status));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Evict if needed
|
|
322
|
+
if (toolResultEvictionLimit && toolResultEvictionLimit > 0 && backend) {
|
|
323
|
+
const resolvedBackend = getBackend(backend, state);
|
|
324
|
+
if (resolvedBackend) {
|
|
325
|
+
const evictResult = await evictToolResult({
|
|
326
|
+
result: formattedOutput,
|
|
327
|
+
toolCallId: toolCallId || `http_request_${Date.now()}`,
|
|
328
|
+
toolName: "http_request",
|
|
329
|
+
backend: resolvedBackend,
|
|
330
|
+
tokenLimit: toolResultEvictionLimit,
|
|
331
|
+
});
|
|
332
|
+
return evictResult.content;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return formattedOutput;
|
|
337
|
+
} catch (error: unknown) {
|
|
338
|
+
const err = error as Error;
|
|
339
|
+
let errorMessage: string;
|
|
340
|
+
|
|
341
|
+
if (err.name === "TimeoutError" || err.name === "AbortError") {
|
|
342
|
+
errorMessage = REQUEST_TIMEOUT(timeout);
|
|
343
|
+
} else {
|
|
344
|
+
errorMessage = `HTTP request error: ${err.message}`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Emit finish event with error status
|
|
348
|
+
if (onEvent) {
|
|
349
|
+
onEvent(createHttpRequestFinishEvent(url, 0));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return errorMessage;
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Tool description for fetch_url.
|
|
360
|
+
*/
|
|
361
|
+
const FETCH_URL_TOOL_DESCRIPTION = `Fetch web page content and convert HTML to clean Markdown format.
|
|
362
|
+
|
|
363
|
+
Uses Mozilla Readability to extract main article content and Turndown to convert to Markdown.
|
|
364
|
+
|
|
365
|
+
Returns the page content as formatted Markdown, suitable for analysis and summarization.
|
|
366
|
+
|
|
367
|
+
IMPORTANT AGENT INSTRUCTIONS:
|
|
368
|
+
- Use this tool to read documentation, articles, and web pages
|
|
369
|
+
- The content is already cleaned and formatted as Markdown
|
|
370
|
+
- Cite the URL when referencing fetched content`;
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Create the fetch_url tool.
|
|
374
|
+
*/
|
|
375
|
+
export function createFetchUrlTool(
|
|
376
|
+
state: DeepAgentState,
|
|
377
|
+
options: {
|
|
378
|
+
backend?: BackendProtocol | BackendFactory;
|
|
379
|
+
onEvent?: EventCallback;
|
|
380
|
+
toolResultEvictionLimit?: number;
|
|
381
|
+
defaultTimeout: number;
|
|
382
|
+
}
|
|
383
|
+
) {
|
|
384
|
+
const { backend, onEvent, toolResultEvictionLimit, defaultTimeout } = options;
|
|
385
|
+
|
|
386
|
+
return tool({
|
|
387
|
+
description: FETCH_URL_TOOL_DESCRIPTION,
|
|
388
|
+
inputSchema: z.object({
|
|
389
|
+
url: z.string().url().describe("The URL to fetch (must be valid HTTP/HTTPS URL)"),
|
|
390
|
+
timeout: z
|
|
391
|
+
.number()
|
|
392
|
+
.default(defaultTimeout)
|
|
393
|
+
.describe("Request timeout in seconds"),
|
|
394
|
+
extract_article: z
|
|
395
|
+
.boolean()
|
|
396
|
+
.default(true)
|
|
397
|
+
.describe(
|
|
398
|
+
"Extract main article content using Readability (disable for non-article pages)"
|
|
399
|
+
),
|
|
400
|
+
}),
|
|
401
|
+
execute: async ({ url, timeout, extract_article }, { toolCallId }) => {
|
|
402
|
+
// Emit start event
|
|
403
|
+
if (onEvent) {
|
|
404
|
+
onEvent(createFetchUrlStartEvent(url));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
// Fetch HTML
|
|
409
|
+
const response = await fetch(url, {
|
|
410
|
+
signal: AbortSignal.timeout(timeout * 1000),
|
|
411
|
+
headers: {
|
|
412
|
+
"User-Agent": "Mozilla/5.0 (compatible; DeepAgents/1.0)",
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
if (!response.ok) {
|
|
417
|
+
const errorMsg = `HTTP error: ${response.status} ${response.statusText}`;
|
|
418
|
+
|
|
419
|
+
if (onEvent) {
|
|
420
|
+
onEvent(createFetchUrlFinishEvent(response.url, false));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return errorMsg;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const html = await response.text();
|
|
427
|
+
|
|
428
|
+
// Parse DOM
|
|
429
|
+
const dom = new JSDOM(html, { url });
|
|
430
|
+
|
|
431
|
+
let contentToConvert = html;
|
|
432
|
+
|
|
433
|
+
// Extract article content if requested
|
|
434
|
+
if (extract_article) {
|
|
435
|
+
try {
|
|
436
|
+
const reader = new Readability(dom.window.document);
|
|
437
|
+
const article = reader.parse();
|
|
438
|
+
|
|
439
|
+
if (article && article.content) {
|
|
440
|
+
contentToConvert = article.content;
|
|
441
|
+
}
|
|
442
|
+
} catch (readabilityError) {
|
|
443
|
+
// If Readability fails, fall back to full HTML
|
|
444
|
+
console.warn("Readability extraction failed, using full HTML");
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Convert to Markdown
|
|
449
|
+
const turndownService = new TurndownService({
|
|
450
|
+
headingStyle: "atx",
|
|
451
|
+
codeBlockStyle: "fenced",
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const markdown = turndownService.turndown(contentToConvert);
|
|
455
|
+
|
|
456
|
+
// Emit finish event
|
|
457
|
+
if (onEvent) {
|
|
458
|
+
onEvent(createFetchUrlFinishEvent(response.url, true));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Evict large content
|
|
462
|
+
if (toolResultEvictionLimit && toolResultEvictionLimit > 0 && backend) {
|
|
463
|
+
const resolvedBackend = getBackend(backend, state);
|
|
464
|
+
if (resolvedBackend) {
|
|
465
|
+
const evictResult = await evictToolResult({
|
|
466
|
+
result: markdown,
|
|
467
|
+
toolCallId: toolCallId || `fetch_url_${Date.now()}`,
|
|
468
|
+
toolName: "fetch_url",
|
|
469
|
+
backend: resolvedBackend,
|
|
470
|
+
tokenLimit: toolResultEvictionLimit,
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
return evictResult.content;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return markdown;
|
|
478
|
+
} catch (error: unknown) {
|
|
479
|
+
const err = error as Error;
|
|
480
|
+
let errorMessage: string;
|
|
481
|
+
|
|
482
|
+
if (err.name === "TimeoutError" || err.name === "AbortError") {
|
|
483
|
+
errorMessage = REQUEST_TIMEOUT(timeout);
|
|
484
|
+
} else {
|
|
485
|
+
errorMessage = `Error fetching URL: ${err.message}`;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Emit error finish event
|
|
489
|
+
if (onEvent) {
|
|
490
|
+
onEvent(createFetchUrlFinishEvent(url, false));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return errorMessage;
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ============================================================================
|
|
500
|
+
// Main Factory Function
|
|
501
|
+
// ============================================================================
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Options for creating web tools.
|
|
505
|
+
*/
|
|
506
|
+
export interface CreateWebToolsOptions {
|
|
507
|
+
/** Backend for filesystem operations (for eviction) */
|
|
508
|
+
backend?: BackendProtocol | BackendFactory;
|
|
509
|
+
/** Callback for emitting events */
|
|
510
|
+
onEvent?: EventCallback;
|
|
511
|
+
/** Token limit before evicting large tool results (default: disabled) */
|
|
512
|
+
toolResultEvictionLimit?: number;
|
|
513
|
+
/** Tavily API key (defaults to process.env.TAVILY_API_KEY) */
|
|
514
|
+
tavilyApiKey?: string;
|
|
515
|
+
/** Default timeout for HTTP requests in seconds (default: 30) */
|
|
516
|
+
defaultTimeout?: number;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Create all web tools (web_search, http_request, fetch_url).
|
|
521
|
+
* Tools are only created if TAVILY_API_KEY is available.
|
|
522
|
+
*/
|
|
523
|
+
export function createWebTools(
|
|
524
|
+
state: DeepAgentState,
|
|
525
|
+
options?: CreateWebToolsOptions
|
|
526
|
+
): Record<string, any> {
|
|
527
|
+
const {
|
|
528
|
+
backend,
|
|
529
|
+
onEvent,
|
|
530
|
+
toolResultEvictionLimit,
|
|
531
|
+
tavilyApiKey = process.env.TAVILY_API_KEY,
|
|
532
|
+
defaultTimeout = DEFAULT_TIMEOUT_SECONDS,
|
|
533
|
+
} = options || {};
|
|
534
|
+
|
|
535
|
+
// Return empty object if no Tavily API key
|
|
536
|
+
if (!tavilyApiKey) {
|
|
537
|
+
console.warn(
|
|
538
|
+
"Tavily API key not found. Web tools (web_search, fetch_url, http_request) will not be available. " +
|
|
539
|
+
"Set TAVILY_API_KEY environment variable to enable web tools."
|
|
540
|
+
);
|
|
541
|
+
return {};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
web_search: createWebSearchTool(state, { backend, onEvent, toolResultEvictionLimit, tavilyApiKey }),
|
|
546
|
+
http_request: createHttpRequestTool(state, { backend, onEvent, toolResultEvictionLimit, defaultTimeout }),
|
|
547
|
+
fetch_url: createFetchUrlTool(state, { backend, onEvent, toolResultEvictionLimit, defaultTimeout }),
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ============================================================================
|
|
552
|
+
// Exports
|
|
553
|
+
// ============================================================================
|
|
554
|
+
|
|
555
|
+
export { htmlToMarkdown };
|
|
556
|
+
|
|
557
|
+
// ============================================================================
|
|
558
|
+
// Individual Tool References
|
|
559
|
+
// ============================================================================
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Individual builtin tool references for selective subagent configuration.
|
|
563
|
+
* These are references to the creator functions, not instances.
|
|
564
|
+
*/
|
|
565
|
+
export const web_search = createWebSearchTool;
|
|
566
|
+
export const http_request = createHttpRequestTool;
|
|
567
|
+
export const fetch_url = createFetchUrlTool;
|