diff --git a/apps/shinkai-tool-perplexity-api/jest.config.ts b/apps/shinkai-tool-perplexity-api/jest.config.ts new file mode 100644 index 0000000..7c78812 --- /dev/null +++ b/apps/shinkai-tool-perplexity-api/jest.config.ts @@ -0,0 +1,6 @@ +/* eslint-disable */ +export default { + displayName: '@shinkai_protocol/shinkai-tool-perplexity-api', + preset: '../../jest.preset.js', + coverageDirectory: '../../coverage/apps/shinkai-tool-perplexity-api' +}; diff --git a/apps/shinkai-tool-perplexity-api/package.json b/apps/shinkai-tool-perplexity-api/package.json new file mode 100644 index 0000000..b736e6a --- /dev/null +++ b/apps/shinkai-tool-perplexity-api/package.json @@ -0,0 +1,4 @@ +{ + "name": "@shinkai_protocol/shinkai-tool-perplexity-api", + "type": "commonjs" +} diff --git a/apps/shinkai-tool-perplexity-api/project.json b/apps/shinkai-tool-perplexity-api/project.json new file mode 100644 index 0000000..6e00a7e --- /dev/null +++ b/apps/shinkai-tool-perplexity-api/project.json @@ -0,0 +1,30 @@ +{ + "name": "@shinkai_protocol/shinkai-tool-perplexity-api", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/shinkai-tool-perplexity-api/src", + "projectType": "library", + "tags": ["tool"], + "targets": { + "build": { + "executor": "nx:run-commands", + "defaultConfiguration": "production", + "options": { + "command": "npx ts-node scripts/tool-bundler.ts --entry ./apps/shinkai-tool-perplexity-api/src/index.ts --outputFolder ./dist/apps/shinkai-tool-perplexity-api" + }, + "configurations": { + "development": {}, + "production": {} + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "apps/shinkai-tool-perplexity-api/**/*.ts", + "apps/shinkai-tool-perplexity-api/package.json" + ] + } + } + } +} diff --git a/apps/shinkai-tool-perplexity-api/src/index.test.ts b/apps/shinkai-tool-perplexity-api/src/index.test.ts new file mode 100644 index 0000000..5b8e7d1 --- /dev/null +++ b/apps/shinkai-tool-perplexity-api/src/index.test.ts @@ -0,0 +1,7 @@ +import { Tool } from '../src/index'; + +test('exists definition', () => { + const tool = new Tool({ apiKey: 'pplx-XXX' }); + const definition = tool.getDefinition(); + expect(definition).toBeInstanceOf(Object); +}); diff --git a/apps/shinkai-tool-perplexity-api/src/index.ts b/apps/shinkai-tool-perplexity-api/src/index.ts new file mode 100644 index 0000000..e08bd91 --- /dev/null +++ b/apps/shinkai-tool-perplexity-api/src/index.ts @@ -0,0 +1,84 @@ +import { BaseTool, RunResult } from '@shinkai_protocol/shinkai-tools-builder'; +import { ToolDefinition } from 'libs/shinkai-tools-builder/src/tool-definition'; +import TurndownService from 'turndown'; +import axios from 'axios'; + +type Config = { + apiKey: string; +}; +type Params = { + query: string; +}; + +type Result = { + response: string; +}; + +interface PerplexityResponse { + choices: Array<{ + message: { + content: string; + }; + }>; +} + +export class Tool extends BaseTool { + definition: ToolDefinition = { + id: 'shinkai-tool-perplexity-api', + name: 'Shinkai: Perplexity API', + description: 'Searches the web using Perplexity API (limited)', + author: 'Shinkai', + keywords: ['perplexity', 'api', 'shinkai'], + configurations: { + type: 'object', + properties: { + apiKey: { type: 'string' }, + }, + required: ['apiKey'], + }, + parameters: { + type: 'object', + properties: { + query: { type: 'string' }, + }, + required: ['query'], + }, + result: { + type: 'object', + properties: { + response: { type: 'string' }, + }, + required: ['response'], + }, + }; + + async run(params: Params): Promise> { + const response = await fetch('https://api.perplexity.ai/chat/completions', { + method: 'POST', + headers: { + accept: 'application/json', + authorization: `Bearer ${this.config.apiKey}`, + 'content-type': 'application/json', + }, + body: JSON.stringify({ + model: 'llama-3.1-sonar-small-128k-online', + messages: [ + { role: 'system', content: 'Be precise and concise.' }, + { role: 'user', content: params.query }, + ], + }), + }); + + if (!response.ok) { + throw new Error( + `Failed to fetch data from Perplexity API, status: ${response.status}`, + ); + } + + const data = (await response.json()) as PerplexityResponse; + const responseContent = + data.choices[0]?.message?.content || 'No information available'; + + return { data: { response: responseContent } }; + } +} diff --git a/apps/shinkai-tool-perplexity-api/tsconfig.app.json b/apps/shinkai-tool-perplexity-api/tsconfig.app.json new file mode 100644 index 0000000..b139ef3 --- /dev/null +++ b/apps/shinkai-tool-perplexity-api/tsconfig.app.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["./src/**/*.ts"] +} diff --git a/apps/shinkai-tool-perplexity-api/tsconfig.json b/apps/shinkai-tool-perplexity-api/tsconfig.json new file mode 100644 index 0000000..61cc7dc --- /dev/null +++ b/apps/shinkai-tool-perplexity-api/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": {}, + "include": ["./src/**/*.ts"] +} diff --git a/apps/shinkai-tool-perplexity-api/tsconfig.spec.json b/apps/shinkai-tool-perplexity-api/tsconfig.spec.json new file mode 100644 index 0000000..2f36ef0 --- /dev/null +++ b/apps/shinkai-tool-perplexity-api/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "test/**/*.test.ts", + "test/**/*.spec.ts", + "test/**/*.d.ts" + ] +} diff --git a/apps/shinkai-tool-perplexity/jest.config.ts b/apps/shinkai-tool-perplexity/jest.config.ts new file mode 100644 index 0000000..d850bd6 --- /dev/null +++ b/apps/shinkai-tool-perplexity/jest.config.ts @@ -0,0 +1,6 @@ +/* eslint-disable */ +export default { + displayName: '@shinkai_protocol/shinkai-tool-perplexity', + preset: '../../jest.preset.js', + coverageDirectory: '../../coverage/apps/shinkai-tool-perplexity', +}; diff --git a/apps/shinkai-tool-perplexity/package.json b/apps/shinkai-tool-perplexity/package.json new file mode 100644 index 0000000..56b33a9 --- /dev/null +++ b/apps/shinkai-tool-perplexity/package.json @@ -0,0 +1,4 @@ +{ + "name": "@shinkai_protocol/shinkai-tool-perplexity", + "type": "commonjs" +} diff --git a/apps/shinkai-tool-perplexity/project.json b/apps/shinkai-tool-perplexity/project.json new file mode 100644 index 0000000..fb2c482 --- /dev/null +++ b/apps/shinkai-tool-perplexity/project.json @@ -0,0 +1,30 @@ +{ + "name": "@shinkai_protocol/shinkai-tool-perplexity", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/shinkai-tool-perplexity/src", + "projectType": "library", + "tags": ["tool"], + "targets": { + "build": { + "executor": "nx:run-commands", + "defaultConfiguration": "production", + "options": { + "command": "npx ts-node scripts/tool-bundler.ts --entry ./apps/shinkai-tool-perplexity/src/index.ts --outputFolder ./dist/apps/shinkai-tool-perplexity" + }, + "configurations": { + "development": {}, + "production": {} + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "apps/shinkai-tool-perplexity/**/*.ts", + "apps/shinkai-tool-perplexity/package.json" + ] + } + } + } +} diff --git a/apps/shinkai-tool-perplexity/src/chrome-paths.d.ts b/apps/shinkai-tool-perplexity/src/chrome-paths.d.ts new file mode 100644 index 0000000..9b369ed --- /dev/null +++ b/apps/shinkai-tool-perplexity/src/chrome-paths.d.ts @@ -0,0 +1,3 @@ +declare module 'chrome-paths' { + const chrome: string; +} diff --git a/apps/shinkai-tool-perplexity/src/index.test.ts b/apps/shinkai-tool-perplexity/src/index.test.ts new file mode 100644 index 0000000..6fe0e9c --- /dev/null +++ b/apps/shinkai-tool-perplexity/src/index.test.ts @@ -0,0 +1,15 @@ +import { Tool } from '../src/index'; + +test('exists definition', async () => { + const tool = new Tool({}); + const definition = tool.getDefinition(); + expect(definition).toBeInstanceOf(Object); +}); + +test('run', async () => { + const tool = new Tool({ + chromePath: process.env?.CHROME_PATH, + }); + const run_result = await tool.run({ query: 'What is the meaning of life?' }); + expect(run_result.data.response).toEqual(expect.any(String)); +}, 15000); diff --git a/apps/shinkai-tool-perplexity/src/index.ts b/apps/shinkai-tool-perplexity/src/index.ts new file mode 100644 index 0000000..d79e9c5 --- /dev/null +++ b/apps/shinkai-tool-perplexity/src/index.ts @@ -0,0 +1,107 @@ +import { BaseTool, RunResult } from '@shinkai_protocol/shinkai-tools-builder'; +import { ToolDefinition } from 'libs/shinkai-tools-builder/src/tool-definition'; +import * as playwright from 'playwright'; +import * as chromePaths from 'chrome-paths'; +import TurndownService = require('turndown'); + +type Config = { + chromePath?: string; +}; +type Params = { + query: string; +}; +type Result = { response: string }; + +export class Tool extends BaseTool { + definition: ToolDefinition = { + id: 'shinkai-tool-perplexity', + name: 'Shinkai: Perplexity', + description: 'Searches the internet using Perplexity', + author: 'Shinkai', + keywords: ['perplexity', 'shinkai'], + configurations: { + type: 'object', + properties: { + chromePath: { type: 'string', nullable: true }, + }, + required: [], + }, + parameters: { + type: 'object', + properties: { + query: { type: 'string' }, + }, + required: ['query'], + }, + result: { + type: 'object', + properties: { + response: { type: 'string' }, + }, + required: ['response'], + }, + }; + + async run(params: Params): Promise> { + const browser = await playwright['chromium'].launch({ + executablePath: this.config?.chromePath || chromePaths.chrome, + // headless: false, + }); + const context = await browser.newContext({ + viewport: { width: 1280, height: 800 }, // Set viewport size + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36', // Set Mac user agent + }); + const page = await context.newPage(); + + console.log("Navigating to Perplexity's website..."); + await page.goto('https://www.perplexity.ai/'); + + console.log('Waiting for the page to load...'); + await page.waitForTimeout(2500); + + console.log('Filling textarea with query:', params.query); + await page.fill('textarea', params.query); + + console.log('Clicking the button with the specified SVG...'); + await page.click('button:has(svg[data-icon="arrow-right"])'); + + console.log( + 'Waiting for the button with the specified SVG to be visible...', + ); + await page.waitForSelector('button:has(svg[data-icon="arrow-right"])'); + + console.log('Waiting for results to load...'); + await page.waitForSelector('div:has-text("Related")'); + + console.log('Extracting HTML content...'); + const htmlContent = await page.evaluate(() => { + const resultElements = document.querySelectorAll('div[dir="auto"]'); + return Array.from(resultElements) + .map((element) => element.innerHTML) + .join('\n\n'); + }); + + console.log('Closing browser...'); + await browser.close(); + + console.log('Converting HTML to Markdown...'); + const turndownService = new TurndownService(); + turndownService.addRule('preserveLinks', { + filter: 'a', + replacement: function (content, node) { + const element = node as Element; + const href = element.getAttribute('href'); + return `[${content}](${href})`; + }, + }); + const markdown = turndownService.turndown(htmlContent); + + const result: Result = { + response: markdown, + }; + + console.log('Returning result:', result); + return Promise.resolve({ data: result }); + } +} diff --git a/apps/shinkai-tool-perplexity/tsconfig.app.json b/apps/shinkai-tool-perplexity/tsconfig.app.json new file mode 100644 index 0000000..b139ef3 --- /dev/null +++ b/apps/shinkai-tool-perplexity/tsconfig.app.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["./src/**/*.ts"] +} diff --git a/apps/shinkai-tool-perplexity/tsconfig.json b/apps/shinkai-tool-perplexity/tsconfig.json new file mode 100644 index 0000000..61cc7dc --- /dev/null +++ b/apps/shinkai-tool-perplexity/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": {}, + "include": ["./src/**/*.ts"] +} diff --git a/apps/shinkai-tool-perplexity/tsconfig.spec.json b/apps/shinkai-tool-perplexity/tsconfig.spec.json new file mode 100644 index 0000000..6cfffe7 --- /dev/null +++ b/apps/shinkai-tool-perplexity/tsconfig.spec.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"], + "lib": ["dom"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/libs/shinkai-tools-runner/src/built_in_tools.rs b/libs/shinkai-tools-runner/src/built_in_tools.rs index ca8bbd9..4891f5b 100644 --- a/libs/shinkai-tools-runner/src/built_in_tools.rs +++ b/libs/shinkai-tools-runner/src/built_in_tools.rs @@ -206,6 +206,25 @@ lazy_static! { ))) .unwrap(), )), + m.insert( + "shinkai-tool-perplexity", + &*Box::leak(Box::new( + serde_json::from_str::(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tools/shinkai-tool-perplexity/definition.json" + ))) + .unwrap(), + )), + ); + m.insert( + "shinkai-tool-perplexity-api", + &*Box::leak(Box::new( + serde_json::from_str::(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tools/shinkai-tool-perplexity-api/definition.json" + ))) + .unwrap(), + )), ); m.insert( "shinkai-tool-youtube-summary", diff --git a/package-lock.json b/package-lock.json index 96bd718..07e0d11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@shinkai_protocol/source", - "version": "0.7.10", + "version": "0.7.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@shinkai_protocol/source", - "version": "0.7.10", + "version": "0.7.11", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@coinbase/coinbase-sdk": "^0.0.16", diff --git a/package.json b/package.json index af46c72..762f81c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shinkai_protocol/source", - "version": "0.7.10", + "version": "0.7.11", "description": "This repository serves as the ecosystem to execute Shinkai tools, provided by the Shinkai team or third-party developers, in a secure environment. It provides a sandboxed space for executing these tools, ensuring that they run safely and efficiently, while also allowing for seamless integration with Rust code.", "main": "index.js", "author": "",