diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b814832..88f4281 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,3 +31,21 @@ jobs: run: pnpm install - name: Run typecheck run: pnpm run typecheck + prettier: + name: Prettier + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: "pnpm" + - name: Install dependencies + run: pnpm install + - name: Run prettier check + run: pnpm run lint diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..bd5535a --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +pnpm-lock.yaml diff --git a/.swcrc b/.swcrc index 8357f08..052dd87 100644 --- a/.swcrc +++ b/.swcrc @@ -15,4 +15,4 @@ } } } -} \ No newline at end of file +} diff --git a/README.md b/README.md index cc52894..c8c5947 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Website -**Athena** is a production-ready general AI agent built to *do*, not just *think*. It bridges insight with execution, helping you move from idea to results effortlessly. +**Athena** is a production-ready general AI agent built to _do_, not just _think_. It bridges insight with execution, helping you move from idea to results effortlessly. Some examples of what Athena can do: @@ -47,16 +47,19 @@ With all the tools it has, Athena is capable of: ## ๐Ÿš€ Quick Start 1. Clone the repository: + ```bash git clone https://github.com/Athena-AI-Lab/athena-core.git ``` 2. [Install pnpm](https://pnpm.io/installation) (if not already installed): + ```bash npm install -g pnpm ``` 3. Install project dependencies: + ```bash cd athena-core pnpm i @@ -64,6 +67,7 @@ pnpx playwright install ``` 4. Copy the example config file: + ```bash cp configs/config.yaml-example configs/config.yaml ``` @@ -99,6 +103,7 @@ plugins: > For a complete list of plugins and detailed configuration options, please refer to the [Configuration Guide](docs/configuration.md). See [Cerebrum](docs/configuration.md#cerebrum) section for best practices on selecting the right model for your use case. 6. Launch Athena: + ```bash pnpm start ``` @@ -117,25 +122,30 @@ Trust us: the AI will probably do a better job explaining it than we ever could. ## ๐Ÿ—“๏ธ Roadmap -Our mission is to realize **human-level intelligence**, or *AGI*, by evolving Athena into a truly autonomous and capable agent. Here's a more detailed roadmap of what we're working on: +Our mission is to realize **human-level intelligence**, or _AGI_, by evolving Athena into a truly autonomous and capable agent. Here's a more detailed roadmap of what we're working on: - [ ] **Autonomous Code Writing** + - Enable Athena to iteratively write and improve its own plugins - [ ] **Robust Browser Automation** + - Improve reliability and fault tolerance in headless and headful modes - Add advanced DOM element parsing and interaction strategies - [ ] **Context Management Improvements** + - Adjust prompt context windows for different LLMs - Implement context summarization for out-of-window context - [ ] **Long-Term Memory with RAG** + - Set up vector database integration for persistent knowledge - Enable memory recall across sessions and tasks - Support user-specific long-term context embedding and retrieval - [ ] **Image and Video Model Expansion** + - Integrate support for more image and video generation models - Enable multimodal workflows that combine text, image, and video reasoning @@ -148,7 +158,7 @@ Our mission is to realize **human-level intelligence**, or *AGI*, by evolving At We welcome contributions from everyone โ€” whether you're fixing a typo, suggesting a feature, or building a whole new plugin! -Athena is a community-driven project, and we believe in building great tools *together*. Here's how you can help: +Athena is a community-driven project, and we believe in building great tools _together_. Here's how you can help: ### ๐Ÿ’ก Got an Idea? @@ -166,6 +176,7 @@ Open a [GitHub Issue](https://github.com/Athena-AI-Lab/athena-core/issues) and l ### ๐Ÿงช Suggest Tests or Improvements Not into code? You can still help by: + - Testing features and reporting issues - Improving documentation - Sharing Athena with others and providing feedback diff --git a/docs/configuration.md b/docs/configuration.md index 9d62b92..0d767be 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -29,8 +29,8 @@ Plugins are the core components of Athena. Each plugin has its own configuration The `browser` plugin allows Athena to control a web browser via Playwright. ```yaml - browser: - headless: true +browser: + headless: true ``` - `headless`: Whether to run the browser in headless mode. If set to `true`, the browser will not be visible. @@ -42,43 +42,43 @@ The `cerebrum` plugin is the main plugin for Athena. It handles event receiving, Here is a configuration example for the cerebrum plugin using GPT-4o: ```yaml - cerebrum: - base_url: https://api.openai.com/v1 - api_key: sk-proj-your-openai-api-key - model: gpt-4o - temperature: 0.5 - image_supported: true - max_prompts: 50 - max_event_strlen: 65536 - max_tokens: 16384 +cerebrum: + base_url: https://api.openai.com/v1 + api_key: sk-proj-your-openai-api-key + model: gpt-4o + temperature: 0.5 + image_supported: true + max_prompts: 50 + max_event_strlen: 65536 + max_tokens: 16384 ``` However, Athena performs best with DeepSeek V3 (not 0324). The web app version of Athena uses DeepSeek V3 as its cerebrum model. Here is an example configuration for DeepSeek V3: ```yaml - cerebrum: - base_url: https://openrouter.ai/api/v1 - api_key: sk-your-openrouter-api-key - model: deepseek/deepseek-chat - temperature: 0.5 - image_supported: false - max_prompts: 50 - max_event_strlen: 65536 - max_tokens: 16384 +cerebrum: + base_url: https://openrouter.ai/api/v1 + api_key: sk-your-openrouter-api-key + model: deepseek/deepseek-chat + temperature: 0.5 + image_supported: false + max_prompts: 50 + max_event_strlen: 65536 + max_tokens: 16384 ``` Although more expensive, Athena can perform even better with Claude 3.7 Sonnet. Here is an example configuration: ```yaml - cerebrum: - base_url: https://api.anthropic.com/v1 - api_key: sk-ant-api03-your-anthropic-api-key - model: claude-3-7-sonnet-latest - temperature: 0.5 - image_supported: false - max_prompts: 50 - max_event_strlen: 65536 - max_tokens: 16384 +cerebrum: + base_url: https://api.anthropic.com/v1 + api_key: sk-ant-api03-your-anthropic-api-key + model: claude-3-7-sonnet-latest + temperature: 0.5 + image_supported: false + max_prompts: 50 + max_event_strlen: 65536 + max_tokens: 16384 ``` - `base_url`: The base URL of the API endpoint. @@ -95,7 +95,7 @@ Although more expensive, Athena can perform even better with Claude 3.7 Sonnet. Enable `cli-ui` to interact with Athena via the command line. If not needed, you can remove it from the `plugins` section. ```yaml - cli-ui: +cli-ui: ``` ### Clock @@ -103,7 +103,7 @@ Enable `cli-ui` to interact with Athena via the command line. If not needed, you The `clock` plugin provides time awareness and scheduling. When enabled, Athena can get the current date and time, and manage timers and alarms. ```yaml - clock: +clock: ``` ### Discord @@ -111,13 +111,13 @@ The `clock` plugin provides time awareness and scheduling. When enabled, Athena Enable `discord` for Athena to send and receive messages from Discord. ```yaml - discord: - bot_token: your-discord-bot-token - allowed_channel_ids: - - "1234567890" - - "9876543210" - admin_channel_ids: [] - log_channel_ids: [] +discord: + bot_token: your-discord-bot-token + allowed_channel_ids: + - "1234567890" + - "9876543210" + admin_channel_ids: [] + log_channel_ids: [] ``` - `bot_token`: The Discord bot token. @@ -130,7 +130,7 @@ Enable `discord` for Athena to send and receive messages from Discord. Enable `file-system` to allow Athena to access your local file system. Athena will be able to read and write files. ```yaml - file-system: +file-system: ``` ### HTTP @@ -138,13 +138,13 @@ Enable `file-system` to allow Athena to access your local file system. Athena wi Enable `http` for Athena to send HTTP requests, search the web via Jina Search or Exa Search, and download files from the Internet. ```yaml - http: - jina: # Optional Jina config - base_url: https://s.jina.ai - api_key: your-jina-api-key - exa: # Optional Exa config - base_url: https://api.exa.ai # Optional, defaults to this - api_key: your-exa-api-key # Required if using Exa +http: + jina: # Optional Jina config + base_url: https://s.jina.ai + api_key: your-jina-api-key + exa: # Optional Exa config + base_url: https://api.exa.ai # Optional, defaults to this + api_key: your-exa-api-key # Required if using Exa ``` - `jina`: Configuration for [Jina Search](https://jina.ai/). (Optional) @@ -159,24 +159,24 @@ Enable `http` for Athena to send HTTP requests, search the web via Jina Search o Enable `llm` for Athena to chat with other language models and generate images. Since only a single OpenAI endpoint can be configured currently, it's recommended to use a service like LiteLLM proxy to route requests to different language models. OpenRouter is another option, though it doesn't support image generation. - + ```yaml - llm: - base_url: https://openrouter.ai/api/v1 - api_key: sk-or-v1-your-openrouter-api-key - models: - chat: - - name: openai/gpt-4o - desc: GPT-4o is good at general purpose tasks. Supports image input. Whenever you receive an image and need to understand it, pass it to this model using the image arg. - - name: openai/o3-mini - desc: O3 Mini is good at deep thinking and planning. Whenever you need to plan something complicated or solve complex math problems, use this model. - - name: anthropic/claude-3.7-sonnet - desc: Claude 3.7 Sonnet is good at writing code. Whenever you need to write code, use this model. - - name: perplexity/sonar - desc: Perplexity can access the Internet. Whenever you need to search the Internet, use this model. - image: - - name: openai/dall-e-3 # OpenRouter doesn't support image generation - desc: DALL-E 3 is good at generating images. Whenever you are requested to generate images, use this model. +llm: + base_url: https://openrouter.ai/api/v1 + api_key: sk-or-v1-your-openrouter-api-key + models: + chat: + - name: openai/gpt-4o + desc: GPT-4o is good at general purpose tasks. Supports image input. Whenever you receive an image and need to understand it, pass it to this model using the image arg. + - name: openai/o3-mini + desc: O3 Mini is good at deep thinking and planning. Whenever you need to plan something complicated or solve complex math problems, use this model. + - name: anthropic/claude-3.7-sonnet + desc: Claude 3.7 Sonnet is good at writing code. Whenever you need to write code, use this model. + - name: perplexity/sonar + desc: Perplexity can access the Internet. Whenever you need to search the Internet, use this model. + image: + - name: openai/dall-e-3 # OpenRouter doesn't support image generation + desc: DALL-E 3 is good at generating images. Whenever you are requested to generate images, use this model. ``` - `base_url`: The base URL of the API endpoint. @@ -194,7 +194,7 @@ Since only a single OpenAI endpoint can be configured currently, it's recommende Enable `python` for Athena to run inline Python code or Python scripts. This also enables Athena to install pip packages. ```yaml - python: +python: ``` ### Shell @@ -202,7 +202,7 @@ Enable `python` for Athena to run inline Python code or Python scripts. This als Enable `shell` for Athena to run shell commands. ```yaml - shell: +shell: ``` ### Short-Term Memory @@ -210,7 +210,7 @@ Enable `shell` for Athena to run shell commands. Enable `short-term-memory` for Athena to manage a basic task list. ```yaml - short-term-memory: +short-term-memory: ``` ### Telegram @@ -218,13 +218,13 @@ Enable `short-term-memory` for Athena to manage a basic task list. Enable `telegram` for Athena to send and receive messages from Telegram. ```yaml - telegram: - bot_token: your-telegram-bot-token - allowed_chat_ids: - - 1234567890 - - 9876543210 - admin_chat_ids: [] - log_chat_ids: [] +telegram: + bot_token: your-telegram-bot-token + allowed_chat_ids: + - 1234567890 + - 9876543210 + admin_chat_ids: [] + log_chat_ids: [] ``` - `bot_token`: The Telegram bot token. diff --git a/package.json b/package.json index fa1b497..82d7198 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "build": "npm run clean && npm run fast-build", "fast-build": "swc src -d dist --strip-leading-paths", "clean": "rm -rf dist", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "prettier --check .", + "lint:fix": "prettier --write ." }, "repository": { "type": "git", @@ -57,6 +59,7 @@ "@types/node": "^22.14.1", "@types/node-telegram-bot-api": "^0.64.7", "@types/ws": "^8.5.14", + "prettier": "^3.5.3", "tsx": "^4.19.3", "typescript": "^5.7.3" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc83f2c..822cc9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,6 +93,9 @@ importers: '@types/ws': specifier: ^8.5.14 version: 8.5.14 + prettier: + specifier: ^3.5.3 + version: 3.5.3 tsx: specifier: ^4.19.3 version: 4.19.3 @@ -1609,6 +1612,11 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} + hasBin: true + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -3694,6 +3702,8 @@ snapshots: possible-typed-array-names@1.0.0: {} + prettier@3.5.3: {} + process-nextick-args@2.0.1: {} psl@1.15.0: diff --git a/src/core/athena.ts b/src/core/athena.ts index 6d91eda..52ef530 100644 --- a/src/core/athena.ts +++ b/src/core/athena.ts @@ -167,7 +167,7 @@ export class Athena extends EventEmitter { if (tool.explain_retvals) { this.emitPrivateEvent( "athena/tool-result", - tool.explain_retvals(args, retvals) + tool.explain_retvals(args, retvals), ); } return retvals; diff --git a/src/main.ts b/src/main.ts index 30df44a..e5dbdf1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -24,7 +24,7 @@ const main = async () => { new transports.File({ filename: config.log_file, format: format.combine(format.timestamp(), format.json()), - }) + }), ); logger.info(`Log file: ${config.log_file}`); } else { diff --git a/src/plugins/browser/browser-use.ts b/src/plugins/browser/browser-use.ts index ecf1c19..c6e9c6d 100644 --- a/src/plugins/browser/browser-use.ts +++ b/src/plugins/browser/browser-use.ts @@ -107,7 +107,7 @@ export class BrowserUse extends EventEmitter { url: pageState.page.url(), resources: "usable", pretendToBeVisual: true, - }) + }), ); pageState.pageNodes = pageNodes.allNodes; return toExternalNodes(pageNodes.topLevelNodes); @@ -159,7 +159,7 @@ export class BrowserUse extends EventEmitter { Array.from((pageNode.node as Element).attributes).map((attr) => [ attr.name, attr.value, - ]) + ]), ), }; } diff --git a/src/plugins/browser/dom.ts b/src/plugins/browser/dom.ts index 84f1738..51417f0 100644 --- a/src/plugins/browser/dom.ts +++ b/src/plugins/browser/dom.ts @@ -73,7 +73,7 @@ const isClickable = (element: Element, dom: JSDOM): boolean => { } catch (e) {} return ( element.matches( - 'a, button, [role="button"], input[type="button"], input[type="submit"], input[type="checkbox"], input[type="radio"], [onclick], label[for]' + 'a, button, [role="button"], input[type="button"], input[type="submit"], input[type="checkbox"], input[type="radio"], [onclick], label[for]', ) || style?.cursor === "pointer" ); }; @@ -81,7 +81,7 @@ const isClickable = (element: Element, dom: JSDOM): boolean => { const isFillable = (element: Element): boolean => { return ( element.matches( - 'input[type="text"], input[type="password"], input[type="email"], input[type="number"], input[type="search"], input[type="tel"], input[type="url"], textarea, [contenteditable="true"]' + 'input[type="text"], input[type="password"], input[type="email"], input[type="number"], input[type="search"], input[type="tel"], input[type="url"], textarea, [contenteditable="true"]', ) || (element.tagName.toLowerCase() === "input" && element.getAttribute("type") == null) @@ -89,7 +89,7 @@ const isFillable = (element: Element): boolean => { }; export const parseDom = ( - dom: JSDOM + dom: JSDOM, ): { allNodes: IPageNode[]; topLevelNodes: IPageNode[] } => { const allNodes: IPageNode[] = []; @@ -190,7 +190,7 @@ export const toExternalNode = (pageNode: IPageNode): IExternalNode => { const children = pageNode.children.map(toExternalNode); const isChildrenAllText = children.every( - (child) => typeof child === "string" + (child) => typeof child === "string", ); return { @@ -202,7 +202,7 @@ export const toExternalNode = (pageNode: IPageNode): IExternalNode => { text: isChildrenAllText && children.length > 0 ? children.join(" ").trim() - : pageNode.node.getAttribute("value")?.trim() ?? undefined, + : (pageNode.node.getAttribute("value")?.trim() ?? undefined), children: isChildrenAllText ? undefined : children, }; }; diff --git a/src/plugins/browser/init.ts b/src/plugins/browser/init.ts index 791c346..f792ff3 100644 --- a/src/plugins/browser/init.ts +++ b/src/plugins/browser/init.ts @@ -56,7 +56,7 @@ export default class Browser extends PluginBase { return { summary: `A new page is popped up at index ${args.index} from index ${args.from_index}.`, details: `${args.url}\n${args.title}\n${JSON.stringify( - args.content + args.content, )}`, }; }, @@ -140,7 +140,7 @@ export default class Browser extends PluginBase { return { summary: `${args.url} is successfully opened at page ${retvals.index}.`, details: `${retvals.url}\n${retvals.title}\n${JSON.stringify( - retvals.content + retvals.content, )}`, }; }, @@ -233,7 +233,7 @@ export default class Browser extends PluginBase { return { summary: `The element at page ${args.page_index} and index ${args.node_index} is clicked.`, details: `${retvals.url}\n${retvals.title}\n${JSON.stringify( - retvals.content + retvals.content, )}`, }; }, @@ -241,7 +241,7 @@ export default class Browser extends PluginBase { return await this.withLock(async () => { await this.browserUse.clickElement(args.page_index, args.node_index); const metadata = await this.browserUse.getPageMetadata( - args.page_index + args.page_index, ); return { ...metadata, @@ -296,7 +296,7 @@ export default class Browser extends PluginBase { return { summary: `The element at page ${args.page_index} and index ${args.node_index} is filled with ${args.text}.`, details: `${retvals.url}\n${retvals.title}\n${JSON.stringify( - retvals.content + retvals.content, )}`, }; }, @@ -305,10 +305,10 @@ export default class Browser extends PluginBase { await this.browserUse.fillElement( args.page_index, args.node_index, - args.text + args.text, ); const metadata = await this.browserUse.getPageMetadata( - args.page_index + args.page_index, ); return { ...metadata, @@ -353,7 +353,7 @@ export default class Browser extends PluginBase { return { summary: `The content of the page at index ${args.index} is retrieved.`, details: `${retvals.url}\n${retvals.title}\n${JSON.stringify( - retvals.content + retvals.content, )}`, }; }, @@ -409,7 +409,7 @@ export default class Browser extends PluginBase { return await this.withLock(async () => { return this.browserUse.getElementData( args.page_index, - args.node_index + args.node_index, ); }); }, @@ -450,7 +450,7 @@ export default class Browser extends PluginBase { return await this.withLock(async () => { await this.browserUse.screenshot( this.browserUse.pages[args.index].page, - args.path + args.path, ); return { status: "success", @@ -494,7 +494,7 @@ export default class Browser extends PluginBase { return { summary: `The page at index ${args.index} is scrolled down.`, details: `${retvals.url}\n${retvals.title}\n${JSON.stringify( - retvals.content + retvals.content, )}`, }; }, @@ -513,7 +513,7 @@ export default class Browser extends PluginBase { this.browserUse.on("download-started", this.boundDownloadStartedHandler); this.browserUse.on( "download-completed", - this.boundDownloadCompletedHandler + this.boundDownloadCompletedHandler, ); } @@ -544,7 +544,7 @@ export default class Browser extends PluginBase { async withLock(fn: () => Promise): Promise { if (this.lock) { throw new Error( - "Browser is locked. Please wait for the previous call to complete before making a new call." + "Browser is locked. Please wait for the previous call to complete before making a new call.", ); } this.lock = true; diff --git a/src/plugins/cerebrum/init.ts b/src/plugins/cerebrum/init.ts index f7820e2..7c348df 100644 --- a/src/plugins/cerebrum/init.ts +++ b/src/plugins/cerebrum/init.ts @@ -134,7 +134,7 @@ export default class Cerebrum extends PluginBase { } this.processEventQueueTimer = setTimeout( () => this.processEventQueue(), - 500 + 500, ); } @@ -148,7 +148,7 @@ export default class Cerebrum extends PluginBase { let promptsSnapshot = this.prompts.slice(); try { const events = eventQueueSnapshot.map((event) => - this.eventToPrompt(event) + this.eventToPrompt(event), ); this.ensureInitialPrompt(promptsSnapshot); promptsSnapshot.push({ @@ -234,7 +234,7 @@ export default class Cerebrum extends PluginBase { toolCallId = toolCall.id; const result = await this.athena.callTool( toolCall.name, - toolCall.args + toolCall.args, ); this.pushEvent({ tool_result: true, @@ -401,7 +401,7 @@ ${descs.join("\n\n")}`; Object.entries(args as Dict).map(([key, value]) => [ key, this.sanitizeEventArgs(value), - ]) + ]), ) as T; } diff --git a/src/plugins/clock/init.ts b/src/plugins/clock/init.ts index 9d65504..7ccd4e7 100644 --- a/src/plugins/clock/init.ts +++ b/src/plugins/clock/init.ts @@ -301,7 +301,7 @@ export default class Clock extends PluginBase { this.timeout = setTimeout(() => { const now = Date.now(); const firedTimeouts = this.timeouts.filter( - (t) => t.next_trigger_time <= now + (t) => t.next_trigger_time <= now, ); for (const timeout of firedTimeouts) { this.athena.emitEvent("clock/timeout-triggered", { diff --git a/src/plugins/discord/init.ts b/src/plugins/discord/init.ts index d7bd248..e4e6d76 100644 --- a/src/plugins/discord/init.ts +++ b/src/plugins/discord/init.ts @@ -430,7 +430,7 @@ export default class Discord extends PluginBase { throw new Error("The channel is not text-based."); } const message = await (channel as SendableChannels).messages.fetch( - args.message_id + args.message_id, ); if (!message) { throw new Error("The message does not exist."); @@ -471,7 +471,7 @@ export default class Discord extends PluginBase { throw new Error("The channel is not text-based."); } const message = await (channel as SendableChannels).messages.fetch( - args.message_id + args.message_id, ); if (!message) { throw new Error("The message does not exist."); @@ -494,7 +494,7 @@ export default class Discord extends PluginBase { message.content.toLowerCase().includes("channel id") ) { message.channel.send( - `You appear to not have access to Athena, but FYI, your channel ID is ${message.channel.id}.` + `You appear to not have access to Athena, but FYI, your channel ID is ${message.channel.id}.`, ); } return; @@ -504,7 +504,7 @@ export default class Discord extends PluginBase { reply_to_message = message.reference && message.reference.messageId ? await message.channel.messages.fetch( - message.reference.messageId + message.reference.messageId, ) : undefined; } catch (e) { @@ -619,7 +619,7 @@ export default class Discord extends PluginBase { } if ( ["cerebrum/thinking", "athena/tool-call", "athena/tool-result"].includes( - event + event, ) ) { const message = (() => { diff --git a/src/plugins/http/init.ts b/src/plugins/http/init.ts index f2ba9f7..e01c1fd 100644 --- a/src/plugins/http/init.ts +++ b/src/plugins/http/init.ts @@ -237,7 +237,7 @@ export default class Http extends PluginBase { request.on("response", (response) => { if (response.statusCode !== 200) { reject( - new Error(`Failed to download file: ${response.statusCode}`) + new Error(`Failed to download file: ${response.statusCode}`), ); return; } diff --git a/src/plugins/llm/init.ts b/src/plugins/llm/init.ts index 3b414b0..30ed623 100644 --- a/src/plugins/llm/init.ts +++ b/src/plugins/llm/init.ts @@ -37,7 +37,7 @@ export default class Llm extends PluginBase { model: { type: "string", desc: `The model to use. Available models: ${JSON.stringify( - this.config.models.chat + this.config.models.chat, )}`, required: true, }, @@ -124,7 +124,7 @@ export default class Llm extends PluginBase { model: { type: "string", desc: `The model to use. Available models: ${JSON.stringify( - this.config.models.image + this.config.models.image, )}`, required: true, }, diff --git a/src/plugins/long-term-memory/init.ts b/src/plugins/long-term-memory/init.ts index 0d85196..3d704e4 100644 --- a/src/plugins/long-term-memory/init.ts +++ b/src/plugins/long-term-memory/init.ts @@ -19,11 +19,14 @@ export default class LongTermMemory extends PluginBase { } async load(athena: Athena) { - this.db = new DatabaseSync(this.config.persist_db ? this.config.db_file : ':memory:', { - allowExtension: true - }); + this.db = new DatabaseSync( + this.config.persist_db ? this.config.db_file : ":memory:", + { + allowExtension: true, + }, + ); load(this.db); - + // TODO: Support migration for varying dimensions this.db.exec(` CREATE VIRTUAL TABLE IF NOT EXISTS vec_items USING @@ -35,9 +38,9 @@ export default class LongTermMemory extends PluginBase { `); const insertStmt = this.db.prepare( - 'INSERT INTO vec_items(embedding, desc, data) VALUES (?, ?, ?)' + "INSERT INTO vec_items(embedding, desc, data) VALUES (?, ?, ?)", ); - + this.openai = new OpenAI({ baseURL: this.config.base_url, apiKey: this.config.api_key, @@ -73,9 +76,11 @@ export default class LongTermMemory extends PluginBase { input: args.desc, encoding_format: "float", }); - insertStmt.run(Float32Array.from(embedding.data[0].embedding), - args.desc, - JSON.stringify(args.data)); + insertStmt.run( + Float32Array.from(embedding.data[0].embedding), + args.desc, + JSON.stringify(args.data), + ); return { status: "success" }; }, }); @@ -149,21 +154,23 @@ export default class LongTermMemory extends PluginBase { input: args.query, encoding_format: "float", }); - const results = this.db.prepare( - `SELECT + const results = this.db + .prepare( + `SELECT distance, desc, data FROM vec_items WHERE embedding MATCH ? ORDER BY distance - LIMIT ${this.config.max_query_results}` - ).all(Float32Array.from(embedding.data[0].embedding)); + LIMIT ${this.config.max_query_results}`, + ) + .all(Float32Array.from(embedding.data[0].embedding)); if (!results || results.length === 0) { throw new Error("No results found"); } - return results.map(result => { - if (!result || typeof result !== 'object') { + return results.map((result) => { + if (!result || typeof result !== "object") { throw new Error("Invalid result format"); } return { diff --git a/src/plugins/python/init.ts b/src/plugins/python/init.ts index 778840c..b571ce6 100644 --- a/src/plugins/python/init.ts +++ b/src/plugins/python/init.ts @@ -66,7 +66,7 @@ export default class Python extends PluginBase { fn: async (args: Dict) => { return { stdout: (await PythonShell.run(args.path, { args: args.args })).join( - "\n" + "\n", ), }; }, @@ -106,7 +106,7 @@ export default class Python extends PluginBase { } else { resolve({ result: "success" }); } - } + }, ); }); }, diff --git a/src/plugins/short-term-memory/init.ts b/src/plugins/short-term-memory/init.ts index 060ea39..0a5837d 100644 --- a/src/plugins/short-term-memory/init.ts +++ b/src/plugins/short-term-memory/init.ts @@ -11,7 +11,7 @@ export default class ShortTermMemory extends PluginBase { desc() { return `You have a short-term memory. You must use it to keep track of your tasks while you are working on them. When you receive a task from the user and it requires multiple steps to complete, you must think thoroughly about the steps and break them down into smaller tasks. Try to be as detailed as possible and include all necessary information and append these tasks to your short-term memory. Afterwards, you must follow the task list and work on your unfinished tasks, unless the user asks you to do something else. After you have completed a task or multiple tasks at once, you must mark them as finished. After you have finished all tasks, you must clear your short-term memory. Your current short-term memory is: ${JSON.stringify( - this.tasks + this.tasks, )}. If the results of a task to show to the user is textual and long, you should create a Markdown file and append to it gradually as you complete the task. At the end, you should prepare your response according to this file.`; } @@ -43,7 +43,7 @@ export default class ShortTermMemory extends PluginBase { ...args.tasks.map((task: string) => ({ content: task, finished: false, - })) + })), ); return { status: "success" }; }, diff --git a/src/plugins/telegram/init.ts b/src/plugins/telegram/init.ts index 7ce4aa5..5ab40c1 100644 --- a/src/plugins/telegram/init.ts +++ b/src/plugins/telegram/init.ts @@ -539,7 +539,7 @@ export default class Telegram extends PluginBase { ) { this.bot.sendMessage( chatId, - `You appear to not have access to Athena, but FYI, your chat ID is ${chatId}.` + `You appear to not have access to Athena, but FYI, your chat ID is ${chatId}.`, ); } return; @@ -628,7 +628,7 @@ export default class Telegram extends PluginBase { file_name: msg.reply_to_message.document.file_name, file_size: msg.reply_to_message.document.file_size, url: await this.getFileUrl( - msg.reply_to_message.document.file_id + msg.reply_to_message.document.file_id, ), } : undefined, @@ -699,7 +699,7 @@ export default class Telegram extends PluginBase { } if ( ["cerebrum/thinking", "athena/tool-call", "athena/tool-result"].includes( - event + event, ) ) { const message = (() => { diff --git a/src/plugins/webapp-ui/init.ts b/src/plugins/webapp-ui/init.ts index f36c68b..1191ce1 100644 --- a/src/plugins/webapp-ui/init.ts +++ b/src/plugins/webapp-ui/init.ts @@ -30,7 +30,7 @@ export default class WebappUI extends PluginBase { this.athena = athena; this.supabase = createClient( this.config.supabase.url, - this.config.supabase.anon_key + this.config.supabase.anon_key, ); this.supabase.auth.onAuthStateChange((event, session) => { if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") { @@ -53,7 +53,7 @@ export default class WebappUI extends PluginBase { } this.logTransport = new WebappUITransport( this.supabase, - this.config.context_id + this.config.context_id, ); logger.add(this.logTransport); const { data, error: error2 } = await this.supabase @@ -65,7 +65,7 @@ export default class WebappUI extends PluginBase { } else if (data && data[0].states !== null) { this.athena.states = data[0].states; this.logger.info( - `Loaded context states: ${JSON.stringify(data[0].states)}` + `Loaded context states: ${JSON.stringify(data[0].states)}`, ); } this.boundAthenaPrivateEventHandler = @@ -154,7 +154,7 @@ export default class WebappUI extends PluginBase { for (const file of args.files) { if ( file.location.startsWith( - "https://oaidalleapiprodscus.blob.core.windows.net" + "https://oaidalleapiprodscus.blob.core.windows.net", ) ) { // This is an image from DALL-E. Download it and upload it to Supabase to avoid expiration. @@ -170,10 +170,10 @@ export default class WebappUI extends PluginBase { const digest = await fileDigest(file.location); const storagePath = `${this.userId}/${digest.slice( 0, - 2 + 2, )}/${digest.slice(2, 12)}/${encodeURIComponent(file.name).replace( /%/g, - "_" + "_", )}`; const contentType = mime.lookup(file.location); const { error } = await this.supabase.storage @@ -187,7 +187,7 @@ export default class WebappUI extends PluginBase { }); if (error) { throw new Error( - `Error uploading file ${file.name}: ${error.message}` + `Error uploading file ${file.name}: ${error.message}`, ); } file.location = this.supabase.storage @@ -372,7 +372,7 @@ export default class WebappUI extends PluginBase { resolve(); } }); - }) + }), ); } await Promise.all(promises); diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts index a6b2255..cbefaf8 100644 --- a/src/utils/crypto.ts +++ b/src/utils/crypto.ts @@ -3,7 +3,7 @@ import fs from "fs"; export const fileDigest = async ( filePath: string, - algorithm: string = "sha256" + algorithm: string = "sha256", ) => { return new Promise((resolve, reject) => { const hash = crypto.createHash(algorithm); diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 7ea731b..2e01eac 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -8,7 +8,7 @@ const logger = createLogger({ format.colorize(), format.printf((info) => { return `[${info.timestamp}] [${info.level}] ${info.message}`; - }) + }), ), }), ],