brownian-code 2026.2.10

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 (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/bin/brownian +25 -0
  4. package/env.example +21 -0
  5. package/package.json +87 -0
  6. package/src/agent/agent.test.ts +414 -0
  7. package/src/agent/agent.ts +385 -0
  8. package/src/agent/index.ts +27 -0
  9. package/src/agent/prompts.ts +271 -0
  10. package/src/agent/scratchpad.test.ts +482 -0
  11. package/src/agent/scratchpad.ts +526 -0
  12. package/src/agent/token-counter.test.ts +59 -0
  13. package/src/agent/token-counter.ts +33 -0
  14. package/src/agent/types.ts +137 -0
  15. package/src/cli.tsx +385 -0
  16. package/src/commands/builtin.test.ts +271 -0
  17. package/src/commands/builtin.ts +200 -0
  18. package/src/commands/registry.test.ts +188 -0
  19. package/src/commands/registry.ts +111 -0
  20. package/src/commands/types.ts +64 -0
  21. package/src/components/AgentEventView.tsx +487 -0
  22. package/src/components/AnswerBox.tsx +81 -0
  23. package/src/components/ApiKeyPrompt.tsx +75 -0
  24. package/src/components/CommandMenu.test.tsx +64 -0
  25. package/src/components/CommandMenu.tsx +38 -0
  26. package/src/components/CursorText.tsx +43 -0
  27. package/src/components/DebugPanel.tsx +48 -0
  28. package/src/components/ErrorBox.test.tsx +58 -0
  29. package/src/components/ErrorBox.tsx +26 -0
  30. package/src/components/HelpView.test.tsx +70 -0
  31. package/src/components/HelpView.tsx +61 -0
  32. package/src/components/HistoryItemView.tsx +108 -0
  33. package/src/components/Input.tsx +193 -0
  34. package/src/components/Intro.test.tsx +59 -0
  35. package/src/components/Intro.tsx +35 -0
  36. package/src/components/ModelSelector.tsx +288 -0
  37. package/src/components/StatusBar.test.tsx +78 -0
  38. package/src/components/StatusBar.tsx +56 -0
  39. package/src/components/WorkingIndicator.tsx +133 -0
  40. package/src/components/index.ts +23 -0
  41. package/src/e2e/agent-flow.test.ts +378 -0
  42. package/src/evals/components/EvalApp.tsx +206 -0
  43. package/src/evals/components/EvalCurrentQuestion.tsx +42 -0
  44. package/src/evals/components/EvalProgress.tsx +33 -0
  45. package/src/evals/components/EvalRecentResults.tsx +63 -0
  46. package/src/evals/components/EvalStats.tsx +49 -0
  47. package/src/evals/components/index.ts +5 -0
  48. package/src/evals/dataset/crypto_agent.csv +16 -0
  49. package/src/evals/run.ts +355 -0
  50. package/src/gateway/channels/whatsapp/auth-store.ts +15 -0
  51. package/src/gateway/channels/whatsapp/inbound.ts +86 -0
  52. package/src/gateway/channels/whatsapp/login.ts +28 -0
  53. package/src/gateway/channels/whatsapp/outbound.ts +27 -0
  54. package/src/gateway/channels/whatsapp/session.ts +69 -0
  55. package/src/gateway/config.ts +81 -0
  56. package/src/gateway/index.ts +62 -0
  57. package/src/hooks/useAgentRunner.ts +317 -0
  58. package/src/hooks/useDebugLogs.ts +22 -0
  59. package/src/hooks/useInputHistory.ts +106 -0
  60. package/src/hooks/useModelSelection.ts +249 -0
  61. package/src/hooks/useTextBuffer.test.ts +121 -0
  62. package/src/hooks/useTextBuffer.ts +97 -0
  63. package/src/index.tsx +74 -0
  64. package/src/mcp/cache.ts +205 -0
  65. package/src/mcp/client.test.ts +126 -0
  66. package/src/mcp/client.ts +145 -0
  67. package/src/mcp/index.ts +2 -0
  68. package/src/model/llm.test.ts +158 -0
  69. package/src/model/llm.ts +233 -0
  70. package/src/providers.ts +94 -0
  71. package/src/skills/index.ts +17 -0
  72. package/src/skills/loader.ts +73 -0
  73. package/src/skills/registry.ts +125 -0
  74. package/src/skills/types.ts +31 -0
  75. package/src/test-utils/mocks.ts +110 -0
  76. package/src/theme.ts +21 -0
  77. package/src/tools/browser/browser.ts +357 -0
  78. package/src/tools/browser/index.ts +1 -0
  79. package/src/tools/crypto/hive-tools.ts +171 -0
  80. package/src/tools/crypto/index.ts +1 -0
  81. package/src/tools/descriptions/browser.ts +105 -0
  82. package/src/tools/descriptions/crypto-search.ts +58 -0
  83. package/src/tools/descriptions/index.ts +8 -0
  84. package/src/tools/descriptions/web-fetch.ts +44 -0
  85. package/src/tools/descriptions/web-search.ts +26 -0
  86. package/src/tools/fetch/cache.ts +95 -0
  87. package/src/tools/fetch/external-content.ts +200 -0
  88. package/src/tools/fetch/index.ts +1 -0
  89. package/src/tools/fetch/web-fetch-utils.ts +122 -0
  90. package/src/tools/fetch/web-fetch.ts +371 -0
  91. package/src/tools/index.ts +12 -0
  92. package/src/tools/registry.ts +130 -0
  93. package/src/tools/search/exa.ts +43 -0
  94. package/src/tools/search/index.ts +2 -0
  95. package/src/tools/search/tavily.ts +35 -0
  96. package/src/tools/skill.ts +62 -0
  97. package/src/tools/types.ts +53 -0
  98. package/src/utils/ai-message.ts +26 -0
  99. package/src/utils/config.ts +54 -0
  100. package/src/utils/cost-calculator.test.ts +101 -0
  101. package/src/utils/cost-calculator.ts +74 -0
  102. package/src/utils/env.ts +101 -0
  103. package/src/utils/error-classifier.test.ts +146 -0
  104. package/src/utils/error-classifier.ts +91 -0
  105. package/src/utils/in-memory-chat-history.test.ts +291 -0
  106. package/src/utils/in-memory-chat-history.ts +224 -0
  107. package/src/utils/index.ts +19 -0
  108. package/src/utils/input-key-handlers.test.ts +155 -0
  109. package/src/utils/input-key-handlers.ts +64 -0
  110. package/src/utils/logger.ts +67 -0
  111. package/src/utils/long-term-chat-history.ts +138 -0
  112. package/src/utils/markdown-table.ts +227 -0
  113. package/src/utils/ollama.ts +37 -0
  114. package/src/utils/progress-channel.ts +84 -0
  115. package/src/utils/text-navigation.test.ts +222 -0
  116. package/src/utils/text-navigation.ts +81 -0
  117. package/src/utils/thinking-verbs.ts +29 -0
  118. package/src/utils/tokens.test.ts +163 -0
  119. package/src/utils/tokens.ts +67 -0
  120. package/src/utils/tool-description.ts +88 -0
package/src/theme.ts ADDED
@@ -0,0 +1,21 @@
1
+ export const colors = {
2
+ primary: '#258bff',
3
+ primaryLight: '#a5cfff',
4
+ success: 'green',
5
+ error: 'red',
6
+ warning: 'yellow',
7
+ muted: '#a6a6a6',
8
+ mutedDark: '#303030',
9
+ accent: 'cyan',
10
+ highlight: 'magenta',
11
+ white: '#ffffff',
12
+ info: '#6CB6FF',
13
+ queryBg: '#3D3D3D',
14
+ claude: '#E5896A',
15
+ } as const;
16
+
17
+ export const dimensions = {
18
+ boxWidth: 80,
19
+ introWidth: 68,
20
+ } as const;
21
+
@@ -0,0 +1,357 @@
1
+ import { DynamicStructuredTool } from '@langchain/core/tools';
2
+ import { chromium, Browser, Page } from 'playwright';
3
+ import { z } from 'zod';
4
+ import { formatToolResult } from '../types.js';
5
+ import { logger } from '@/utils';
6
+
7
+ let browser: Browser | null = null;
8
+ let page: Page | null = null;
9
+
10
+ // Store refs from the last snapshot for action resolution
11
+ let currentRefs: Map<string, { role: string; name?: string; nth?: number }> = new Map();
12
+
13
+ // Type for Playwright's _snapshotForAI result
14
+ interface SnapshotForAIResult {
15
+ full?: string;
16
+ }
17
+
18
+ // Extended Page type with _snapshotForAI method
19
+ interface PageWithSnapshotForAI extends Page {
20
+ _snapshotForAI?: (opts: { timeout: number; track: string }) => Promise<SnapshotForAIResult>;
21
+ }
22
+
23
+ /**
24
+ * Detect if running as a compiled binary (not via bun runtime).
25
+ */
26
+ function isCompiledBinary(): boolean {
27
+ return !process.argv[0]?.includes('bun');
28
+ }
29
+
30
+ /**
31
+ * Ensure browser and page are initialized.
32
+ * Lazily launches a headless Chromium browser on first use.
33
+ * If Playwright's Chromium isn't installed, attempts auto-install for source installs
34
+ * or provides instructions for compiled binaries.
35
+ */
36
+ async function ensureBrowser(): Promise<Page> {
37
+ if (!browser) {
38
+ try {
39
+ browser = await chromium.launch({ headless: false });
40
+ } catch (err) {
41
+ const msg = err instanceof Error ? err.message : String(err);
42
+ if (msg.includes('Executable doesn\'t exist') || msg.includes('browserType.launch')) {
43
+ if (isCompiledBinary()) {
44
+ throw new Error(
45
+ 'Playwright Chromium is not installed.\n\n' +
46
+ 'To use the browser tool, install Playwright Chromium separately:\n' +
47
+ ' npx playwright install chromium\n\n' +
48
+ 'This is a one-time setup required for the browser tool.'
49
+ );
50
+ }
51
+ // Source install: attempt auto-install
52
+ const { execSync } = await import('child_process');
53
+ try {
54
+ execSync('bunx playwright install chromium', { stdio: 'inherit' });
55
+ browser = await chromium.launch({ headless: false });
56
+ } catch {
57
+ throw new Error(
58
+ 'Failed to auto-install Playwright Chromium.\n\n' +
59
+ 'Please install manually:\n' +
60
+ ' bunx playwright install chromium'
61
+ );
62
+ }
63
+ } else {
64
+ throw err;
65
+ }
66
+ }
67
+ }
68
+ if (!page) {
69
+ const context = await browser.newContext();
70
+ page = await context.newPage();
71
+ }
72
+ return page;
73
+ }
74
+
75
+ /**
76
+ * Close the browser and reset state.
77
+ */
78
+ async function closeBrowser(): Promise<void> {
79
+ if (browser) {
80
+ await browser.close();
81
+ browser = null;
82
+ page = null;
83
+ currentRefs.clear();
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Parse refs from the AI snapshot format.
89
+ * Extracts [ref=eN] patterns and builds a ref map.
90
+ */
91
+ function parseRefsFromSnapshot(snapshot: string): Map<string, { role: string; name?: string; nth?: number }> {
92
+ const refs = new Map<string, { role: string; name?: string; nth?: number }>();
93
+ const lines = snapshot.split('\n');
94
+
95
+ for (const line of lines) {
96
+ // Match patterns like: - button "Click me" [ref=e12]
97
+ const refMatch = line.match(/\[ref=(e\d+)\]/);
98
+ if (!refMatch) continue;
99
+
100
+ const ref = refMatch[1];
101
+
102
+ // Extract role (first word after "- ")
103
+ const roleMatch = line.match(/^\s*-\s*(\w+)/);
104
+ const role = roleMatch ? roleMatch[1] : 'generic';
105
+
106
+ // Extract name (text in quotes)
107
+ const nameMatch = line.match(/"([^"]+)"/);
108
+ const name = nameMatch ? nameMatch[1] : undefined;
109
+
110
+ // Extract nth if present
111
+ const nthMatch = line.match(/\[nth=(\d+)\]/);
112
+ const nth = nthMatch ? parseInt(nthMatch[1], 10) : undefined;
113
+
114
+ refs.set(ref, { role, name, nth });
115
+ }
116
+
117
+ return refs;
118
+ }
119
+
120
+ /**
121
+ * Resolve a ref to a Playwright locator using stored ref data.
122
+ */
123
+ function resolveRefToLocator(p: Page, ref: string): ReturnType<Page['locator']> {
124
+ const refData = currentRefs.get(ref);
125
+
126
+ if (!refData) {
127
+ // Fallback to aria-ref selector if ref not in map
128
+ return p.locator(`aria-ref=${ref}`);
129
+ }
130
+
131
+ // Use getByRole with the stored role and name for reliable resolution
132
+ const options: { name?: string | RegExp; exact?: boolean } = {};
133
+ if (refData.name) {
134
+ options.name = refData.name;
135
+ options.exact = true;
136
+ }
137
+
138
+ let locator = p.getByRole(refData.role as Parameters<Page['getByRole']>[0], options);
139
+
140
+ // Handle nth occurrence if specified
141
+ if (typeof refData.nth === 'number' && refData.nth > 0) {
142
+ locator = locator.nth(refData.nth);
143
+ }
144
+
145
+ return locator;
146
+ }
147
+
148
+ /**
149
+ * Take an AI-optimized snapshot using Playwright's _snapshotForAI method.
150
+ * Falls back to ariaSnapshot if _snapshotForAI is not available.
151
+ */
152
+ async function takeSnapshot(p: Page, maxChars?: number): Promise<{ snapshot: string; truncated: boolean }> {
153
+ const pageWithSnapshot = p as PageWithSnapshotForAI;
154
+
155
+ let snapshot: string;
156
+
157
+ if (pageWithSnapshot._snapshotForAI) {
158
+ // Use the AI-optimized snapshot method
159
+ const result = await pageWithSnapshot._snapshotForAI({ timeout: 10000, track: 'response' });
160
+ snapshot = String(result?.full ?? '');
161
+ } else {
162
+ // Fallback to standard ariaSnapshot
163
+ snapshot = await p.locator(':root').ariaSnapshot();
164
+ }
165
+
166
+ // Parse and store refs for later action resolution
167
+ currentRefs = parseRefsFromSnapshot(snapshot);
168
+
169
+ // Truncate if needed
170
+ let truncated = false;
171
+ const limit = maxChars ?? 50000;
172
+ if (snapshot.length > limit) {
173
+ snapshot = `${snapshot.slice(0, limit)}\n\n[...TRUNCATED - page too large, use read action for full text]`;
174
+ truncated = true;
175
+ }
176
+
177
+ return { snapshot, truncated };
178
+ }
179
+
180
+ // Schema for the act action's request object
181
+ const actRequestSchema = z.object({
182
+ kind: z.enum(['click', 'type', 'press', 'hover', 'scroll', 'wait']).describe('The type of interaction'),
183
+ ref: z.string().optional().describe('Element ref from snapshot (e.g., e12)'),
184
+ text: z.string().optional().describe('Text for type action'),
185
+ key: z.string().optional().describe('Key for press action (e.g., Enter, Tab)'),
186
+ direction: z.enum(['up', 'down']).optional().describe('Scroll direction'),
187
+ timeMs: z.number().optional().describe('Wait time in milliseconds'),
188
+ });
189
+
190
+ export const browserTool = new DynamicStructuredTool({
191
+ name: 'browser',
192
+ description: 'Navigate websites, read content, and interact with pages. Use for accessing websites, documentation, and dynamic content.',
193
+ schema: z.object({
194
+ action: z.enum(['navigate', 'open', 'snapshot', 'act', 'read', 'close']).describe('The browser action to perform'),
195
+ url: z.string().optional().describe('URL for navigate action'),
196
+ maxChars: z.number().optional().describe('Max characters for snapshot (default 50000)'),
197
+ request: actRequestSchema.optional().describe('Request object for act action'),
198
+ }),
199
+ func: async ({ action, url, maxChars, request }) => {
200
+ try {
201
+ switch (action) {
202
+ case 'navigate': {
203
+ if (!url) {
204
+ return formatToolResult({ error: 'url is required for navigate action' });
205
+ }
206
+ const p = await ensureBrowser();
207
+ // Use networkidle for better JS rendering on dynamic sites
208
+ await p.goto(url, { timeout: 30000, waitUntil: 'networkidle' });
209
+ return formatToolResult({
210
+ ok: true,
211
+ url: p.url(),
212
+ title: await p.title(),
213
+ hint: 'Page loaded. Call snapshot to see page structure and find elements to interact with.',
214
+ });
215
+ }
216
+
217
+ case 'open': {
218
+ if (!url) {
219
+ return formatToolResult({ error: 'url is required for open action' });
220
+ }
221
+ const currentPage = await ensureBrowser();
222
+ const context = currentPage.context();
223
+ const newPage = await context.newPage();
224
+ await newPage.goto(url, { timeout: 30000, waitUntil: 'networkidle' });
225
+ // Switch to the new page
226
+ page = newPage;
227
+ return formatToolResult({
228
+ ok: true,
229
+ url: newPage.url(),
230
+ title: await newPage.title(),
231
+ hint: 'New tab opened. Call snapshot to see page structure and find elements to interact with.',
232
+ });
233
+ }
234
+
235
+ case 'snapshot': {
236
+ const p = await ensureBrowser();
237
+ // Wait for any dynamic content to settle
238
+ await p.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {});
239
+
240
+ const { snapshot, truncated } = await takeSnapshot(p, maxChars);
241
+
242
+ return formatToolResult({
243
+ url: p.url(),
244
+ title: await p.title(),
245
+ snapshot,
246
+ truncated,
247
+ refCount: currentRefs.size,
248
+ refs: Object.fromEntries(currentRefs),
249
+ hint: 'Use act with kind="click" and ref="eN" to click elements. Or navigate directly to a /url visible in the snapshot.',
250
+ });
251
+ }
252
+
253
+ case 'act': {
254
+ if (!request) {
255
+ return formatToolResult({ error: 'request is required for act action' });
256
+ }
257
+
258
+ const p = await ensureBrowser();
259
+ const { kind, ref, text, key, direction, timeMs } = request;
260
+
261
+ switch (kind) {
262
+ case 'click': {
263
+ if (!ref) {
264
+ return formatToolResult({ error: 'ref is required for click' });
265
+ }
266
+ const locator = resolveRefToLocator(p, ref);
267
+ await locator.click({ timeout: 8000 });
268
+ // Wait for navigation/content to load
269
+ await p.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
270
+ return formatToolResult({
271
+ ok: true,
272
+ clicked: ref,
273
+ hint: 'Click successful. Call snapshot to see the updated page.',
274
+ });
275
+ }
276
+
277
+ case 'type': {
278
+ if (!ref) {
279
+ return formatToolResult({ error: 'ref is required for type' });
280
+ }
281
+ if (!text) {
282
+ return formatToolResult({ error: 'text is required for type' });
283
+ }
284
+ const locator = resolveRefToLocator(p, ref);
285
+ await locator.fill(text, { timeout: 8000 });
286
+ return formatToolResult({ ok: true, ref, typed: text });
287
+ }
288
+
289
+ case 'press': {
290
+ if (!key) {
291
+ return formatToolResult({ error: 'key is required for press' });
292
+ }
293
+ await p.keyboard.press(key);
294
+ await p.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {});
295
+ return formatToolResult({ ok: true, pressed: key });
296
+ }
297
+
298
+ case 'hover': {
299
+ if (!ref) {
300
+ return formatToolResult({ error: 'ref is required for hover' });
301
+ }
302
+ const locator = resolveRefToLocator(p, ref);
303
+ await locator.hover({ timeout: 8000 });
304
+ return formatToolResult({ ok: true, hovered: ref });
305
+ }
306
+
307
+ case 'scroll': {
308
+ const scrollDirection = direction ?? 'down';
309
+ const amount = scrollDirection === 'down' ? 500 : -500;
310
+ await p.mouse.wheel(0, amount);
311
+ await p.waitForTimeout(500);
312
+ return formatToolResult({ ok: true, scrolled: scrollDirection });
313
+ }
314
+
315
+ case 'wait': {
316
+ const waitTime = Math.min(timeMs ?? 2000, 10000);
317
+ await p.waitForTimeout(waitTime);
318
+ return formatToolResult({ ok: true, waited: waitTime });
319
+ }
320
+
321
+ default:
322
+ return formatToolResult({ error: `Unknown act kind: ${kind}` });
323
+ }
324
+ }
325
+
326
+ case 'read': {
327
+ const p = await ensureBrowser();
328
+ // Wait for content to be fully loaded
329
+ await p.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {});
330
+
331
+ // Extract visible text from main content area, falling back to body
332
+ const content = await p.evaluate(() => {
333
+ const main = document.querySelector('main, article, [role="main"], .content, #content') as HTMLElement | null;
334
+ return (main || document.body).innerText;
335
+ });
336
+ return formatToolResult({
337
+ url: p.url(),
338
+ title: await p.title(),
339
+ content,
340
+ });
341
+ }
342
+
343
+ case 'close': {
344
+ await closeBrowser();
345
+ return formatToolResult({ ok: true, message: 'Browser closed' });
346
+ }
347
+
348
+ default:
349
+ return formatToolResult({ error: `Unknown action: ${action}` });
350
+ }
351
+ } catch (err) {
352
+ const message = err instanceof Error ? err.message : String(err);
353
+ logger.error(`[Browser (Playwright)] error: ${message}`);
354
+ return formatToolResult({ error: `[Browser (Playwright)] ${message}` });
355
+ }
356
+ },
357
+ });
@@ -0,0 +1 @@
1
+ export { browserTool } from './browser.js';
@@ -0,0 +1,171 @@
1
+ /**
2
+ * LangChain-compatible tool wrappers for Hive MCP crypto endpoints.
3
+ *
4
+ * Each tool wraps a Hive MCP call behind a DynamicStructuredTool with
5
+ * Zod validation, MCP caching, and progress reporting.
6
+ *
7
+ * 12 tools total:
8
+ * - get_api_endpoint_schema: get schema for any endpoint
9
+ * - invoke_api_endpoint: invoke any endpoint with args
10
+ * - 10 category discovery tools (one per Hive MCP category)
11
+ */
12
+ import { DynamicStructuredTool } from '@langchain/core/tools';
13
+ import { z } from 'zod';
14
+ import { callHiveTool } from '../../mcp/client.js';
15
+ import { readMCPCache, writeMCPCache } from '../../mcp/cache.js';
16
+
17
+ // ============================================================================
18
+ // Helper: cached MCP call
19
+ // ============================================================================
20
+
21
+ /**
22
+ * Check if a result looks like an error response that should NOT be cached.
23
+ */
24
+ function isErrorResponse(result: string): boolean {
25
+ const lower = result.toLowerCase().trim();
26
+ if (lower.startsWith('error:') || lower.startsWith('error -')) return true;
27
+ if (lower.startsWith('**error:**') || lower.startsWith('**error**')) return true;
28
+ if (lower.startsWith('{"error"') || lower.startsWith('{"message":"error')) return true;
29
+ if (result.length < 80 && (lower.includes('not found') || lower.includes('invalid') || lower.includes('failed'))) return true;
30
+ return false;
31
+ }
32
+
33
+ async function cachedHiveCall(
34
+ toolName: string,
35
+ args: Record<string, unknown>,
36
+ ): Promise<string> {
37
+ // Check cache first
38
+ const cached = readMCPCache(toolName, args);
39
+ if (cached) return cached;
40
+
41
+ const result = await callHiveTool(toolName, args);
42
+
43
+ // Only cache successful responses — never cache errors
44
+ if (!isErrorResponse(result)) {
45
+ writeMCPCache(toolName, args, result);
46
+ }
47
+
48
+ return result;
49
+ }
50
+
51
+ // ============================================================================
52
+ // Category discovery tools
53
+ // ============================================================================
54
+
55
+ interface CategoryToolDef {
56
+ hiveName: string;
57
+ langchainName: string;
58
+ description: string;
59
+ }
60
+
61
+ const CATEGORY_TOOLS: CategoryToolDef[] = [
62
+ {
63
+ hiveName: 'get_market_and_price_endpoints',
64
+ langchainName: 'get_market_and_price_endpoints',
65
+ description: 'List available market data and price endpoints. Use for: token prices, market caps, OHLCV charts, trading volume, price history, trending tokens.',
66
+ },
67
+ {
68
+ hiveName: 'get_onchain_dex_pool_endpoints',
69
+ langchainName: 'get_onchain_dex_pool_endpoints',
70
+ description: 'List available on-chain DEX and pool endpoints. Use for: DEX trades, liquidity pools, pool analytics, swap data, AMM metrics.',
71
+ },
72
+ {
73
+ hiveName: 'get_portfolio_wallet_endpoints',
74
+ langchainName: 'get_portfolio_wallet_endpoints',
75
+ description: 'List available portfolio and wallet endpoints. Use for: wallet balances, token holdings, transaction history, portfolio tracking, wallet PnL.',
76
+ },
77
+ {
78
+ hiveName: 'get_token_contract_endpoints',
79
+ langchainName: 'get_token_contract_endpoints',
80
+ description: 'List available token and contract endpoints. Use for: token metadata, contract info, token holders, supply data, token details.',
81
+ },
82
+ {
83
+ hiveName: 'get_defi_protocol_endpoints',
84
+ langchainName: 'get_defi_protocol_endpoints',
85
+ description: 'List available DeFi protocol endpoints. Use for: TVL data, protocol analytics, yield farming, lending rates, DeFi protocol comparisons.',
86
+ },
87
+ {
88
+ hiveName: 'get_nft_analytics_endpoints',
89
+ langchainName: 'get_nft_analytics_endpoints',
90
+ description: 'List available NFT analytics endpoints. Use for: NFT collections, floor prices, NFT sales, collection rankings, NFT market data.',
91
+ },
92
+ {
93
+ hiveName: 'get_security_risk_endpoints',
94
+ langchainName: 'get_security_risk_endpoints',
95
+ description: 'List available security and risk endpoints. Use for: token security audits, honeypot detection, rug pull checks, contract risk assessment. ALWAYS use for unknown tokens.',
96
+ },
97
+ {
98
+ hiveName: 'get_network_infrastructure_endpoints',
99
+ langchainName: 'get_network_infrastructure_endpoints',
100
+ description: 'List available network and infrastructure endpoints. Use for: gas prices, network stats, block data, chain comparisons, network health.',
101
+ },
102
+ {
103
+ hiveName: 'get_search_discovery_endpoints',
104
+ langchainName: 'get_search_discovery_endpoints',
105
+ description: 'List available search and discovery endpoints. Use for: token search by name/symbol, trending tokens, new listings, discovery tools.',
106
+ },
107
+ {
108
+ hiveName: 'get_social_sentiment_endpoints',
109
+ langchainName: 'get_social_sentiment_endpoints',
110
+ description: 'List available social and sentiment endpoints. Use for: social media metrics, sentiment analysis, community activity, influencer tracking.',
111
+ },
112
+ ];
113
+
114
+ function createCategoryTool(def: CategoryToolDef): DynamicStructuredTool {
115
+ return new DynamicStructuredTool({
116
+ name: def.langchainName,
117
+ description: def.description,
118
+ schema: z.object({}),
119
+ func: async () => {
120
+ return cachedHiveCall(def.hiveName, {});
121
+ },
122
+ });
123
+ }
124
+
125
+ // ============================================================================
126
+ // Schema tool
127
+ // ============================================================================
128
+
129
+ const getApiEndpointSchemaTool = new DynamicStructuredTool({
130
+ name: 'get_api_endpoint_schema',
131
+ description:
132
+ 'Get the parameter schema for any Hive MCP endpoint by name. Call this after discovering endpoints via a category tool to learn what arguments an endpoint accepts before invoking it.',
133
+ schema: z.object({
134
+ endpoint_name: z.string().describe('The exact endpoint name to get the schema for (e.g., "simple_price_browser", "portfolio_wallet")'),
135
+ }),
136
+ func: async (input) => {
137
+ return cachedHiveCall('get_api_endpoint_schema', { endpoint: input.endpoint_name });
138
+ },
139
+ });
140
+
141
+ // ============================================================================
142
+ // Invoke tool
143
+ // ============================================================================
144
+
145
+ const invokeApiEndpointTool = new DynamicStructuredTool({
146
+ name: 'invoke_api_endpoint',
147
+ description:
148
+ 'Invoke any Hive MCP endpoint with arguments. This is the primary data-fetching tool. Pass the endpoint_name and its required arguments (from the schema). Returns live crypto data.',
149
+ schema: z.object({
150
+ endpoint_name: z.string().describe('The endpoint to invoke (e.g., "simple_price_browser", "coins_market_data_browser")'),
151
+ arguments: z.record(z.string(), z.unknown()).optional().describe('Arguments for the endpoint as a JSON object (keys and values from the schema)'),
152
+ }),
153
+ func: async (input) => {
154
+ return cachedHiveCall('invoke_api_endpoint', {
155
+ endpoint_name: input.endpoint_name,
156
+ args: input.arguments ?? {},
157
+ });
158
+ },
159
+ });
160
+
161
+ // ============================================================================
162
+ // Export all tools
163
+ // ============================================================================
164
+
165
+ export function createCryptoTools(): DynamicStructuredTool[] {
166
+ return [
167
+ getApiEndpointSchemaTool,
168
+ invokeApiEndpointTool,
169
+ ...CATEGORY_TOOLS.map(createCategoryTool),
170
+ ];
171
+ }
@@ -0,0 +1 @@
1
+ export { createCryptoTools } from './hive-tools.js';
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Rich description for the browser tool.
3
+ * Used in the system prompt to guide the LLM on when and how to use this tool.
4
+ */
5
+ export const BROWSER_DESCRIPTION = `
6
+ Control a web browser to navigate websites and extract information.
7
+
8
+ **NOTE: For simply reading a web page's content, prefer web_fetch which returns content directly in a single call. Use browser only for interactive tasks requiring JavaScript rendering, clicking, or form filling.**
9
+
10
+ ## When to Use
11
+
12
+ - Accessing dynamic/JavaScript-rendered content that requires a real browser
13
+ - Multi-step web navigation (click links, fill search boxes)
14
+ - Interacting with SPAs or pages that require JavaScript to load content
15
+ - When web_fetch fails or returns incomplete content due to JS-dependent rendering
16
+
17
+ ## When NOT to Use
18
+
19
+ - Reading static web pages or articles (use **web_fetch** instead — it is faster and returns content in a single call)
20
+ - Simple queries that web_search can already answer
21
+ - General knowledge questions
22
+
23
+ ## CRITICAL: Navigate Returns NO Content
24
+
25
+ The \`navigate\` action only loads the page - it does NOT return page content.
26
+ You MUST call \`snapshot\` after navigate to see what's on the page.
27
+
28
+ ## CRITICAL: Use Visible URLs - Do NOT Guess
29
+
30
+ When the snapshot shows a link with a URL (e.g., \`/url: https://...\`):
31
+ 1. **Option A**: Click the link using its ref (e.g., act with kind="click", ref="e22")
32
+ 2. **Option B**: Navigate directly to the URL shown in the snapshot
33
+
34
+ **NEVER make up or guess URLs based on common patterns**. If you need to reach a page:
35
+ 1. Take a snapshot
36
+ 2. Find the link in the snapshot
37
+ 3. Either click it OR navigate to its visible /url value
38
+
39
+ Bad: Guessing https://company.com/news-events/press-releases
40
+ Good: Using the /url value you SEE in the snapshot
41
+
42
+ ## Available Actions
43
+
44
+ - **navigate** - Navigate to a URL in the current tab (returns only url/title, no content)
45
+ - **open** - Open a URL in a NEW tab (use when starting a fresh browsing session)
46
+ - **snapshot** - See page structure with clickable refs (e.g., e1, e2, e3)
47
+ - **act** - Interact with elements using refs (click, type, press, scroll)
48
+ - **read** - Extract full text content from the page
49
+ - **close** - Free browser resources when done
50
+
51
+ ## Workflow (MUST FOLLOW)
52
+
53
+ 1. **navigate** or **open** - Load a URL (returns only url/title, no content)
54
+ 2. **snapshot** - See page structure with clickable refs (e.g., e1, e2, e3)
55
+ 3. **act** - Interact with elements using refs:
56
+ - kind="click", ref="e5" - Click a link/button
57
+ - kind="type", ref="e3", text="search query" - Type in an input
58
+ - kind="press", key="Enter" - Press a key
59
+ - kind="scroll", direction="down" - Scroll the page
60
+ 4. **snapshot** again - See updated page after interaction
61
+ 5. **Repeat steps 3-4** until you find the content you need
62
+ 6. **read** - Extract full text content from the page
63
+ 7. **close** - Free browser resources when done
64
+
65
+ ## Snapshot Format
66
+
67
+ The snapshot returns an AI-optimized accessibility tree with refs:
68
+ - navigation [ref=e1]:
69
+ - link "Home" [ref=e2]
70
+ - link "Investors" [ref=e3]
71
+ - link "Press Releases" [ref=e4]
72
+ - main:
73
+ - heading "Welcome to Acme Protocol" [ref=e5]
74
+ - paragraph: Latest news and updates
75
+ - link "Governance Proposal #42" [ref=e6]
76
+ - link "View All Updates" [ref=e7]
77
+
78
+ ## Act Action Examples
79
+
80
+ To click a link with ref=e4:
81
+ action="act", request with kind="click" and ref="e4"
82
+
83
+ To type in a search box with ref=e10:
84
+ action="act", request with kind="type", ref="e10", text="aave tvl"
85
+
86
+ To press Enter:
87
+ action="act", request with kind="press" and key="Enter"
88
+
89
+ ## Example: Reading a Protocol's Documentation
90
+
91
+ 1. navigate to https://docs.aave.com
92
+ 2. snapshot - see links like "Getting Started" [ref=e4]
93
+ 3. act with kind="click", ref="e4" - click Getting Started link
94
+ 4. snapshot - see documentation content
95
+ 5. read - extract the full page text
96
+
97
+ ## Usage Notes
98
+
99
+ - Always call snapshot after navigate/open - they return only url/title, no content
100
+ - Use **open** to start a fresh tab; use **navigate** to go to a URL within the current tab
101
+ - After clicking, always call snapshot again to see the new page
102
+ - The browser persists across calls - no need to re-navigate to the same URL
103
+ - Use read for bulk text extraction once you've navigated to the right page
104
+ - Close the browser when done to free system resources
105
+ `.trim();