From d7592b9167d41a0a47a22681a35e25ce212e94ec Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 31 May 2026 09:25:46 +0000 Subject: [PATCH 1/2] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/Payel-git-ol/Tradefast/issues/35 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..8cb6950 --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-05-31T09:25:46.839Z for PR creation at branch issue-35-3aa024e2f5f5 for issue https://github.com/Payel-git-ol/Tradefast/issues/35 \ No newline at end of file From df179c37fcd53066e94d53882b3c3ca17ab4d408 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 31 May 2026 09:32:07 +0000 Subject: [PATCH 2/2] Enable AI chat web search --- .gitkeep | 1 - README.md | 3 ++ src/cli/App.tsx | 18 +++++++++++ src/services/chat.ts | 18 +++++++++++ tests/cli.test.tsx | 73 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 1 deletion(-) delete mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index 8cb6950..0000000 --- a/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# .gitkeep file auto-generated at 2026-05-31T09:25:46.839Z for PR creation at branch issue-35-3aa024e2f5f5 for issue https://github.com/Payel-git-ol/Tradefast/issues/35 \ No newline at end of file diff --git a/README.md b/README.md index 7210ab4..1b6b66f 100644 --- a/README.md +++ b/README.md @@ -443,6 +443,9 @@ exposes these tools to the AI: toggle specific groups (e.g. enable only `economic-calendars` for fundamentals) or the whole-internet `web-search` platform to tailor research scope. +- **`run_web_search`** — searches the public Internet directly from chat for + current rates, news, fiat/forex pairs, or anything outside the tracked crypto + symbols, then uses those results to answer the user. - **`run_ratings_adjust`** — modifies a source's credibility rating. Parameters: `source` (title or ID) and `grade` (integer, e.g. `-1` = −1%). Triggered by user feedback like *"Хабр -1"* or *"понизь рейтинг Хабру"*. diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 8db3fb4..010547e 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -735,6 +735,24 @@ export function App({ app, version, apiUrl, promptOperatingMode }: AppProps): Re openCurrencySelector(); return 'Currency selector opened — pick a currency.'; } + case 'run_web_search': { + const query = ((toolArgs.query as string) || '').trim(); + const requestedLimit = typeof toolArgs.limit === 'number' + ? toolArgs.limit + : Number(toolArgs.limit); + const limit = Number.isFinite(requestedLimit) && requestedLimit > 0 + ? Math.min(10, Math.floor(requestedLimit)) + : 5; + if (!query) return 'Error: query is required for web search.'; + const results = await app.search(query, limit); + return JSON.stringify(results.map((result) => ({ + title: result.title, + url: result.url, + snippet: result.snippet, + source: result.source, + score: result.score, + }))); + } case 'run_help': { const text = COMMANDS.map(c => ` ${c.name.padEnd(12)} ${c.summary}`).join('\n'); return `Available commands:\n${text}`; diff --git a/src/services/chat.ts b/src/services/chat.ts index 29e06fe..7e72342 100644 --- a/src/services/chat.ts +++ b/src/services/chat.ts @@ -165,6 +165,21 @@ const TOOL_DEFINITIONS: { type: string; function: Record }[] = }, }, }, + { + type: 'function', + function: { + name: 'run_web_search', + description: 'Search the public Internet for current information, exchange rates, news, or topics outside the tracked crypto symbols.', + parameters: { + type: 'object', + properties: { + query: { type: 'string', description: 'Search query, e.g. "RUB/USD exchange rate today"' }, + limit: { type: 'integer', description: 'Maximum number of results to return (1-10). Defaults to 5.' }, + }, + required: ['query'], + }, + }, + }, { type: 'function', function: { @@ -253,12 +268,15 @@ const SYSTEM_PROMPT = `Ты — AI-ассистент торговой сист - get_timeframe — узнать текущий таймфрейм - get_mode — узнать текущий режим торговли - get_currency — узнать список отслеживаемых символов +- run_web_search — найти актуальную информацию в интернете, включая фиатные/форекс-курсы, новости и темы вне отслеживаемых криптосимволов - run_serching_level — установить глубину поиска (normal / high / max) - run_serching_platforms — выбрать группы источников для краулинга - run_ratings_adjust — изменить рейтинг источника (grade: -1 = -1%, +5 = +5%). Нужен source (название или ID) и grade (целое число). ВАЖНО ПРО РЕЙТИНГИ: Когда пользователь говорит что-то вроде "Хабр -1", "понизь Хабр", "Хабр плохой источник", "уменьши рейтинг Хабру" или аналогично — используй run_ratings_adjust. Параметр source — это название источника (например "Хабр", "Investing.com", "Reddit CryptoCurrency"), а grade — это целое число со знаком: -1, -2, +2, +5 и т.д. Если пользователь не указал конкретное число, спроси или используй -1 по умолчанию для понижения и +1 для повышения. +Если пользователь просит актуальный курс, свежие новости, данные по фиатным/форекс-парам или другой информации, которой нет среди отслеживаемых символов — сначала используй run_web_search, а затем отвечай по найденным результатам. Не отказывайся только из-за того, что пара не входит в криптовалютные символы Tradefast. + Если пользователь просит сделать анализ рынка, показать статус или что-то подобное — используй соответствующий инструмент. Прежде чем спрашивать пользователя о текущих настройках — сначала сам проверь их через get_* инструменты. Когда ты выполняешь команду — результат возвращается тебе, ты сам его анализируешь и объясняешь пользователю. Дополнительный запрос на валидацию не нужен. diff --git a/tests/cli.test.tsx b/tests/cli.test.tsx index 60eeb6b..893da06 100644 --- a/tests/cli.test.tsx +++ b/tests/cli.test.tsx @@ -248,6 +248,79 @@ describe('command autocomplete', () => { expect(lastFrame()).toContain('type a command'); unmount(); }); + + it('lets AI chat search the internet directly for current non-tracked rates', async () => { + const previousKey = process.env.TRADEFAST_AI_API_KEY; + const previousUrl = process.env.TRADEFAST_AI_API_URL; + process.env.TRADEFAST_AI_API_KEY = 'test-key'; + process.env.TRADEFAST_AI_API_URL = 'http://127.0.0.1/v1/chat/completions'; + + const search = vi.fn().mockResolvedValue([ + { + query: 'RUB/USD exchange rate', + source: 'web-search', + title: 'USD/RUB Exchange Rate', + url: 'https://example.com/rub-usd', + snippet: '1 USD equals 90 RUB', + score: 1, + }, + ]); + const fetchMock = vi + .fn() + .mockResolvedValueOnce(new Response(JSON.stringify({ + choices: [ + { + message: { + content: null, + tool_calls: [ + { + id: 'call_1', + type: 'function', + function: { + name: 'run_web_search', + arguments: JSON.stringify({ query: 'RUB/USD exchange rate', limit: 3 }), + }, + }, + ], + }, + }, + ], + }))) + .mockResolvedValueOnce(new Response(JSON.stringify({ + choices: [{ message: { content: 'По веб-поиску: 1 USD примерно равен 90 RUB.' } }], + }))); + vi.stubGlobal('fetch', fetchMock); + + const tallStdout = { rows: 80, columns: 120, write: () => {}, on: () => {}, removeListener: () => {} } as any; + const app = { ...fakeApp, search } as unknown as Tradefast; + const { lastFrame, stdin, unmount } = render( + , + { stdout: tallStdout }, + ); + + try { + await new Promise((resolve) => setTimeout(resolve, 10)); + for (const ch of 'rub/usd rate') { + stdin.write(ch); + await new Promise((resolve) => setTimeout(resolve, 1)); + } + stdin.write('\r'); + await new Promise((resolve) => setTimeout(resolve, 50)); + + const firstBody = JSON.parse((fetchMock.mock.calls[0]?.[1] as RequestInit).body as string); + const toolNames = firstBody.tools.map((tool: { function: { name: string } }) => tool.function.name); + expect(toolNames).toContain('run_web_search'); + expect(search).toHaveBeenCalledWith('RUB/USD exchange rate', 3); + expect(lastFrame()).toContain('По веб-поиску'); + } finally { + unmount(); + vi.unstubAllGlobals(); + if (previousKey === undefined) delete process.env.TRADEFAST_AI_API_KEY; + else process.env.TRADEFAST_AI_API_KEY = previousKey; + if (previousUrl === undefined) delete process.env.TRADEFAST_AI_API_URL; + else process.env.TRADEFAST_AI_API_URL = previousUrl; + } + }); }); describe('cli themes', () => {