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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/package.json +95 -0
  4. package/src/agent.ts +1230 -0
  5. package/src/backends/composite.ts +273 -0
  6. package/src/backends/filesystem.ts +692 -0
  7. package/src/backends/index.ts +22 -0
  8. package/src/backends/local-sandbox.ts +175 -0
  9. package/src/backends/persistent.ts +593 -0
  10. package/src/backends/sandbox.ts +510 -0
  11. package/src/backends/state.ts +244 -0
  12. package/src/backends/utils.ts +287 -0
  13. package/src/checkpointer/file-saver.ts +98 -0
  14. package/src/checkpointer/index.ts +5 -0
  15. package/src/checkpointer/kv-saver.ts +82 -0
  16. package/src/checkpointer/memory-saver.ts +82 -0
  17. package/src/checkpointer/types.ts +125 -0
  18. package/src/cli/components/ApiKeyInput.tsx +300 -0
  19. package/src/cli/components/FilePreview.tsx +237 -0
  20. package/src/cli/components/Input.tsx +277 -0
  21. package/src/cli/components/Message.tsx +93 -0
  22. package/src/cli/components/ModelSelection.tsx +338 -0
  23. package/src/cli/components/SlashMenu.tsx +101 -0
  24. package/src/cli/components/StatusBar.tsx +89 -0
  25. package/src/cli/components/Subagent.tsx +91 -0
  26. package/src/cli/components/TodoList.tsx +133 -0
  27. package/src/cli/components/ToolApproval.tsx +70 -0
  28. package/src/cli/components/ToolCall.tsx +144 -0
  29. package/src/cli/components/ToolCallSummary.tsx +175 -0
  30. package/src/cli/components/Welcome.tsx +75 -0
  31. package/src/cli/components/index.ts +24 -0
  32. package/src/cli/hooks/index.ts +12 -0
  33. package/src/cli/hooks/useAgent.ts +933 -0
  34. package/src/cli/index.tsx +1066 -0
  35. package/src/cli/theme.ts +205 -0
  36. package/src/cli/utils/model-list.ts +365 -0
  37. package/src/constants/errors.ts +29 -0
  38. package/src/constants/limits.ts +195 -0
  39. package/src/index.ts +176 -0
  40. package/src/middleware/agent-memory.ts +330 -0
  41. package/src/prompts.ts +196 -0
  42. package/src/skills/index.ts +2 -0
  43. package/src/skills/load.ts +191 -0
  44. package/src/skills/types.ts +53 -0
  45. package/src/tools/execute.ts +167 -0
  46. package/src/tools/filesystem.ts +418 -0
  47. package/src/tools/index.ts +39 -0
  48. package/src/tools/subagent.ts +443 -0
  49. package/src/tools/todos.ts +101 -0
  50. package/src/tools/web.ts +567 -0
  51. package/src/types/backend.ts +177 -0
  52. package/src/types/core.ts +220 -0
  53. package/src/types/events.ts +429 -0
  54. package/src/types/index.ts +94 -0
  55. package/src/types/structured-output.ts +43 -0
  56. package/src/types/subagent.ts +96 -0
  57. package/src/types.ts +22 -0
  58. package/src/utils/approval.ts +213 -0
  59. package/src/utils/events.ts +416 -0
  60. package/src/utils/eviction.ts +181 -0
  61. package/src/utils/index.ts +34 -0
  62. package/src/utils/model-parser.ts +38 -0
  63. package/src/utils/patch-tool-calls.ts +233 -0
  64. package/src/utils/project-detection.ts +32 -0
  65. package/src/utils/summarization.ts +254 -0
@@ -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;