confused-ai-core 0.1.0

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 (114) hide show
  1. package/FEATURES.md +169 -0
  2. package/package.json +119 -0
  3. package/src/agent.ts +187 -0
  4. package/src/agentic/index.ts +87 -0
  5. package/src/agentic/runner.ts +386 -0
  6. package/src/agentic/types.ts +91 -0
  7. package/src/artifacts/artifact.ts +417 -0
  8. package/src/artifacts/index.ts +42 -0
  9. package/src/artifacts/media.ts +304 -0
  10. package/src/cli/index.ts +122 -0
  11. package/src/core/base-agent.ts +151 -0
  12. package/src/core/context-builder.ts +106 -0
  13. package/src/core/index.ts +8 -0
  14. package/src/core/schemas.ts +17 -0
  15. package/src/core/types.ts +158 -0
  16. package/src/create-agent.ts +309 -0
  17. package/src/debug-logger.ts +188 -0
  18. package/src/dx/agent.ts +88 -0
  19. package/src/dx/define-agent.ts +183 -0
  20. package/src/dx/dev-logger.ts +57 -0
  21. package/src/dx/index.ts +11 -0
  22. package/src/errors.ts +175 -0
  23. package/src/execution/engine.ts +522 -0
  24. package/src/execution/graph-builder.ts +362 -0
  25. package/src/execution/index.ts +8 -0
  26. package/src/execution/types.ts +257 -0
  27. package/src/execution/worker-pool.ts +308 -0
  28. package/src/extensions/index.ts +123 -0
  29. package/src/guardrails/allowlist.ts +155 -0
  30. package/src/guardrails/index.ts +17 -0
  31. package/src/guardrails/types.ts +159 -0
  32. package/src/guardrails/validator.ts +265 -0
  33. package/src/index.ts +74 -0
  34. package/src/knowledge/index.ts +5 -0
  35. package/src/knowledge/types.ts +52 -0
  36. package/src/learning/in-memory-store.ts +72 -0
  37. package/src/learning/index.ts +6 -0
  38. package/src/learning/types.ts +42 -0
  39. package/src/llm/cache.ts +300 -0
  40. package/src/llm/index.ts +22 -0
  41. package/src/llm/model-resolver.ts +81 -0
  42. package/src/llm/openai-provider.ts +313 -0
  43. package/src/llm/openrouter-provider.ts +29 -0
  44. package/src/llm/types.ts +131 -0
  45. package/src/memory/in-memory-store.ts +255 -0
  46. package/src/memory/index.ts +7 -0
  47. package/src/memory/types.ts +193 -0
  48. package/src/memory/vector-store.ts +251 -0
  49. package/src/observability/console-logger.ts +123 -0
  50. package/src/observability/index.ts +12 -0
  51. package/src/observability/metrics.ts +85 -0
  52. package/src/observability/otlp-exporter.ts +417 -0
  53. package/src/observability/tracer.ts +105 -0
  54. package/src/observability/types.ts +341 -0
  55. package/src/orchestration/agent-adapter.ts +33 -0
  56. package/src/orchestration/index.ts +34 -0
  57. package/src/orchestration/load-balancer.ts +151 -0
  58. package/src/orchestration/mcp-types.ts +59 -0
  59. package/src/orchestration/message-bus.ts +192 -0
  60. package/src/orchestration/orchestrator.ts +349 -0
  61. package/src/orchestration/pipeline.ts +66 -0
  62. package/src/orchestration/supervisor.ts +107 -0
  63. package/src/orchestration/swarm.ts +1099 -0
  64. package/src/orchestration/toolkit.ts +47 -0
  65. package/src/orchestration/types.ts +339 -0
  66. package/src/planner/classical-planner.ts +383 -0
  67. package/src/planner/index.ts +8 -0
  68. package/src/planner/llm-planner.ts +353 -0
  69. package/src/planner/types.ts +227 -0
  70. package/src/planner/validator.ts +297 -0
  71. package/src/production/circuit-breaker.ts +290 -0
  72. package/src/production/graceful-shutdown.ts +251 -0
  73. package/src/production/health.ts +333 -0
  74. package/src/production/index.ts +57 -0
  75. package/src/production/latency-eval.ts +62 -0
  76. package/src/production/rate-limiter.ts +287 -0
  77. package/src/production/resumable-stream.ts +289 -0
  78. package/src/production/types.ts +81 -0
  79. package/src/sdk/index.ts +374 -0
  80. package/src/session/db-driver.ts +50 -0
  81. package/src/session/in-memory-store.ts +235 -0
  82. package/src/session/index.ts +12 -0
  83. package/src/session/sql-store.ts +315 -0
  84. package/src/session/sqlite-store.ts +61 -0
  85. package/src/session/types.ts +153 -0
  86. package/src/tools/base-tool.ts +223 -0
  87. package/src/tools/browser-tool.ts +123 -0
  88. package/src/tools/calculator-tool.ts +265 -0
  89. package/src/tools/file-tools.ts +394 -0
  90. package/src/tools/github-tool.ts +432 -0
  91. package/src/tools/hackernews-tool.ts +187 -0
  92. package/src/tools/http-tool.ts +118 -0
  93. package/src/tools/index.ts +99 -0
  94. package/src/tools/jira-tool.ts +373 -0
  95. package/src/tools/notion-tool.ts +322 -0
  96. package/src/tools/openai-tool.ts +236 -0
  97. package/src/tools/registry.ts +131 -0
  98. package/src/tools/serpapi-tool.ts +234 -0
  99. package/src/tools/shell-tool.ts +118 -0
  100. package/src/tools/slack-tool.ts +327 -0
  101. package/src/tools/telegram-tool.ts +127 -0
  102. package/src/tools/types.ts +229 -0
  103. package/src/tools/websearch-tool.ts +335 -0
  104. package/src/tools/wikipedia-tool.ts +177 -0
  105. package/src/tools/yfinance-tool.ts +33 -0
  106. package/src/voice/index.ts +17 -0
  107. package/src/voice/voice-provider.ts +228 -0
  108. package/tests/artifact.test.ts +241 -0
  109. package/tests/circuit-breaker.test.ts +171 -0
  110. package/tests/health.test.ts +192 -0
  111. package/tests/llm-cache.test.ts +186 -0
  112. package/tests/rate-limiter.test.ts +161 -0
  113. package/tsconfig.json +29 -0
  114. package/vitest.config.ts +47 -0
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Telegram tool implementation - TypeScript TelegramTools
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import { BaseTool, BaseToolConfig } from './base-tool.js';
7
+ import { ToolContext, ToolCategory } from './types.js';
8
+
9
+ /**
10
+ * Telegram API response
11
+ */
12
+ interface TelegramResponse {
13
+ ok: boolean;
14
+ result?: unknown;
15
+ description?: string;
16
+ error_code?: number;
17
+ }
18
+
19
+ interface TelegramResult {
20
+ success: boolean;
21
+ message?: string;
22
+ error?: string;
23
+ response?: unknown;
24
+ }
25
+
26
+ /**
27
+ * Parameters for sending a Telegram message
28
+ */
29
+ const TelegramSendMessageParameters = z.object({
30
+ message: z.string().describe('The message to send'),
31
+ chat_id: z.string().optional().describe('Chat ID to send message to (overrides default)'),
32
+ parse_mode: z.enum(['HTML', 'Markdown', 'MarkdownV2']).optional().describe('Parse mode for the message'),
33
+ });
34
+
35
+ /**
36
+ * Telegram tool for sending messages
37
+ */
38
+ export class TelegramTool extends BaseTool<typeof TelegramSendMessageParameters, TelegramResult> {
39
+ private token: string;
40
+ private defaultChatId: string;
41
+ private baseUrl = 'https://api.telegram.org';
42
+
43
+ constructor(
44
+ config?: Partial<Omit<BaseToolConfig<typeof TelegramSendMessageParameters>, 'parameters'>> & {
45
+ token?: string;
46
+ chatId?: string;
47
+ }
48
+ ) {
49
+ super({
50
+ name: config?.name ?? 'telegram.send_message',
51
+ description: config?.description ?? 'Send a message to a Telegram chat',
52
+ parameters: TelegramSendMessageParameters,
53
+ category: config?.category ?? ToolCategory.API,
54
+ permissions: {
55
+ allowNetwork: true,
56
+ maxExecutionTimeMs: 30000,
57
+ ...config?.permissions,
58
+ },
59
+ ...config,
60
+ });
61
+
62
+ this.token = config?.token || process.env.TELEGRAM_TOKEN || '';
63
+ this.defaultChatId = config?.chatId || process.env.TELEGRAM_CHAT_ID || '';
64
+
65
+ if (!this.token) {
66
+ throw new Error('Telegram token is required. Set TELEGRAM_TOKEN environment variable or pass token in config.');
67
+ }
68
+ }
69
+
70
+ protected async performExecute(
71
+ params: z.infer<typeof TelegramSendMessageParameters>,
72
+ _context: ToolContext
73
+ ): Promise<TelegramResult> {
74
+ const chatId = params.chat_id || this.defaultChatId;
75
+
76
+ if (!chatId) {
77
+ return {
78
+ success: false,
79
+ error: 'Chat ID is required. Set TELEGRAM_CHAT_ID environment variable, pass chatId in config, or provide chat_id in parameters.',
80
+ };
81
+ }
82
+
83
+ try {
84
+ const response = await fetch(`${this.baseUrl}/bot${this.token}/sendMessage`, {
85
+ method: 'POST',
86
+ headers: {
87
+ 'Content-Type': 'application/json',
88
+ },
89
+ body: JSON.stringify({
90
+ chat_id: chatId,
91
+ text: params.message,
92
+ parse_mode: params.parse_mode,
93
+ }),
94
+ });
95
+
96
+ const data = (await response.json()) as TelegramResponse;
97
+
98
+ if (data.ok) {
99
+ return {
100
+ success: true,
101
+ message: 'Message sent successfully',
102
+ response: data.result,
103
+ };
104
+ } else {
105
+ return {
106
+ success: false,
107
+ error: data.description || 'Unknown error from Telegram API',
108
+ response: data,
109
+ };
110
+ }
111
+ } catch (error) {
112
+ return {
113
+ success: false,
114
+ error: error instanceof Error ? error.message : 'Unknown error occurred',
115
+ };
116
+ }
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Telegram toolkit
122
+ */
123
+ export class TelegramToolkit {
124
+ static create(options: { token?: string; chatId?: string }): Array<TelegramTool> {
125
+ return [new TelegramTool({ token: options.token, chatId: options.chatId })];
126
+ }
127
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Tool integration types and interfaces
3
+ */
4
+
5
+ import type { EntityId } from '../core/types.js';
6
+ import { z } from 'zod';
7
+
8
+ /**
9
+ * Tool parameter schema using Zod
10
+ */
11
+ export type ToolParameters = z.ZodObject<Record<string, z.ZodType>>;
12
+
13
+ /**
14
+ * Tool execution context
15
+ */
16
+ export interface ToolContext {
17
+ readonly toolId: EntityId;
18
+ readonly agentId: EntityId;
19
+ readonly sessionId: string;
20
+ readonly timeoutMs?: number;
21
+ readonly permissions: ToolPermissions;
22
+ }
23
+
24
+ /**
25
+ * Tool permissions
26
+ */
27
+ export interface ToolPermissions {
28
+ readonly allowNetwork: boolean;
29
+ readonly allowFileSystem: boolean;
30
+ readonly allowedPaths?: string[];
31
+ readonly allowedHosts?: string[];
32
+ readonly maxExecutionTimeMs: number;
33
+ }
34
+
35
+ /**
36
+ * Tool execution result
37
+ */
38
+ export interface ToolResult<T = unknown> {
39
+ readonly success: boolean;
40
+ readonly data?: T;
41
+ readonly error?: ToolError;
42
+ readonly executionTimeMs: number;
43
+ readonly metadata: ToolExecutionMetadata;
44
+ }
45
+
46
+ /**
47
+ * Tool error details
48
+ */
49
+ export interface ToolError {
50
+ readonly code: string;
51
+ readonly message: string;
52
+ readonly details?: unknown;
53
+ }
54
+
55
+ /**
56
+ * Tool execution metadata
57
+ */
58
+ export interface ToolExecutionMetadata {
59
+ readonly startTime: Date;
60
+ readonly endTime: Date;
61
+ readonly retries: number;
62
+ readonly tokensUsed?: number;
63
+ }
64
+
65
+ /**
66
+ * Tool definition
67
+ */
68
+ export interface Tool<TParams extends ToolParameters = ToolParameters, TOutput = unknown> {
69
+ readonly id: EntityId;
70
+ readonly name: string;
71
+ readonly description: string;
72
+ readonly parameters: TParams;
73
+ readonly permissions: ToolPermissions;
74
+ readonly category: ToolCategory;
75
+ readonly version: string;
76
+ readonly author?: string;
77
+ readonly tags?: string[];
78
+
79
+ /**
80
+ * Execute the tool with validated parameters
81
+ */
82
+ execute(params: z.infer<TParams>, context: ToolContext): Promise<ToolResult<TOutput>>;
83
+
84
+ /**
85
+ * Validate parameters without executing
86
+ */
87
+ validate(params: unknown): params is z.infer<TParams>;
88
+ }
89
+
90
+ /**
91
+ * Tool categories
92
+ */
93
+ export enum ToolCategory {
94
+ WEB = 'web',
95
+ DATABASE = 'database',
96
+ FILE_SYSTEM = 'file_system',
97
+ API = 'api',
98
+ UTILITY = 'utility',
99
+ AI = 'ai',
100
+ CUSTOM = 'custom',
101
+ }
102
+
103
+ /**
104
+ * Tool registry for managing available tools
105
+ */
106
+ export interface ToolRegistry {
107
+ /**
108
+ * Register a tool
109
+ */
110
+ register(tool: Tool): void;
111
+
112
+ /**
113
+ * Unregister a tool by ID
114
+ */
115
+ unregister(toolId: EntityId): boolean;
116
+
117
+ /**
118
+ * Get a tool by ID
119
+ */
120
+ get(toolId: EntityId): Tool | undefined;
121
+
122
+ /**
123
+ * Get a tool by name
124
+ */
125
+ getByName(name: string): Tool | undefined;
126
+
127
+ /**
128
+ * List all registered tools
129
+ */
130
+ list(): Tool[];
131
+
132
+ /**
133
+ * List tools by category
134
+ */
135
+ listByCategory(category: ToolCategory): Tool[];
136
+
137
+ /**
138
+ * Search tools by name or description
139
+ */
140
+ search(query: string): Tool[];
141
+
142
+ /**
143
+ * Check if a tool is registered
144
+ */
145
+ has(toolId: EntityId): boolean;
146
+
147
+ /**
148
+ * Clear all registered tools
149
+ */
150
+ clear(): void;
151
+ }
152
+
153
+ /**
154
+ * Tool sandbox configuration
155
+ */
156
+ export interface ToolSandboxConfig {
157
+ readonly enabled: boolean;
158
+ readonly timeoutMs: number;
159
+ readonly maxMemoryMb: number;
160
+ readonly allowedModules?: string[];
161
+ readonly blockedModules?: string[];
162
+ readonly environmentVariables?: Record<string, string>;
163
+ }
164
+
165
+ /**
166
+ * Tool middleware for intercepting tool calls
167
+ */
168
+ export interface ToolMiddleware {
169
+ /**
170
+ * Called before tool execution
171
+ */
172
+ beforeExecute?: (tool: Tool, params: unknown, context: ToolContext) => Promise<void> | void;
173
+
174
+ /**
175
+ * Called after tool execution
176
+ */
177
+ afterExecute?: (tool: Tool, result: ToolResult, context: ToolContext) => Promise<void> | void;
178
+
179
+ /**
180
+ * Called on tool execution error
181
+ */
182
+ onError?: (tool: Tool, error: Error, context: ToolContext) => Promise<void> | void;
183
+ }
184
+
185
+ /**
186
+ * Tool factory for creating tool instances
187
+ */
188
+ export interface ToolFactory {
189
+ /**
190
+ * Create a tool instance
191
+ */
192
+ create(config: Record<string, unknown>): Tool;
193
+
194
+ /**
195
+ * Get the tool schema
196
+ */
197
+ getSchema(): ToolSchema;
198
+ }
199
+
200
+ /**
201
+ * Tool schema for documentation and validation
202
+ */
203
+ export interface ToolSchema {
204
+ readonly name: string;
205
+ readonly description: string;
206
+ readonly parameters: Record<string, ParameterSchema>;
207
+ readonly returns: ParameterSchema;
208
+ readonly examples?: ToolExample[];
209
+ }
210
+
211
+ /**
212
+ * Parameter schema
213
+ */
214
+ export interface ParameterSchema {
215
+ readonly type: string;
216
+ readonly description: string;
217
+ readonly required: boolean;
218
+ readonly default?: unknown;
219
+ readonly enum?: unknown[];
220
+ }
221
+
222
+ /**
223
+ * Tool usage example
224
+ */
225
+ export interface ToolExample {
226
+ readonly description: string;
227
+ readonly parameters: Record<string, unknown>;
228
+ readonly expectedOutput?: unknown;
229
+ }
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Web search tool implementation - TypeScript WebSearchTools and DuckDuckGoTools
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import { BaseTool, BaseToolConfig } from './base-tool.js';
7
+ import { ToolContext, ToolCategory } from './types.js';
8
+
9
+ /**
10
+ * Search result item
11
+ */
12
+ interface SearchResult {
13
+ title: string;
14
+ url: string;
15
+ snippet?: string;
16
+ source?: string;
17
+ }
18
+
19
+ interface WebSearchResponse {
20
+ query: string;
21
+ results: SearchResult[];
22
+ backend?: string;
23
+ }
24
+
25
+ interface NewsResult {
26
+ title: string;
27
+ url: string;
28
+ snippet?: string;
29
+ date?: string;
30
+ source?: string;
31
+ }
32
+
33
+ interface NewsSearchResponse {
34
+ query: string;
35
+ results: NewsResult[];
36
+ backend?: string;
37
+ }
38
+
39
+ /**
40
+ * Parameters for web search
41
+ */
42
+ const WebSearchParameters = z.object({
43
+ query: z.string().describe('The search query'),
44
+ max_results: z.number().min(1).max(20).optional().default(5).describe('Maximum number of results to return'),
45
+ });
46
+
47
+ /**
48
+ * DuckDuckGo search tool using DuckDuckGo's HTML interface
49
+ */
50
+ export class DuckDuckGoSearchTool extends BaseTool<typeof WebSearchParameters, WebSearchResponse> {
51
+ private modifier?: string;
52
+
53
+ constructor(
54
+ config?: Partial<Omit<BaseToolConfig<typeof WebSearchParameters>, 'parameters'>> & {
55
+ modifier?: string;
56
+ }
57
+ ) {
58
+ super({
59
+ name: config?.name ?? 'duckduckgo.search',
60
+ description: config?.description ?? 'Search the web using DuckDuckGo',
61
+ parameters: WebSearchParameters,
62
+ category: config?.category ?? ToolCategory.WEB,
63
+ permissions: {
64
+ allowNetwork: true,
65
+ maxExecutionTimeMs: 30000,
66
+ ...config?.permissions,
67
+ },
68
+ ...config,
69
+ });
70
+ this.modifier = config?.modifier;
71
+ }
72
+
73
+ protected async performExecute(
74
+ params: z.infer<typeof WebSearchParameters>,
75
+ _context: ToolContext
76
+ ): Promise<WebSearchResponse> {
77
+ const searchQuery = this.modifier ? `${this.modifier} ${params.query}` : params.query;
78
+
79
+ try {
80
+ // Use DuckDuckGo's instant answer API
81
+ const response = await fetch(
82
+ `https://duckduckgo.com/html/?q=${encodeURIComponent(searchQuery)}`,
83
+ {
84
+ headers: {
85
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
86
+ },
87
+ }
88
+ );
89
+
90
+ if (!response.ok) {
91
+ throw new Error(`DuckDuckGo search failed: ${response.status}`);
92
+ }
93
+
94
+ const html = await response.text();
95
+ const results = this.parseResults(html, params.max_results);
96
+
97
+ return {
98
+ query: searchQuery,
99
+ results,
100
+ backend: 'duckduckgo',
101
+ };
102
+ } catch (error) {
103
+ // Fallback to returning empty results with error info
104
+ return {
105
+ query: searchQuery,
106
+ results: [],
107
+ backend: 'duckduckgo',
108
+ };
109
+ }
110
+ }
111
+
112
+ private parseResults(html: string, maxResults: number): SearchResult[] {
113
+ const results: SearchResult[] = [];
114
+ // Simple regex-based parsing for DuckDuckGo HTML results
115
+ const resultRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi;
116
+
117
+ let match;
118
+ const titles: string[] = [];
119
+ const urls: string[] = [];
120
+
121
+ while ((match = resultRegex.exec(html)) !== null && urls.length < maxResults) {
122
+ const url = match[1];
123
+ const title = match[2].replace(/<[^>]*>/g, ''); // Strip HTML tags
124
+ if (url && !url.startsWith('/')) {
125
+ urls.push(url);
126
+ titles.push(title);
127
+ }
128
+ }
129
+
130
+ for (let i = 0; i < urls.length; i++) {
131
+ results.push({
132
+ title: titles[i] || 'Untitled',
133
+ url: urls[i],
134
+ source: 'DuckDuckGo',
135
+ });
136
+ }
137
+
138
+ return results;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * DuckDuckGo news search tool
144
+ */
145
+ export class DuckDuckGoNewsTool extends BaseTool<typeof WebSearchParameters, NewsSearchResponse> {
146
+ constructor(config?: Partial<Omit<BaseToolConfig<typeof WebSearchParameters>, 'parameters'>>) {
147
+ super({
148
+ name: config?.name ?? 'duckduckgo.news',
149
+ description: config?.description ?? 'Search for news using DuckDuckGo',
150
+ parameters: WebSearchParameters,
151
+ category: config?.category ?? ToolCategory.WEB,
152
+ permissions: {
153
+ allowNetwork: true,
154
+ maxExecutionTimeMs: 30000,
155
+ ...config?.permissions,
156
+ },
157
+ ...config,
158
+ });
159
+ }
160
+
161
+ protected async performExecute(
162
+ params: z.infer<typeof WebSearchParameters>,
163
+ _context: ToolContext
164
+ ): Promise<NewsSearchResponse> {
165
+ try {
166
+ const response = await fetch(
167
+ `https://duckduckgo.com/html/?q=${encodeURIComponent(params.query + ' news')}`,
168
+ {
169
+ headers: {
170
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
171
+ },
172
+ }
173
+ );
174
+
175
+ if (!response.ok) {
176
+ throw new Error(`DuckDuckGo news search failed: ${response.status}`);
177
+ }
178
+
179
+ const html = await response.text();
180
+ const results = this.parseNewsResults(html, params.max_results);
181
+
182
+ return {
183
+ query: params.query,
184
+ results,
185
+ backend: 'duckduckgo',
186
+ };
187
+ } catch (error) {
188
+ return {
189
+ query: params.query,
190
+ results: [],
191
+ backend: 'duckduckgo',
192
+ };
193
+ }
194
+ }
195
+
196
+ private parseNewsResults(html: string, maxResults: number): NewsResult[] {
197
+ const results: NewsResult[] = [];
198
+ // Similar parsing logic for news
199
+ const resultRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi;
200
+
201
+ let match;
202
+ while ((match = resultRegex.exec(html)) !== null && results.length < maxResults) {
203
+ const url = match[1];
204
+ const title = match[2].replace(/<[^>]*>/g, '');
205
+ if (url && !url.startsWith('/')) {
206
+ results.push({
207
+ title: title || 'Untitled',
208
+ url: url,
209
+ source: 'DuckDuckGo News',
210
+ });
211
+ }
212
+ }
213
+
214
+ return results;
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Generic web search tool that can use different backends
220
+ */
221
+ const GenericWebSearchParameters = z.object({
222
+ query: z.string().describe('The search query'),
223
+ max_results: z.number().min(1).max(20).optional().default(5).describe('Maximum number of results to return'),
224
+ backend: z.enum(['duckduckgo', 'bing', 'google']).optional().default('duckduckgo').describe('Search backend to use'),
225
+ });
226
+
227
+ export class WebSearchTool extends BaseTool<typeof GenericWebSearchParameters, WebSearchResponse> {
228
+ private modifier?: string;
229
+
230
+ constructor(
231
+ config?: Partial<Omit<BaseToolConfig<typeof GenericWebSearchParameters>, 'parameters'>> & {
232
+ modifier?: string;
233
+ }
234
+ ) {
235
+ super({
236
+ name: config?.name ?? 'web.search',
237
+ description: config?.description ?? 'Search the web using various search engines',
238
+ parameters: GenericWebSearchParameters,
239
+ category: config?.category ?? ToolCategory.WEB,
240
+ permissions: {
241
+ allowNetwork: true,
242
+ maxExecutionTimeMs: 30000,
243
+ ...config?.permissions,
244
+ },
245
+ ...config,
246
+ });
247
+ this.modifier = config?.modifier;
248
+ }
249
+
250
+ protected async performExecute(
251
+ params: z.infer<typeof GenericWebSearchParameters>,
252
+ _context: ToolContext
253
+ ): Promise<WebSearchResponse> {
254
+ const searchQuery = this.modifier ? `${this.modifier} ${params.query}` : params.query;
255
+
256
+ // For now, only DuckDuckGo is implemented without API keys
257
+ // Other backends would require API keys
258
+ switch (params.backend) {
259
+ case 'duckduckgo':
260
+ default:
261
+ return this.searchDuckDuckGo(searchQuery, params.max_results);
262
+ }
263
+ }
264
+
265
+ private async searchDuckDuckGo(query: string, maxResults: number): Promise<WebSearchResponse> {
266
+ try {
267
+ const response = await fetch(
268
+ `https://duckduckgo.com/html/?q=${encodeURIComponent(query)}`,
269
+ {
270
+ headers: {
271
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
272
+ },
273
+ }
274
+ );
275
+
276
+ if (!response.ok) {
277
+ throw new Error(`Search failed: ${response.status}`);
278
+ }
279
+
280
+ const html = await response.text();
281
+ const results = this.parseResults(html, maxResults);
282
+
283
+ return {
284
+ query,
285
+ results,
286
+ backend: 'duckduckgo',
287
+ };
288
+ } catch (error) {
289
+ return {
290
+ query,
291
+ results: [],
292
+ backend: 'duckduckgo',
293
+ };
294
+ }
295
+ }
296
+
297
+ private parseResults(html: string, maxResults: number): SearchResult[] {
298
+ const results: SearchResult[] = [];
299
+ const resultRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi;
300
+
301
+ let match;
302
+ while ((match = resultRegex.exec(html)) !== null && results.length < maxResults) {
303
+ const url = match[1];
304
+ const title = match[2].replace(/<[^>]*>/g, '');
305
+ if (url && !url.startsWith('/')) {
306
+ results.push({
307
+ title: title || 'Untitled',
308
+ url: url,
309
+ source: 'DuckDuckGo',
310
+ });
311
+ }
312
+ }
313
+
314
+ return results;
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Web search toolkit
320
+ */
321
+ export class WebSearchToolkit {
322
+ static createDuckDuckGo(options?: { modifier?: string; enableNews?: boolean }): Array<DuckDuckGoSearchTool | DuckDuckGoNewsTool> {
323
+ const tools: Array<DuckDuckGoSearchTool | DuckDuckGoNewsTool> = [
324
+ new DuckDuckGoSearchTool({ modifier: options?.modifier }),
325
+ ];
326
+ if (options?.enableNews !== false) {
327
+ tools.push(new DuckDuckGoNewsTool());
328
+ }
329
+ return tools;
330
+ }
331
+
332
+ static createGeneric(options?: { modifier?: string }): Array<WebSearchTool> {
333
+ return [new WebSearchTool({ modifier: options?.modifier })];
334
+ }
335
+ }