diff --git a/.flake8 b/.flake8 old mode 100644 new mode 100755 diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml new file mode 100644 index 00000000..6e801a22 --- /dev/null +++ b/.github/workflows/build-push.yml @@ -0,0 +1,68 @@ +name: Reusable Docker Production Build / Push (AWS ECR) + +on: + workflow_call: + inputs: + app_directory: + description: "Relative path to the application directory" + required: true + type: string + app_name: + description: "Application name" + required: true + type: string + aws_account_id: + description: "AWS Account ID" + required: true + type: string + aws_region: + description: "AWS Region where ECR is located" + required: true + type: string + ecr_repository: + description: "Name of the ECR repository" + required: true + type: string + image_tag: + description: "Docker image tag (e.g., v1.2.3)" + required: true + type: string + +permissions: + contents: read + id-token: write + +jobs: + build-and-push: + name: Build and Push Image to AWS ECR + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/GithubActionsECRPushRole + aws-region: ${{ inputs.aws_region }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Amazon ECR + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build image + working-directory: ${{ inputs.app_directory }}/${{ inputs.app_name }} + run: | + docker buildx build \ + --platform=linux/amd64 \ + --build-arg APP_ENV=production \ + -t ${{ inputs.aws_account_id }}.dkr.ecr.${{ inputs.aws_region }}.amazonaws.com/${{ inputs.ecr_repository }}:${{ inputs.image_tag }} \ + . + + - name: Push image + run: | + docker push ${{ inputs.aws_account_id }}.dkr.ecr.${{ inputs.aws_region }}.amazonaws.com/${{ inputs.ecr_repository }}:${{ inputs.image_tag }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..3dc07902 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,47 @@ +name: Deploy MySuperAgent Infrastructure + +on: + # Only run on specific branch and path conditions + push: + branches: + - main # Only on the main branch + paths: + - "infrastructure/mysuperagent-stack.yml" # Only when this file changes + + # Allow manual triggering through GitHub UI + workflow_dispatch: + inputs: + environment: + description: "Environment to deploy to" + required: true + default: "staging" + type: choice + options: + - staging + - production + +# Don't run on PRs or other events +# (absence of 'pull_request' in triggers means it won't run on PRs) + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Deploy CloudFormation stack + run: | + aws cloudformation deploy \ + --template-file infrastructure/mysuperagent-stack.yml \ + --stack-name mysuperagent-stack-${{ github.event.inputs.environment || 'staging' }} \ + --capabilities CAPABILITY_IAM \ + --no-fail-on-empty-changeset diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml old mode 100644 new mode 100755 diff --git a/.github/workflows/mor-agents-build-linux.yml b/.github/workflows/mor-agents-build-linux.yml deleted file mode 100644 index cdc44ae7..00000000 --- a/.github/workflows/mor-agents-build-linux.yml +++ /dev/null @@ -1,179 +0,0 @@ -name: MOR Agents Build Linux - -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: "recursive" - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install pyinstaller - - - name: Build with PyInstaller - run: | - pyinstaller --name="MORagents" --add-data "images/moragents.png:images" main.py - - - name: Create Debian package - run: | - mkdir -p debian/DEBIAN - mkdir -p debian/usr/bin - mkdir -p debian/usr/share/applications - mkdir -p debian/usr/share/icons/hicolor/256x256/apps - cp -r dist/MORagents/* debian/usr/bin/ - cp images/moragents.png debian/usr/share/icons/hicolor/256x256/apps/moragents.png - echo "[Desktop Entry] - Name=MORagents - Exec=/usr/bin/MORagents - Icon=moragents - Type=Application - Categories=Utility;" > debian/usr/share/applications/moragents.desktop - echo "Package: moragents - Version: 1.0 - Section: utils - Priority: optional - Architecture: amd64 - Maintainer: LachsBagel - Description: MORagents application - MORagents is a desktop application for AI agents." > debian/DEBIAN/control - - dpkg-deb --build debian moragents.deb - - - name: Create setup script - run: | - cat << EOF > moragents-setup.sh - #!/bin/bash - set -e - - # Colors for output - RED='\033[0;31m' - GREEN='\033[0;32m' - YELLOW='\033[0;33m' - NC='\033[0m' # No Color - - # Function to check if a command exists - command_exists() { - command -v "$1" >/dev/null 2>&1 - } - - # Function to add current user to docker group - add_user_to_docker_group() { - local current_user=\$(whoami) - if [ "\$current_user" != "root" ]; then - echo -e "\${YELLOW}Adding current user to docker group...${NC}" - sudo usermod -aG docker "\$current_user" - echo -e "\${GREEN}User added to docker group. Please log out and log back in for changes to take effect.${NC}" - else - echo -e "\${YELLOW}Running as root. Skipping user addition to docker group.${NC}" - fi - } - - # Function to wait for Ollama service to be ready - wait_for_ollama() { - echo -e "\${YELLOW}Waiting for Ollama service to be ready...${NC}" - for i in {1..30}; do - if curl -s http://localhost:11434/api/tags >/dev/null; then - echo -e "\${GREEN}Ollama service is ready.${NC}" - return 0 - fi - sleep 2 - done - echo -e "\${RED}Timed out waiting for Ollama service.${NC}" - return 1 - } - - # Function to pull Ollama model with retries - pull_ollama_model() { - local model=\$1 - local max_attempts=3 - local attempt=1 - - while [ \$attempt -le \$max_attempts ]; do - echo -e "\${YELLOW}Pulling Ollama model \$model (Attempt \$attempt)...${NC}" - if ollama pull \$model; then - echo -e "\${GREEN}Successfully pulled \$model.${NC}" - return 0 - fi - echo -e "\${YELLOW}Failed to pull \$model. Retrying...${NC}" - sleep 5 - attempt=\$((attempt + 1)) - done - - echo -e "\${RED}Failed to pull \$model after \$max_attempts attempts.${NC}" - return 1 - } - - # Install curl if not present - if ! command_exists curl; then - echo -e "\${YELLOW}Installing curl...${NC}" - sudo apt-get update - sudo apt-get install -y curl - fi - - # Install Docker if not present - if ! command_exists docker; then - echo -e "\${YELLOW}Installing Docker...${NC}" - curl -fsSL https://get.docker.com -o get-docker.sh - sudo sh get-docker.sh - add_user_to_docker_group - sudo systemctl enable docker - sudo systemctl start docker - else - echo -e "\${GREEN}Docker is already installed.${NC}" - fi - - # Install Ollama - echo -e "\${YELLOW}Installing Ollama...${NC}" - curl -fsSL https://ollama.com/install.sh | sh - - # Start Ollama service - echo -e "\${YELLOW}Starting Ollama service...${NC}" - nohup ollama serve > /dev/null 2>&1 & - - # Wait for Ollama service to be ready - wait_for_ollama - - # Pull Ollama models - echo -e "\${YELLOW}Pulling Ollama models...${NC}" - pull_ollama_model llama3.2:3b - pull_ollama_model nomic-embed-text - - # Pull necessary Docker images - echo -e "\${YELLOW}Pulling Docker images...${NC}" - sudo docker pull lachsbagel/moragents_dockers-nginx:amd64-0.2.0 - sudo docker pull lachsbagel/moragents_dockers-agents:amd64-0.2.0 - - # Start Docker containers - echo -e "\${YELLOW}Starting Docker containers...${NC}" - sudo docker run -d --name agents -p 8080:5000 --restart always -v /var/lib/agents -v /app/src lachsbagel/moragents_dockers-agents:amd64-0.2.0 - sudo docker run -d --name nginx -p 3333:80 lachsbagel/moragents_dockers-nginx:amd64-0.2.0 - - echo -e "\${GREEN}Setup complete!${NC}" - EOF - - chmod +x moragents-setup.sh - - - name: Upload Debian Package and Setup Script - uses: actions/upload-artifact@v4 - with: - name: MORagentsSetup-Linux - path: | - moragents.deb - moragents-setup.sh diff --git a/.github/workflows/mor-agents-build-mac-arm.yml b/.github/workflows/mor-agents-build-mac-arm.yml deleted file mode 100644 index 644e82a3..00000000 --- a/.github/workflows/mor-agents-build-mac-arm.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: MOR Agents Build macOS ARM - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - -jobs: - build: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: 'recursive' - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install pyinstaller - - - name: Build with PyInstaller - run: | - pyinstaller --windowed --name="MORagents" --icon="images/moragents.icns" --osx-entitlements-file "build_assets/macOS/MORagents.entitlements" main.py - - - name: Move .app to expected location - run: | - mv dist/MORagents.app build_assets/macOS/ - - - name: Install Packages app - run: | - wget http://s.sudre.free.fr/files/Packages_1211_dev.dmg - hdiutil attach Packages_1211_dev.dmg - sudo installer -pkg /Volumes/Packages\ 1.2.11/packages/Packages.pkg -target / - hdiutil detach /Volumes/Packages\ 1.2.11 - - - name: Create installer package - run: | - cd build_assets/macOS - /usr/local/bin/packagesbuild --verbose --project MorpheusPackagesSudre.pkgproj - - - name: Upload Installer - uses: actions/upload-artifact@v4 - with: - name: MORagentsSetup-macOS - path: ./build_assets/macOS/MORAgentsInstaller.pkg diff --git a/.github/workflows/mor-agents-build-mac-intel.yml b/.github/workflows/mor-agents-build-mac-intel.yml deleted file mode 100644 index a3677a4c..00000000 --- a/.github/workflows/mor-agents-build-mac-intel.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: MOR Agents Build macOS Intel - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - -jobs: - build: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: 'recursive' - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install pyinstaller - - - name: Build with PyInstaller - run: | - pyinstaller --windowed --name="MORagents" --icon="images/moragents.icns" --osx-entitlements-file "build_assets/macOS/MORagents.entitlements" main.py - - - name: Move .app to expected location - run: | - mv dist/MORagents.app build_assets/macOS/ - - - name: Install Packages app - run: | - wget http://s.sudre.free.fr/files/Packages_1211_dev.dmg - hdiutil attach Packages_1211_dev.dmg - sudo installer -pkg /Volumes/Packages\ 1.2.11/packages/Packages.pkg -target / - hdiutil detach /Volumes/Packages\ 1.2.11 - - - name: Create installer package - run: | - cd build_assets/macOS - /usr/local/bin/packagesbuild --verbose --project MorpheusPackagesSudreIntel.pkgproj - - - name: Upload Installer - uses: actions/upload-artifact@v4 - with: - name: MORagentsSetup-macOS - path: ./build_assets/macOS/MORAgentsInstaller.pkg diff --git a/.github/workflows/mor-agents-build-windows.yml b/.github/workflows/mor-agents-build-windows.yml deleted file mode 100644 index cb65dd8b..00000000 --- a/.github/workflows/mor-agents-build-windows.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: MOR Agents Build Windows - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - -jobs: - build: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pyinstaller - pip install -r requirements.txt - - - name: Build with PyInstaller - run: pyinstaller --icon=images/moragents.ico --name=MORagents main.py - - - name: Install Inno Setup - run: choco install innosetup -y - - - name: Compile Inno Setup Script - run: | - iscc /O".\MORagentsWindowsInstaller" "wizard_windows.iss" - - - name: Upload Installer - uses: actions/upload-artifact@v4 - with: - name: MORagentsSetup - path: .\MORagentsWindowsInstaller\MORagentsSetup.exe diff --git a/.github/workflows/security_scan.yml b/.github/workflows/security_scan.yml deleted file mode 100644 index f89d1f1d..00000000 --- a/.github/workflows/security_scan.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Security Scan -permissions: - security-events: write - contents: write - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - security_scan: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install safety bandit - - name: Run Safety check - run: safety check -r requirements.txt - continue-on-error: true - - name: Run Bandit - run: bandit -r . -f custom - continue-on-error: true - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - scan-type: 'fs' - ignore-unfixed: true - format: 'sarif' - output: 'trivy-results.sarif' - severity: 'CRITICAL' - scanners: 'vuln,secret,config' - - name: Upload Trivy scan results - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..317bf473 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,44 @@ +name: Python Tests + +on: + push: + branches: [main] + paths: + - "submodules/agents/**" # Only run when changes in this path + pull_request: + branches: [main] + paths: + - "submodules/agents/**" # Only run when changes in this path + workflow_dispatch: # Allow manual triggering + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: recursive # Make sure we get the submodules too + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" # Adjust version as needed for your project + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: 1.5.1 # Specify Poetry version + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Install dependencies + run: | + cd submodules/agents + poetry install --no-interaction + + - name: Run tests + run: | + cd submodules/agents + poetry run python -m pytest diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 8236d569..5a4f41cc --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ venv.bak/ # Distribution / packaging dist/ -build/ *.egg-info/ *.egg .eggs/ @@ -50,16 +49,15 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +.mypy_cache/ ## resource files resources/* -## installer -MORagentsWindowsInstaller -MORagentsWindowsInstaller.zip -build_assets/macOS/MORAgentsInstaller.pkg -## app -MORagents.app +## app related items +app.log +submodules/agents/data/vector_store +uploads/* ## lint .pytest_cache/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index c4130ded..00000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "submodules/moragents_dockers"] - path = submodules/moragents_dockers - url = https://github.com/cliffordattractor/moragents_dockers - branch = main diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml old mode 100644 new mode 100755 diff --git a/AGENTABILITIES.md b/AGENTABILITIES.md old mode 100644 new mode 100755 index fc05519e..ce3bc99c --- a/AGENTABILITIES.md +++ b/AGENTABILITIES.md @@ -1,53 +1,60 @@ -# MORagents -Welcome to the world of Web3 agents! If you're interested in building and using agents, this document will guide you through the principles and +# MySuperAgent + +Welcome to the world of agents! If you're interested in building and using agents, this document will guide you through the principles and current projects underway. ### Principles: + 1. **Agents Need Guardrails When Executing Decisions**: Agents should not be given all private key information or be allowed to enforce their own policy non-deterministically. -This is due to the limitations of current LLMs in understanding complex transactions and the risk of [gaslighting](https://arxiv.org/abs/2311.04235). + This is due to the limitations of current LLMs in understanding complex transactions and the risk of [gaslighting](https://arxiv.org/abs/2311.04235). ### Current Projects: -1. **lachsbagel on Discord** - [this repo](https://github.com/MorpheusAIs/moragents): - 1. Architecture - 2. [HideNSeek](https://github.com/MorpheusAIs/HideNSeek): An algorithm for verifying and fingerprinting which model a compute provider is actually running - 1. Link to Paper [Hide and Seek: Fingerprinting Large Language Models with Evolutionary Learning](https://www.arxiv.org/abs/2408.02871) - 3. (pending) A follow-on paper exploring parameter size estimation is in the works. -2. **artfuljars on Discord**: - 1. Windows Build (EXE version of .app) - 2. Two installation wizards: - 1. Windows - 2. macOS - 3. CICD build for Windows - 4. CICD builds for Linux and macOS (apple and intel) - 5. Vulnerability scanning of dependencies and code -3. GenLayer - 1. (pending) [FeedBuzz](https://github.com/yeagerai/feedbuzz-contracts) - AI filtered logging system to surface user demand and failure modes for new functionality -4. **CliffordAttractor on Discord** - Following Assume 16GB+ RAM: - 1. Developed a [price fetcher agent](submodules/moragents_dockers/agents/src/data_agent) using CoinGecko. - 2. A [web interface](submodules/moragents_dockers/frontend) which is served by the local Docker installation and integrated with Rainbow, enabling the use of MetaMask, WalletConnect, and other - EVM-based wallets. - 3. (NEEDS REFACTORING DUE TO 1INCH CHANGE) [Swap agent](submodules/moragents_dockers/agents/src/swap_agent) which can iteratively ask users to provide needed details for disambiguation. - 4. [General-purpose agent](https://github.com/MorpheusAIs/moragents/pull/34) that can ingest arbitrary documents, such as PDFs, for basic document QA and text generation. - 5. [Local delegating agent](https://github.com/MorpheusAIs/moragents/pull/45) which can maintain user's persona/interests as well as coordinating to task agents and tools. -5. **Dan Y.** - 1. [X/Twitter Posting Agent](https://github.com/MorpheusAIs/moragents/pull/57) - An agent which generates spicy tweets with an X integration for one-click posting. - 2. Real-time search agent - 3. Replaced llama 3.1 and functionary with llama 3.2 to massively increase speed 10X and reduce install footprint 6X - 5. Inter-agent Delegator which can coordinate between local and decentralized agents - 6. Coinbase DCA and Gasless USDC Agents - 7. Trending tokens and rugcheck agents - 8. Improved UI with multi-wallet connect - 9. (pending) Agent forge to allow devs to publish their custom agents to the Morpheus/Lumerin Agent Registry -6. **Niveshi** - 1. [MOR Rewards agent](https://github.com/MorpheusAIs/moragents/tree/main/submodules/moragents_dockers/agents/src/reward_agent/src). Lets you see how many MOR tokens are claimable for your wallet - 2. (being debugged) Cryto Live News Agent +1. **Architecture** (lachsbagel): + + 1. Core Architecture + 2. HideNSeek: Model verification and fingerprinting algorithm + 1. Paper: [Hide and Seek: Fingerprinting Large Language Models with Evolutionary Learning](https://www.arxiv.org/abs/2408.02871) + 3. Parameter size estimation research + +2. **Available Agents**: + + A. Data & Analysis + + - Crypto Market Data Agent: Real-time price and market data from CoinGecko + - DexScreener Agent: DEX activity monitoring and token analytics + - Codex Agent: On-chain analytics and token trends + - Document Analysis Agent: PDF and document QA capabilities + - Rugcheck Agent: Solana token safety analysis + + B. Social & News + + - Tweet Sizzler Agent: AI-powered tweet generation + - Elfa Social Search: Social media monitoring for crypto + - Crypto News Agent: Real-time crypto news analysis + + C. Trading & Transactions + + - Token Swap Agent: Cross-chain token swapping + - Base Chain Agent: USDC and token transactions on Base + + D. Image Generation + + - Image Generation Agent: AI image creation and editing + + E. Morpheus Platform + + - MOR Rewards Agent: Track and manage MOR token rewards + - Delegating Agent: Persona management and task coordination ### Decentralized Inference: + #### Non-Local Installation Agents for Permission-less Compute + Pending Lumerin's work. Eventually Agent Builders will be able to permission-lessly upload Agent and Model artifacts to a decentralized registry. ### How to Contribute: + - If you are working on an agent which can provide value through open models and relies on processing public data, please reach out to lachsbagel on Discord (link below) - Otherwise, you are more than welcome to publish your agent to the registry when it goes live pending Lumerin's work and any other necessary pieces which come up to better ensure security and verifiability of models in non-local execution environments. - If you are working on security and/or verifiability of models and the runtime, please reach out to LachsBagel on the Morpheus Discord. @@ -56,6 +63,7 @@ Pending Lumerin's work. Eventually Agent Builders will be able to permission-les - [6079](https://6079.ai/) will help with implementing the plumbing for [HideNSeek](https://github.com/MorpheusAIs/HideNSeek) ### Contact + Join the [Morpheus Discord](https://discord.com/invite/Dc26EFb6JK) -*Last Updated: January 16, 2025* +_Last Updated: January 16, 2025_ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md old mode 100644 new mode 100755 index 2613dd5f..9c1b6210 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing and Future Interoperability of Agents -**For contributors looking to build a local agent** please see [Agents README](submodules/moragents_dockers/README.md) section: "Steps to Add a New Agent". +**For contributors looking to build a local agent** please see [Agents README](submodules/agents/README.md) section: "Steps to Add a New Agent". This will let you experiment with your agent locally. Later in 2024/5 you will be able to publish your agent to the Morpheus registry. @@ -9,7 +9,8 @@ There will be a future router which will expand to coordinate the decentralized --- # Contribution Process: -1. Prospective contributors can propose an idea or find a relevant issue from [Issues](https://github.com/MorpheusAIs/moragents/issues) + +1. Prospective contributors can propose an idea or find a relevant issue from [Issues](https://github.com/MorpheusAIs/mysuperagent/issues) 1. See what's being worked on here in [AGENTABILITIES](./AGENTABILITIES.md) 2. Contact lachsbagel on the Morpheus Discord or comment on an issue above 3. Agree on timeline and weights diff --git a/DISCLAIMER.md b/DISCLAIMER.md old mode 100644 new mode 100755 diff --git a/LICENSE.md b/LICENSE.md old mode 100644 new mode 100755 diff --git a/MORagents.spec b/MORagents.spec deleted file mode 100644 index 3f504305..00000000 --- a/MORagents.spec +++ /dev/null @@ -1,51 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- - - -a = Analysis( - ['main.py'], - pathex=[], - binaries=[], - datas=[], - hiddenimports=[], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - noarchive=False, - optimize=0, -) -pyz = PYZ(a.pure) - -exe = EXE( - pyz, - a.scripts, - [], - exclude_binaries=True, - name='MORagents', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - console=False, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity='Developer ID Application: Liquid Tensor LLC (ZQN244GMTD)', - entitlements_file='build_assets/macOS/MORagents.entitlements', - icon=['images/moragents.icns'], -) -coll = COLLECT( - exe, - a.binaries, - a.datas, - strip=False, - upx=True, - upx_exclude=[], - name='MORagents', -) -app = BUNDLE( - coll, - name='MORagents.app', - icon='images/moragents.icns', - bundle_identifier='com.liquidtensor.moragents', -) diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 58024180..68fb06eb --- a/README.md +++ b/README.md @@ -1,124 +1,203 @@ -![morpheus ecosystem](images/morpheus-ecosystem@3x_green.png) -# MORagents +![morpheus ecosystem](static/morpheus-ecosystem@3x_green.png) -## Local Agents Built with the Friendliest of Dev Tooling -Python for AI Agents, JS for UI. Runs in your favorite browser. Made possible by Docker. -Fully Extensible! Add your own agents and have them automatically invoked based on user intent. +# MySuperAgent -**Note:** This repository is meant to act as a sandbox for Smart Agent developers to explore existing agents and to build their own. It is not designed to be a production-ready application or consumer-based product. Please set your expectations accordingly. For additional information about a specific agent, browse to the submodules/moragents_dockers/agents/src/agents/ directory in the repo and view the README file for the agent you are interested in. +## A Platform for Building, Deploying, and Leveraging AI Agents -![UI 1](images/MORagents-UI.png) +A developer-focused platform that enables building, testing, and deploying AI agents. Powered by Python and React, with seamless integration of LLMs and Web3 capabilities. -![UI 2](images/gasless-usdc-base-agent.png) +**Note:** MySuperAgent serves as both a development sandbox and production platform for AI agent builders. The platform enables rapid prototyping and deployment of agents that can be automatically invoked based on user intent. For detailed documentation on specific agents, see the agent READMEs in the `/agents` directory. -![UI 3](images/dca-strategy-agent.png) +![UI](static/demo.gif) -![UI 4](images/image-generator.png) +--- -![UI 5](images/tweet_sizzler.png) +### Available Agents & Features -![UI 6](images/real-time-info.png) +#### Generate Images (Imagen) πŸ’« -![UI 7](images/mor_rewards.png) +- Generate AI images using Stable Diffusion XL +- Example: "Generate an image of Donald Trump" +- Example: "Create a cyberpunk style portrait of Elon Musk" -![UI 8](images/price-fetcher-realtime-news.png) +#### Document Analysis πŸ“„ -![UI 9](images/moragents_chatpdf.png) +- Upload and analyze documents with natural language querying +- Example: "Summarize the uploaded document" +- Example: "What are the key points in this uploaded document?" ---- +#### Crypto Market Data πŸ“ˆ + +- Real-time price, market cap and TVL data +- Example: "What's the price of ETH?" +- Example: "What's the FDV of USDC?" + +#### Token Swaps πŸ”„ + +- Swap tokens across multiple chains +- Example: "Swap 0.2 ETH for USDC" +- Example: "Exchange my BTC for ETH" + +#### Tweet Generator πŸ”₯ + +- AI-powered viral tweet generation +- Example: "Create a viral tweet about Web3" +- Example: "Create a spicy crypto market tweet about Gary Gensler" + +#### Base Transactions ⚑ + +- Send and swap tokens on Base +- Example: "Send USDC on Base" +- Example: "Swap USDC for ETH on Base" + +#### MOR Claims Manager 🎁 + +- Manage and claim MOR token rewards +- Example: "Claim my MOR rewards" +- Example: "Help me claim my pending MOR tokens" + +#### MOR Rewards Tracking πŸ† + +- Track and analyze MOR rewards +- Example: "Show my MOR rewards balance" +- Example: "Calculate my pending MOR rewards" + +#### Crypto News Analysis πŸ“° -### Features - -#### Generate Images 🏞️ - - "Generate an image of a cryptographically secure doggo" -#### Send Gasless USDC with Coinbase 🚚 - - "Send USDC on Base" - _- WARNING: Highly experimental. Please backup your wallet file by downloading from wallet selector._ - - [INSTRUCTIONS](submodules/moragents_dockers/agents/src/agents/base_agent/README.md) -#### Dollar Cost Averaging (DCA) with Coinbase - - "DCA Strategy on Base" - _- WARNING: Highly experimental. Please backup your wallet file by downloading from wallet selector._ - - [INSTRUCTIONS](submodules/moragents_dockers/agents/src/agents/dca_agent/README.md) -#### Write Sizzling Tweets 🌢No Content Moderation πŸ˜… - - "Write a based tweet about Crypto and AI" - - [INSTRUCTIONS](submodules/moragents_dockers/agents/src/agents/tweet_sizzler/README.md) -#### Real-time Info πŸ•ΈοΈ - - "Real-time info about Company XYZ" -#### Trending Crypto News - - "Latest news for USDC" -#### Check MOR rewards πŸ† - - "How many MOR rewards do I have?" -#### Fetch Price, Market Cap, and TVL of coins and tokens supported on CoinGecko πŸ“ˆ - - "What's the price of ETH?" - - "What's the market cap of BTC?" -#### Upload a PDF with paperclip icon, then ask questions about the PDF πŸ“„ - - "Can you give me a summary?" - - "What's the main point of the document?" -#### Ask about popular tokens and summary reports for tokens 🍿 - - "What are the most active tokens on Solana?" +- Get latest crypto market news and analysis +- Example: "Analyze recent crypto market news for ETH" +- Example: "What's the latest news impact on BTC?" + +#### DexScreener πŸ“Š + +- Monitor DEX activity and token metrics +- Example: "Scan Dexscreener for the most active tokens on solana" +- Example: "Show me DEX activity for ETH as reported by Dexscreener" + +#### Elfa Social Search πŸ” + +- Search social media for crypto mentions +- Example: "Search for mentions of MOR on social media" +- Example: "What are the top trending tokens on social media?" + +#### Solana Token Safety πŸ›‘οΈ + +- Check token safety and rugpull risks +- Example: "Check token safety for SAMO" +- Example: "Show me the most viewed tokens on rugcheck" + +#### Codex Agent πŸ—„οΈ + +- Advanced token analytics and insights +- Example: "What are the top trending tokens on Ethereum?" +- Example: "Who are the top holders of $TRUMP on Solana?" --- -## Easy Install -### macOS ->Best performance when >=16GB RAM - -#### Steps to Install -1. Download Installer - 1. For Mac on Apple Silicon M1, M2, M3, M4 (arm64) - 1. Download and run MORagents installer [MORagents022-apple.pkg](https://drive.proton.me/urls/YA24T6MMT0#iCrO2BCuVZff) - > SHA256 3f51ce7cb5a81903f1cc612901c5c63cacc106e83e14c5ca791ddd6b5e71e883 MORagents022-apple.pkg -2. Wait several minutes for background files to download and then your browser should automatically open to http://localhost:3333 - > Note: After installation is complete, the MORagents app icon will bounce for several minutes on your dock, and then stop. This is normal behavior as it's downloading <7GB of files in the background. You can open "Activity Monitor" and in the Network tab see that it's downloading. - -#### Future Usage -- Open the "MORagents" app from Mac search bar. - - For easier access: Right-click MORagents icon on dock -> Options -> Keep in Dock - -#### Troubleshooting -- If the app shows connections errors in connecting to agents. Please ensure Docker Desktop is running, then close and reopen **MORagents** from desktop. -- If installation is unsuccessful, run the following in your Terminal and open the MORagents....pkg again - ```shell - $ xcode-select --install +## Getting Started + +### Using the Platform + +Visit [https://mysuperagent.io](https://mysuperagent.io) to access the hosted platform. + +#### Requirements + +- Modern web browser (Chrome, Firefox, Safari) +- Web3 wallet for blockchain interactions +- API keys for specific agent integrations (Coinbase, Twitter, etc) + +### Local Development + +To run MySuperAgent locally, follow these comprehensive setup instructions: + +#### Prerequisites + +1. Install Ollama: + ```bash + # Install Ollama following instructions at https://ollama.ai + ollama pull 3.2:3b + ollama serve ``` -- If you don't already have Docker installed, the installation may fail the first time. Simply run Docker, grant permissions as needed, and ensure you are logged in with a Docker account. Then run the installation a second time and it should work. ---- -### Windows (x86_64) ->Best performance when >=16GB RAM - -#### Steps -1. Download [MORagentsSetupWindows022.zip](https://drive.proton.me/urls/MGNQ086Y2G#1cVhZOkkY1TU) - > SHA256 823790f9c2e2a1db7071012ad720e21a446d2fa86a58ac100cff134a107e7a3d MORagentsSetupWindows022.zip -2. Go to downloaded **MORagentsSetupWindows022(.zip)** file and double click to open -3. Double click **MORagentsSetup.exe** - 1. You may need to click "More info" -> "Run anyway" - 2. If that still doesn't work, try temporarily disabling your antivirus and open the .exe again -4. Click and Run **MORagentsSetup.exe** - 1. This will auto-install Docker and Ollama dependencies. Those will ask you for confirmation. -5. Open **MORagents** from Desktop - 1. Wait for Docker engine to start... - 2. If you see any errors or if anything hangs for >10min, please try opening the MORagents app again from the Desktop - -#### Troubleshooting -If the app shows connections errors in connecting to agents. Please ensure Docker Desktop is running, then close and reopen **MORagents** from desktop. +#### Running with Docker ---- +1. Clone the repository: -#### Linux -*Coming soon* + ```bash + git clone https://github.com/mysuperagent/mysuperagent.git + cd mysuperagent + ``` ---- -# Adding a New Agent +2. Start the Docker environment: -See [Agents README](submodules/moragents_dockers/README.md) section: "Steps to Add a New Agent". + ```bash + docker compose -p mysuperagent -f build/docker-compose.yml up + ``` -This will allow you to add custom agents which will be automatically invoked based on relevant user queries. +3. Access the application: + - Frontend: http://localhost:3333 + - Server API: http://localhost:8888 ---- +#### Running Services Independently + +1. Clone the repository: + + ```bash + git clone https://github.com/mysuperagent/mysuperagent.git + cd mysuperagent + ``` + +2. Start PostgreSQL: + + ```bash + docker run -d \ + -p 5678:5678 \ + -e POSTGRES_USER=neo \ + -e POSTGRES_PASSWORD=trinity \ + -e POSTGRES_DB=morpheus_db \ + postgres:16-bullseye -p 5678 + ``` + +3. Start the Frontend: + + ```bash + cd submodules/frontend + docker build -t frontend . + docker run -d -p 3333:80 frontend + ``` + +4. Start the Agents API: + ```bash + cd submodules/agents + docker build -t agents -f build/Dockerfile . + docker run -d \ + -p 8888:5000 \ + -e DATABASE_URL=postgresql://neo:trinity@localhost:5678/morpheus_db \ + agents + ``` + +#### Simulating Production Environment + +To test features that require external API integrations, you'll need to export the following environment variables: + +``` +export TogetherApiKey="mock-key" +export CerebrasApiKey="mock-key" +export CodexApiKey="mock-key" +export ElfaApiKey="mock-key" +``` + +### Developer Documentation + +For developers looking to build and deploy their own agents: + +1. [Agent Development Guide](docs/agent-development-guide.md) +2. [API Documentation](docs/available-apis-guide.md) +3. [Testing Framework](docs/testing-framework-guide.md) +4. [Deployment Guide](docs/deployment-guide.md) + +# Contributing -### Build it Yourself +See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on adding new agents and contributing to the platform. -#### Build instructions: -1. [macOS](build_assets/macOS/README_MACOS_DEV_BUILD.md) -2. [Windows](build_assets/windows/README_WINDOWS_DEV_BUILD.md) +Your agents will be automatically invoked based on user intent through our advanced routing system. diff --git a/build/build_and_push.sh b/build/build_and_push.sh new file mode 100755 index 00000000..91c3c3c1 --- /dev/null +++ b/build/build_and_push.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Set AWS Region +AWS_REGION="us-west-1" + +# AWS ECR Repository URL (Replace with your AWS account ID) +AWS_ACCOUNT_ID="586794444026" +ECR_URL="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com" + +# Define image names and their corresponding directories +IMAGE_NAMES=("agents" "mysuperagent-frontend") +IMAGE_DIRS=("../agents" "../frontend") + +# Authenticate with AWS ECR +echo "Logging into AWS ECR..." +aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ECR_URL + +# Loop through images to build, tag, and push +for ((i=0; i<${#IMAGE_NAMES[@]}; i++)); do + IMAGE_NAME="${IMAGE_NAMES[$i]}" + IMAGE_DIR="${IMAGE_DIRS[$i]}" + + echo "Building $IMAGE_NAME in directory $IMAGE_DIR..." + + # Navigate to the project directory + cd "$IMAGE_DIR" || { echo "Failed to navigate to $IMAGE_DIR"; exit 1; } + + # Build the Docker image + if [ "$IMAGE_NAME" == "agents" ]; then + docker build -t "$IMAGE_NAME" -f build/Dockerfile . + else + docker build -t "$IMAGE_NAME" . + fi + + # Navigate back to the root directory + cd - > /dev/null + + # Tag the image + echo "Tagging $IMAGE_NAME..." + docker tag "$IMAGE_NAME:latest" "$ECR_URL/$IMAGE_NAME:latest" + + # Push the image + echo "Pushing $IMAGE_NAME to ECR..." + docker push "$ECR_URL/$IMAGE_NAME:latest" + + echo "$IMAGE_NAME pushed successfully!" +done + +echo "All images have been built and pushed successfully!" diff --git a/build/docker-compose.yml b/build/docker-compose.yml new file mode 100755 index 00000000..435b0f06 --- /dev/null +++ b/build/docker-compose.yml @@ -0,0 +1,65 @@ +services: + agents: + image: lachsbagel/mysuperagent_dockers-agents:0.3.0 + build: + context: ../submodules/agents + dockerfile: build/Dockerfile + platform: ${PLATFORM:-linux/arm64} + ports: + - "8888:5000" + restart: always + volumes: + - mysuperagent_agents_data:/var/lib/agents + - ../submodules/agents/src:/app/src + extra_hosts: + - "host.docker.internal:host-gateway" + environment: + - BASE_URL=http://host.docker.internal:11434 + - DATABASE_URL=postgresql://neo:trinity@mor_postgres:5678/morpheus_db + working_dir: /app/src + command: poetry run uvicorn app:app --host 0.0.0.0 --port 5000 --reload + networks: + - mor_network + + frontend: + image: lachsbagel/mysuperagent_dockers-frontend:0.3.0 + build: + context: ../submodules/frontend + dockerfile: Dockerfile.dev + platform: ${PLATFORM:-linux/arm64} + ports: + - "3333:80" + volumes: + - ../submodules/frontend:/app + - /app/node_modules + environment: + - NODE_ENV=development + networks: + - mor_network + + postgres: + image: postgres:16-bullseye + container_name: mor_postgres + hostname: mor_postgres + command: -p 5678 + environment: + - POSTGRES_USER=neo + - POSTGRES_PASSWORD=trinity + - POSTGRES_DB=morpheus_db + ports: + - "5678:5678" + volumes: + - mor_pg_data:/var/lib/postgresql/data + restart: always + networks: + - mor_network + +volumes: + mysuperagent_agents_data: + mor_pg_data: + name: mor_pg_data + +networks: + mor_network: + driver: bridge + name: mor_network diff --git a/build/ecr-policy.json b/build/ecr-policy.json new file mode 100644 index 00000000..6d546d4f --- /dev/null +++ b/build/ecr-policy.json @@ -0,0 +1,15 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability" + ], + "Resource": "*" + } + ] +} diff --git a/build/moragents-stack.yml b/build/moragents-stack.yml new file mode 100644 index 00000000..ef5d875b --- /dev/null +++ b/build/moragents-stack.yml @@ -0,0 +1,200 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: "MySuperAgent Infrastructure - HTTPS enabled with ALB and ACM" + +Resources: + # Security Group for EC2 + InstanceSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: "Enable SSH, HTTP, and backend services" + VpcId: vpc-0e17a977e38b866c1 # βœ… Ensure the security group is in the correct VPC + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: 3333 + ToPort: 3333 + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: 8888 + ToPort: 8888 + CidrIp: 0.0.0.0/0 + + # Instance Profile (Reusing Existing IAM Role) + InstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Roles: + - cdk-hnb659fds-cfn-exec-role-586794444026-us-east-1 # βœ… Keep this + Path: "/" + + # EC2 Instance (Ensure Correct VPC & Subnet) + EC2Instance: + Type: AWS::EC2::Instance + Properties: + InstanceType: t4g.small + SubnetId: subnet-01abb34d1b3a008ac + SecurityGroupIds: + - !Ref InstanceSecurityGroup + KeyName: agents-key + IamInstanceProfile: !Ref InstanceProfile # βœ… Correctly references the profile + ImageId: ami-06114d52c0976a495 + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: 30 + VolumeType: gp2 + UserData: + Fn::Base64: !Sub | + #!/bin/bash + yum update -y + sudo yum install -y docker jq aws-cli + sudo systemctl start docker + sudo systemctl enable docker + sudo usermod -aG docker ec2-user + + # Get ECR auth token + aws ecr get-login-password --region us-west-1 | docker login --username AWS --password-stdin 586794444026.dkr.ecr.us-west-1.amazonaws.com + + # Pull and run the containers + docker pull 586794444026.dkr.ecr.us-west-1.amazonaws.com/agents:latest + docker pull 586794444026.dkr.ecr.us-west-1.amazonaws.com/mysuperagent-frontend:latest + + docker run -d --restart always -p 8888:5000 586794444026.dkr.ecr.us-west-1.amazonaws.com/agents:latest + docker run -d --restart always -p 3333:80 586794444026.dkr.ecr.us-west-1.amazonaws.com/mysuperagent-frontend:latest + + # Application Load Balancer (ALB) + ApplicationLoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Name: mysuperagentALB + Scheme: internet-facing + SecurityGroups: + - sg-0ab173f93f4f06ac1 # βœ… This is the correct security group (mysuperagent-alb-sg) + Subnets: + - subnet-01abb34d1b3a008ac + - subnet-0a852de834c421b24 + Type: application + + # ALB Target Group for Backend API + BackendTargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + Name: BackendTargetGroup + Port: 8888 + Protocol: HTTP + VpcId: vpc-0e17a977e38b866c1 # βœ… Ensures correct VPC association + TargetType: instance + Targets: + - Id: !Ref EC2Instance + + # ALB Target Group for Frontend + FrontendTargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + Name: FrontendTargetGroup + Port: 3333 + Protocol: HTTP + VpcId: vpc-0e17a977e38b866c1 # βœ… Ensures correct VPC association + TargetType: instance + Targets: + - Id: !Ref EC2Instance + + # Create ACM Certificate + Certificate: + Type: AWS::CertificateManager::Certificate + Properties: + DomainName: mysuperagent.io + SubjectAlternativeNames: + - "*.mysuperagent.io" + ValidationMethod: DNS + DomainValidationOptions: + - DomainName: mysuperagent.io + HostedZoneId: Z08521642MAYLL3XZZMIN + + # HTTPS Listener for ALB - Make FrontendTargetGroup the default + HTTPSListener: + Type: AWS::ElasticLoadBalancingV2::Listener + DependsOn: Certificate + Properties: + LoadBalancerArn: !Ref ApplicationLoadBalancer + Port: 443 + Protocol: HTTPS + SslPolicy: ELBSecurityPolicy-2016-08 + Certificates: + - CertificateArn: !Ref Certificate + DefaultActions: + - Type: forward + TargetGroupArn: !Ref FrontendTargetGroup + + # API Path Listener Rule - Forward /api/* to BackendTargetGroup + APIListenerRule: + Type: AWS::ElasticLoadBalancingV2::ListenerRule + Properties: + ListenerArn: !Ref HTTPSListener + Priority: 10 + Conditions: + - Field: host-header + Values: + - api.mysuperagent.io + Actions: + - Type: forward + TargetGroupArn: !Ref BackendTargetGroup + + # HTTP Listener (Redirects to HTTPS) + HTTPListener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + LoadBalancerArn: !Ref ApplicationLoadBalancer + Port: 80 + Protocol: HTTP + DefaultActions: + - Type: redirect + RedirectConfig: + Protocol: HTTPS + Port: "443" + StatusCode: HTTP_301 + + # Route 53 DNS for Main Domain + DNSRecord: + Type: AWS::Route53::RecordSet + Properties: + HostedZoneId: Z08521642MAYLL3XZZMIN + Name: mysuperagent.io. + Type: A + AliasTarget: + DNSName: !GetAtt ApplicationLoadBalancer.DNSName + HostedZoneId: !GetAtt ApplicationLoadBalancer.CanonicalHostedZoneID + + # Route 53 DNS for API Subdomain + ApiDNSRecord: + Type: AWS::Route53::RecordSet + Properties: + HostedZoneId: Z08521642MAYLL3XZZMIN + Name: api.mysuperagent.io. + Type: A + AliasTarget: + DNSName: !GetAtt ApplicationLoadBalancer.DNSName + HostedZoneId: !GetAtt ApplicationLoadBalancer.CanonicalHostedZoneID + +Outputs: + InstanceId: + Description: ID of the EC2 instance + Value: !Ref EC2Instance + LoadBalancerDNS: + Description: DNS of the ALB + Value: !GetAtt ApplicationLoadBalancer.DNSName + FrontendURL: + Description: Frontend URL + Value: https://mysuperagent.io + ApiURL: + Description: API URL + Value: https://api.mysuperagent.io + CertificateARN: + Description: The ARN of the created certificate + Value: !Ref Certificate + SSHCommand: + Description: SSH Command + Value: !Sub ssh -i agents-key.pem ec2-user@${EC2Instance.PublicIp} diff --git a/build_assets/linux/install_moragents.sh b/build_assets/linux/install_moragents.sh deleted file mode 100644 index 316d2694..00000000 --- a/build_assets/linux/install_moragents.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color - -# Check if script is run as root -if [ "$EUID" -ne 0 ]; then - echo -e "${RED}Please run as root${NC}" - exit 1 -fi - -echo -e "${GREEN}Welcome to the MORagents installer for Linux${NC}" - -# Install the .deb package -echo -e "${YELLOW}Installing MORagents...${NC}" -dpkg -i moragents.deb -apt-get install -f -y - -# Run the setup script -echo -e "${YELLOW}Running MORagents setup...${NC}" -./moragents-setup.sh - -echo -e "${GREEN}Installation complete!${NC}" -echo "You can now start MORagents from your application menu or by running 'MORagents' in the terminal." -echo -e "${YELLOW}NOTE: Please log out and log back in for Docker group changes to take effect.${NC}" diff --git a/build_assets/macOS/MORagents.entitlements b/build_assets/macOS/MORagents.entitlements deleted file mode 100644 index 496c3cc4..00000000 --- a/build_assets/macOS/MORagents.entitlements +++ /dev/null @@ -1,14 +0,0 @@ - - - - - com.apple.security.network.client - - com.apple.security.network.server - - com.apple.security.network.client.server - - com.apple.security.automation.apple-events - - - diff --git a/build_assets/macOS/MorpheusPackagesSudre.pkgproj b/build_assets/macOS/MorpheusPackagesSudre.pkgproj deleted file mode 100644 index 439f41b3..00000000 --- a/build_assets/macOS/MorpheusPackagesSudre.pkgproj +++ /dev/null @@ -1,1932 +0,0 @@ - - - - - PACKAGES - - - MUST-CLOSE-APPLICATION-ITEMS - - MUST-CLOSE-APPLICATIONS - - PACKAGE_FILES - - DEFAULT_INSTALL_LOCATION - / - HIERARCHY - - CHILDREN - - - CHILDREN - - - BUNDLE_CAN_DOWNGRADE - - BUNDLE_POSTINSTALL_PATH - - PATH_TYPE - 1 - - BUNDLE_PREINSTALL_PATH - - PATH_TYPE - 1 - - CHILDREN - - GID - 80 - PATH - MORagents.app - PATH_TYPE - 1 - PERMISSIONS - 493 - TYPE - 3 - UID - 0 - - - GID - 80 - PATH - Applications - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Application Support - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Automator - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Documentation - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Extensions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Filesystems - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Frameworks - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Input Methods - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Internet Plug-Ins - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Keyboard Layouts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchAgents - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchDaemons - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PreferencePanes - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Preferences - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Printers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PrivilegedHelperTools - PATH_TYPE - 0 - PERMISSIONS - 1005 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickLook - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickTime - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Screen Savers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Scripts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Services - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Widgets - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Shared - PATH_TYPE - 0 - PERMISSIONS - 1023 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Users - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - / - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - PAYLOAD_TYPE - 0 - PRESERVE_EXTENDED_ATTRIBUTES - - SHOW_INVISIBLE - - SPLIT_FORKS - - TREAT_MISSING_FILES_AS_WARNING - - VERSION - 5 - - PACKAGE_SCRIPTS - - POSTINSTALL_PATH - - PATH - postinstall.sh - PATH_TYPE - 1 - - PREINSTALL_PATH - - PATH - preinstall.sh - PATH_TYPE - 1 - - RESOURCES - - - PACKAGE_SETTINGS - - AUTHENTICATION - 1 - CONCLUSION_ACTION - 0 - FOLLOW_SYMBOLIC_LINKS - - IDENTIFIER - com.morpheus.pkg.MORAgents - LOCATION - 0 - NAME - MORAgents - OVERWRITE_PERMISSIONS - - PAYLOAD_SIZE - -1 - REFERENCE_PATH - - RELOCATABLE - - USE_HFS+_COMPRESSION - - VERSION - 1.0 - - TYPE - 0 - UUID - 2954F3A7-88A3-4C44-9062-BD8EA2D9DB60 - - - MUST-CLOSE-APPLICATION-ITEMS - - MUST-CLOSE-APPLICATIONS - - PACKAGE_FILES - - DEFAULT_INSTALL_LOCATION - / - HIERARCHY - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Applications - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Application Support - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Automator - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Documentation - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Extensions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Filesystems - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Frameworks - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Input Methods - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Internet Plug-Ins - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Keyboard Layouts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchAgents - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchDaemons - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PreferencePanes - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Preferences - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Printers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PrivilegedHelperTools - PATH_TYPE - 0 - PERMISSIONS - 1005 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickLook - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickTime - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Screen Savers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Scripts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Services - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Widgets - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Shared - PATH_TYPE - 0 - PERMISSIONS - 1023 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Users - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - / - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - PAYLOAD_TYPE - 0 - PRESERVE_EXTENDED_ATTRIBUTES - - SHOW_INVISIBLE - - SPLIT_FORKS - - TREAT_MISSING_FILES_AS_WARNING - - VERSION - 5 - - PACKAGE_SCRIPTS - - POSTINSTALL_PATH - - PATH_TYPE - 1 - - PREINSTALL_PATH - - PATH - preinstall_docker.sh - PATH_TYPE - 1 - - RESOURCES - - - PACKAGE_SETTINGS - - AUTHENTICATION - 1 - CONCLUSION_ACTION - 0 - FOLLOW_SYMBOLIC_LINKS - - IDENTIFIER - com.morpheus.pkg.Docker - LOCATION - 0 - NAME - Docker - OVERWRITE_PERMISSIONS - - PAYLOAD_SIZE - -1 - REFERENCE_PATH - - RELOCATABLE - - USE_HFS+_COMPRESSION - - VERSION - 1.0 - - TYPE - 0 - UUID - 17D68BA0-D319-4CB2-9C33-5FBF7CD96392 - - - MUST-CLOSE-APPLICATION-ITEMS - - MUST-CLOSE-APPLICATIONS - - PACKAGE_FILES - - DEFAULT_INSTALL_LOCATION - / - HIERARCHY - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Applications - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Application Support - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Automator - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Documentation - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Extensions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Filesystems - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Frameworks - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Input Methods - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Internet Plug-Ins - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Keyboard Layouts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchAgents - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchDaemons - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PreferencePanes - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Preferences - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Printers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PrivilegedHelperTools - PATH_TYPE - 0 - PERMISSIONS - 1005 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickLook - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickTime - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Screen Savers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Scripts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Services - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Widgets - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Shared - PATH_TYPE - 0 - PERMISSIONS - 1023 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Users - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - / - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - PAYLOAD_TYPE - 0 - PRESERVE_EXTENDED_ATTRIBUTES - - SHOW_INVISIBLE - - SPLIT_FORKS - - TREAT_MISSING_FILES_AS_WARNING - - VERSION - 5 - - PACKAGE_SCRIPTS - - POSTINSTALL_PATH - - PATH_TYPE - 1 - - PREINSTALL_PATH - - PATH - preinstall_ollama.sh - PATH_TYPE - 1 - - RESOURCES - - - PACKAGE_SETTINGS - - AUTHENTICATION - 1 - CONCLUSION_ACTION - 0 - FOLLOW_SYMBOLIC_LINKS - - IDENTIFIER - com.morpheus.pkg.OllamaBrew - LOCATION - 0 - NAME - OllamaBrew - OVERWRITE_PERMISSIONS - - PAYLOAD_SIZE - -1 - REFERENCE_PATH - - RELOCATABLE - - USE_HFS+_COMPRESSION - - VERSION - 1.0 - - TYPE - 0 - UUID - A6B4C36F-B0FC-417F-9B18-59E66B8D78DC - - - PROJECT - - PROJECT_COMMENTS - - NOTES - - - - PROJECT_PRESENTATION - - BACKGROUND - - APPAREANCES - - DARK_AQUA - - LIGHT_AQUA - - - SHARED_SETTINGS_FOR_ALL_APPAREANCES - - - INSTALLATION TYPE - - HIERARCHIES - - INSTALLER - - LIST - - - CHILDREN - - DESCRIPTION - - OPTIONS - - HIDDEN - - STATE - 1 - - PACKAGE_UUID - 2954F3A7-88A3-4C44-9062-BD8EA2D9DB60 - TITLE - - TYPE - 0 - UUID - F1F121E0-7C16-4710-B85B-C10E4F7715DC - - - CHILDREN - - DESCRIPTION - - OPTIONS - - HIDDEN - - STATE - 1 - - PACKAGE_UUID - 17D68BA0-D319-4CB2-9C33-5FBF7CD96392 - TITLE - - TYPE - 0 - UUID - 4C046B91-0499-4942-A1F5-C9517C6FFFA0 - - - CHILDREN - - DESCRIPTION - - OPTIONS - - HIDDEN - - STATE - 1 - - PACKAGE_UUID - A6B4C36F-B0FC-417F-9B18-59E66B8D78DC - TITLE - - TYPE - 0 - UUID - 7120666F-EAF2-49DF-A382-C69590CD0905 - - - REMOVED - - - - MODE - 0 - - INSTALLATION_STEPS - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewIntroductionController - INSTALLER_PLUGIN - Introduction - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewReadMeController - INSTALLER_PLUGIN - ReadMe - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewLicenseController - INSTALLER_PLUGIN - License - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewDestinationSelectController - INSTALLER_PLUGIN - TargetSelect - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewInstallationTypeController - INSTALLER_PLUGIN - PackageSelection - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewInstallationController - INSTALLER_PLUGIN - Install - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewSummaryController - INSTALLER_PLUGIN - Summary - LIST_TITLE_KEY - InstallerSectionTitle - - - INTRODUCTION - - LOCALIZATIONS - - - LANGUAGE - English - VALUE - - PATH - welcome.html - PATH_TYPE - 1 - - - - - LICENSE - - LOCALIZATIONS - - - LANGUAGE - English - VALUE - - PATH - license.html - PATH_TYPE - 1 - - - - MODE - 0 - - README - - LOCALIZATIONS - - - SUMMARY - - LOCALIZATIONS - - - TITLE - - LOCALIZATIONS - - - LANGUAGE - English - VALUE - MORAgents - - - - - PROJECT_REQUIREMENTS - - LIST - - RESOURCES - - ROOT_VOLUME_ONLY - - - PROJECT_SETTINGS - - BUILD_FORMAT - 0 - BUILD_PATH - - PATH - . - PATH_TYPE - 1 - - EXCLUDED_FILES - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - .DS_Store - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove .DS_Store files - PROXY_TOOLTIP - Remove ".DS_Store" files created by the Finder. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - .pbdevelopment - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove .pbdevelopment files - PROXY_TOOLTIP - Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - CVS - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .cvsignore - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - .cvspass - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - .svn - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .git - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .gitignore - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove SCM metadata - PROXY_TOOLTIP - Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - classes.nib - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - designable.db - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - info.nib - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Optimize nib files - PROXY_TOOLTIP - Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - Resources Disabled - TYPE - 1 - - - PROTECTED - - PROXY_NAME - Remove Resources Disabled folders - PROXY_TOOLTIP - Remove "Resources Disabled" folders. - STATE - - - - SEPARATOR - - - - NAME - MORAgentsInstaller - PAYLOAD_ONLY - - TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING - - - - TYPE - 0 - VERSION - 2 - - diff --git a/build_assets/macOS/MorpheusPackagesSudreIntel.pkgproj b/build_assets/macOS/MorpheusPackagesSudreIntel.pkgproj deleted file mode 100644 index 12e7eadf..00000000 --- a/build_assets/macOS/MorpheusPackagesSudreIntel.pkgproj +++ /dev/null @@ -1,1932 +0,0 @@ - - - - - PACKAGES - - - MUST-CLOSE-APPLICATION-ITEMS - - MUST-CLOSE-APPLICATIONS - - PACKAGE_FILES - - DEFAULT_INSTALL_LOCATION - / - HIERARCHY - - CHILDREN - - - CHILDREN - - - BUNDLE_CAN_DOWNGRADE - - BUNDLE_POSTINSTALL_PATH - - PATH_TYPE - 1 - - BUNDLE_PREINSTALL_PATH - - PATH_TYPE - 1 - - CHILDREN - - GID - 80 - PATH - MORagents.app - PATH_TYPE - 1 - PERMISSIONS - 493 - TYPE - 3 - UID - 0 - - - GID - 80 - PATH - Applications - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Application Support - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Automator - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Documentation - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Extensions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Filesystems - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Frameworks - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Input Methods - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Internet Plug-Ins - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Keyboard Layouts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchAgents - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchDaemons - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PreferencePanes - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Preferences - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Printers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PrivilegedHelperTools - PATH_TYPE - 0 - PERMISSIONS - 1005 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickLook - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickTime - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Screen Savers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Scripts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Services - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Widgets - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Shared - PATH_TYPE - 0 - PERMISSIONS - 1023 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Users - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - / - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - PAYLOAD_TYPE - 0 - PRESERVE_EXTENDED_ATTRIBUTES - - SHOW_INVISIBLE - - SPLIT_FORKS - - TREAT_MISSING_FILES_AS_WARNING - - VERSION - 5 - - PACKAGE_SCRIPTS - - POSTINSTALL_PATH - - PATH - postinstall_intel.sh - PATH_TYPE - 1 - - PREINSTALL_PATH - - PATH - preinstall.sh - PATH_TYPE - 1 - - RESOURCES - - - PACKAGE_SETTINGS - - AUTHENTICATION - 1 - CONCLUSION_ACTION - 0 - FOLLOW_SYMBOLIC_LINKS - - IDENTIFIER - com.morpheus.pkg.MORAgents - LOCATION - 0 - NAME - MORAgents - OVERWRITE_PERMISSIONS - - PAYLOAD_SIZE - -1 - REFERENCE_PATH - - RELOCATABLE - - USE_HFS+_COMPRESSION - - VERSION - 1.0 - - TYPE - 0 - UUID - 2954F3A7-88A3-4C44-9062-BD8EA2D9DB60 - - - MUST-CLOSE-APPLICATION-ITEMS - - MUST-CLOSE-APPLICATIONS - - PACKAGE_FILES - - DEFAULT_INSTALL_LOCATION - / - HIERARCHY - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Applications - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Application Support - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Automator - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Documentation - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Extensions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Filesystems - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Frameworks - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Input Methods - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Internet Plug-Ins - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Keyboard Layouts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchAgents - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchDaemons - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PreferencePanes - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Preferences - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Printers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PrivilegedHelperTools - PATH_TYPE - 0 - PERMISSIONS - 1005 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickLook - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickTime - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Screen Savers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Scripts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Services - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Widgets - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Shared - PATH_TYPE - 0 - PERMISSIONS - 1023 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Users - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - / - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - PAYLOAD_TYPE - 0 - PRESERVE_EXTENDED_ATTRIBUTES - - SHOW_INVISIBLE - - SPLIT_FORKS - - TREAT_MISSING_FILES_AS_WARNING - - VERSION - 5 - - PACKAGE_SCRIPTS - - POSTINSTALL_PATH - - PATH_TYPE - 1 - - PREINSTALL_PATH - - PATH - preinstall_docker_intel.sh - PATH_TYPE - 1 - - RESOURCES - - - PACKAGE_SETTINGS - - AUTHENTICATION - 1 - CONCLUSION_ACTION - 0 - FOLLOW_SYMBOLIC_LINKS - - IDENTIFIER - com.morpheus.pkg.Docker - LOCATION - 0 - NAME - Docker - OVERWRITE_PERMISSIONS - - PAYLOAD_SIZE - -1 - REFERENCE_PATH - - RELOCATABLE - - USE_HFS+_COMPRESSION - - VERSION - 1.0 - - TYPE - 0 - UUID - 17D68BA0-D319-4CB2-9C33-5FBF7CD96392 - - - MUST-CLOSE-APPLICATION-ITEMS - - MUST-CLOSE-APPLICATIONS - - PACKAGE_FILES - - DEFAULT_INSTALL_LOCATION - / - HIERARCHY - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Applications - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Application Support - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Automator - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Documentation - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Extensions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Filesystems - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Frameworks - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Input Methods - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Internet Plug-Ins - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Keyboard Layouts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchAgents - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchDaemons - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PreferencePanes - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Preferences - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Printers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PrivilegedHelperTools - PATH_TYPE - 0 - PERMISSIONS - 1005 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickLook - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickTime - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Screen Savers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Scripts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Services - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Widgets - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Shared - PATH_TYPE - 0 - PERMISSIONS - 1023 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Users - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - / - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - PAYLOAD_TYPE - 0 - PRESERVE_EXTENDED_ATTRIBUTES - - SHOW_INVISIBLE - - SPLIT_FORKS - - TREAT_MISSING_FILES_AS_WARNING - - VERSION - 5 - - PACKAGE_SCRIPTS - - POSTINSTALL_PATH - - PATH_TYPE - 1 - - PREINSTALL_PATH - - PATH - preinstall_ollama.sh - PATH_TYPE - 1 - - RESOURCES - - - PACKAGE_SETTINGS - - AUTHENTICATION - 1 - CONCLUSION_ACTION - 0 - FOLLOW_SYMBOLIC_LINKS - - IDENTIFIER - com.morpheus.pkg.OllamaBrew - LOCATION - 0 - NAME - OllamaBrew - OVERWRITE_PERMISSIONS - - PAYLOAD_SIZE - -1 - REFERENCE_PATH - - RELOCATABLE - - USE_HFS+_COMPRESSION - - VERSION - 1.0 - - TYPE - 0 - UUID - A6B4C36F-B0FC-417F-9B18-59E66B8D78DC - - - PROJECT - - PROJECT_COMMENTS - - NOTES - - - - PROJECT_PRESENTATION - - BACKGROUND - - APPAREANCES - - DARK_AQUA - - LIGHT_AQUA - - - SHARED_SETTINGS_FOR_ALL_APPAREANCES - - - INSTALLATION TYPE - - HIERARCHIES - - INSTALLER - - LIST - - - CHILDREN - - DESCRIPTION - - OPTIONS - - HIDDEN - - STATE - 1 - - PACKAGE_UUID - 2954F3A7-88A3-4C44-9062-BD8EA2D9DB60 - TITLE - - TYPE - 0 - UUID - F1F121E0-7C16-4710-B85B-C10E4F7715DC - - - CHILDREN - - DESCRIPTION - - OPTIONS - - HIDDEN - - STATE - 1 - - PACKAGE_UUID - 17D68BA0-D319-4CB2-9C33-5FBF7CD96392 - TITLE - - TYPE - 0 - UUID - 4C046B91-0499-4942-A1F5-C9517C6FFFA0 - - - CHILDREN - - DESCRIPTION - - OPTIONS - - HIDDEN - - STATE - 1 - - PACKAGE_UUID - A6B4C36F-B0FC-417F-9B18-59E66B8D78DC - TITLE - - TYPE - 0 - UUID - 7120666F-EAF2-49DF-A382-C69590CD0905 - - - REMOVED - - - - MODE - 0 - - INSTALLATION_STEPS - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewIntroductionController - INSTALLER_PLUGIN - Introduction - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewReadMeController - INSTALLER_PLUGIN - ReadMe - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewLicenseController - INSTALLER_PLUGIN - License - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewDestinationSelectController - INSTALLER_PLUGIN - TargetSelect - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewInstallationTypeController - INSTALLER_PLUGIN - PackageSelection - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewInstallationController - INSTALLER_PLUGIN - Install - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewSummaryController - INSTALLER_PLUGIN - Summary - LIST_TITLE_KEY - InstallerSectionTitle - - - INTRODUCTION - - LOCALIZATIONS - - - LANGUAGE - English - VALUE - - PATH - welcome.html - PATH_TYPE - 1 - - - - - LICENSE - - LOCALIZATIONS - - - LANGUAGE - English - VALUE - - PATH - license.html - PATH_TYPE - 1 - - - - MODE - 0 - - README - - LOCALIZATIONS - - - SUMMARY - - LOCALIZATIONS - - - TITLE - - LOCALIZATIONS - - - LANGUAGE - English - VALUE - MORAgents - - - - - PROJECT_REQUIREMENTS - - LIST - - RESOURCES - - ROOT_VOLUME_ONLY - - - PROJECT_SETTINGS - - BUILD_FORMAT - 0 - BUILD_PATH - - PATH - . - PATH_TYPE - 1 - - EXCLUDED_FILES - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - .DS_Store - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove .DS_Store files - PROXY_TOOLTIP - Remove ".DS_Store" files created by the Finder. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - .pbdevelopment - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove .pbdevelopment files - PROXY_TOOLTIP - Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - CVS - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .cvsignore - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - .cvspass - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - .svn - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .git - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .gitignore - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove SCM metadata - PROXY_TOOLTIP - Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - classes.nib - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - designable.db - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - info.nib - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Optimize nib files - PROXY_TOOLTIP - Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - Resources Disabled - TYPE - 1 - - - PROTECTED - - PROXY_NAME - Remove Resources Disabled folders - PROXY_TOOLTIP - Remove "Resources Disabled" folders. - STATE - - - - SEPARATOR - - - - NAME - MORAgentsInstaller - PAYLOAD_ONLY - - TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING - - - - TYPE - 0 - VERSION - 2 - - diff --git a/build_assets/macOS/Packaging_Instructions_macOS.md b/build_assets/macOS/Packaging_Instructions_macOS.md deleted file mode 100644 index 992088c6..00000000 --- a/build_assets/macOS/Packaging_Instructions_macOS.md +++ /dev/null @@ -1,62 +0,0 @@ -## Distribution - -## Build and Sign MORagents.app -```sh -pyinstaller --windowed --osx-bundle-identifier "com.liquidtensor.moragents" --codesign-identity "Developer ID Application: Liquid Tensor LLC (ZQN244GMTD)" --name="MORagents" --icon="images/moragents.icns" --osx-entitlements-file "build_assets/macOS/MORagents.entitlements" main.py -``` -NOTE: you will need to use your own codesigning identity if you intend to distribute. Codesigining is not required for running it on the same mac you built the .app on. For this you can drop the ```--codesign-identity "Developer ID Application: Liquid Tensor LLC (ZQN244GMTD)"``` part from the command. - -## Wizard Creation -1. Install Packages app dev version [Packages App](http://s.sudre.free.fr/Software/Packages/about.html) [packages_1211_dev.dmg](http://s.sudre.free.fr/files/Packages_1211_dev.dmg) -3. $ mv dist/MORagents.app build_assets/macOS/ -2. $ cd build_assets/macOS -3. $ /usr/local/bin/packagesbuild --verbose --project MorpheusPackagesSudre.pkgproj - -#1. Download the dev version of the Packages app (it has to be the dev version because the latest build doesn't work on mac sonoma/last updated 2022) [Packages App](http://s.sudre.free.fr/Software/Packages/about.html) [packages_1211_dev.dmg](http://s.sudre.free.fr/files/Packages_1211_dev.dmg) -#2. Download the files in https://github.com/MorpheusAIs/moragents/tree/main/build_assets/macOS, along with [Docker Desktop Mac Install](https://docs.docker.com/desktop/install/mac-install/) and the MORAgents.app you built in previous step. -#In the Packages App...\ -# 1. Create a New Project ... set the template as "Distribution".\ -# 2. Under the Packages, either edit the existing package or create a new one. Set "Identifier" to com.morpheus.pkg.MORAgents, set the Payload to have MORAgents.app under /Applications, then under Scripts, add the preinstall.sh and postinstall.sh from the downloaded files.\ -# 3. Create a new Package, Set "Identifier" to com.morpheus.pkg.DockerDesktop, then under Payload, add the Docker Desktop Mac Install under /Applications\ -# 4. Create a new Package, Set "Identifier" to com.morpheus.pkg.Ollama, then under preinstall scripts, add the preinstall_ollama.sh script from the downloaded files.\ -# 5. Navigate to Project, then in Presentation click the topright dropdown and select Introduction choose the welcome.html file, add a License section and choose license.html. -# 6. In the upmost toolbar click Build -> Build to generate a .pkg file in the directory you saved the MORagents Packages package - ---- - ->Note: Following steps are only required for distribution. -If you are just running the app on your own Mac you can run the .pkg you created, and then open MORagents from your searchbar. -Future usage only requires you to run MORagents from your searchbar. - ---- - -## Signing -```sh - productsign --sign "Developer ID Installer: Liquid Tensor LLC (ZQN244GMTD)" MORAgentsInstaller.pkg MORagents022-apple.pkg -``` - -## Notarize -```sh -xcrun notarytool submit MORagents022-apple.pkg --keychain-profile "NotaryProfile" --wait -``` - -## Staple -```sh -xcrun stapler staple MORagents022-apple.pkg -``` - ---- - -## Verify (Optional) - -### Verify notarization -```sh -xcrun notarytool info "" --keychain-profile "NotaryProfile" -``` - -### Verify codesign -```sh -codesign --verify --verbose=4 MORagents.app - -codesign -dv --verbose=4 MORagents.app -``` diff --git a/build_assets/macOS/README_MACOS_DEV_BUILD.md b/build_assets/macOS/README_MACOS_DEV_BUILD.md deleted file mode 100644 index 29d355c7..00000000 --- a/build_assets/macOS/README_MACOS_DEV_BUILD.md +++ /dev/null @@ -1,55 +0,0 @@ -## For Developers - -This README will guide you on building a Docker container for agent execution as well as a desktop UI app. -You may instead simply download the [pre-built app](../../README.md) - -#### macOS -1. Ensure you have Python, Pip, [Docker Desktop](https://www.docker.com/products/docker-desktop/), and Git installed. Note: please install python version 3.10.0+, older versions will produce outdated versions of tkinter when running the requirements install. - - -2. Clone Repo -```shell - $ git clone https://github.com/MorpheusAIs/moragents - $ cd moragents -``` - -3. Build Docker Image for Local Agent Execution - -```shell -For ARM (M1, M2, M3) - $ docker-compose -f submodules/moragents_dockers/docker-compose-apple.yml up - -For Intel (x86_64) - $ docker-compose -f submodules/moragents_dockers/docker-compose.yml up -``` - - -4. Install Deps for UI, Recommended to use virtualenv -```shell - $ python3 -m venv .venv - $ . .venv/bin/activate - $ pip install -r requirements.txt -``` - -5. Build App for Local Installation -```shell - $ pyinstaller --windowed --name="MORagents" --icon="images/moragents.icns" --osx-entitlements-file "build_assets/macOS/MORagents.entitlements" main.py -``` - # If you have issues, try - python -m PyInstaller --windowed --name="MORagents" --icon="images/moragents.icns" --osx-entitlements-file "build_assets/macOS/MORagents.entitlements" main.py - -6. Install Application -```shell - $ cp dist/MORagents.app /Applications -``` - -7. Open the ```MORagents``` app on your Mac, Docker needs to be running before opening MORagents - --- -### Signing, Notarization, and Stapling for Distribution -Instructions are [here](Packaging_Instructions_macOS.md). - --- - - -Windows build instructions can be found [here](../windows/README_WINDOWS_DEV_BUILD.md) diff --git a/build_assets/macOS/license.html b/build_assets/macOS/license.html deleted file mode 100644 index f50d127f..00000000 --- a/build_assets/macOS/license.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - -

License Agreement

-

Please read the following License Agreement carefully. By continuing, you acknowledge that you have read and agreed to the License.

- -
-MIT License - -Copyright (c) 2024 Liquid Tensor LLC - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -
- -

By clicking Continue, installing, and/or using the MORagents software you accept the terms of the License agreement.

- - diff --git a/build_assets/macOS/postinstall.sh b/build_assets/macOS/postinstall.sh deleted file mode 100644 index 68cced52..00000000 --- a/build_assets/macOS/postinstall.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/bash - -# Function to log messages -log_message() { - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# Function to check if an application is running -is_app_running() { - app_name=$1 - pgrep -x "$app_name" >/dev/null -} - -# Function to attempt opening Docker with retries -open_docker_with_retry() { - max_attempts=5 - attempt=1 - while [ $attempt -le $max_attempts ]; do - log_message "Attempt $attempt to open Docker.app" - if [ -d "/Applications/Docker.app" ]; then - if open -a "Docker.app" 2>/dev/null; then - log_message "Docker.app opened successfully" - return 0 - else - log_message "Failed to open Docker.app, waiting before retry..." - fi - else - log_message "Docker.app not found in /Applications" - fi - sleep 10 - attempt=$((attempt+1)) - done - log_message "Failed to open Docker.app after $max_attempts attempts" - return 1 -} - -# Main script starts here -log_message "Starting post-install script" - -# Wait for a bit to ensure Docker installation is complete -log_message "Waiting for 30 seconds to ensure Docker installation is complete..." -sleep 30 - -# Attempt to open Docker -if ! open_docker_with_retry; then - log_message "Warning: Could not open Docker.app. It may need to be opened manually." -fi - -# Set the timeout duration (in seconds) -timeout=300 # 5 minutes - -# Wait for Docker Desktop to be running -log_message "Waiting for Docker Desktop to start..." -start_time=$(date +%s) -while ! is_app_running "Docker Desktop"; do - current_time=$(date +%s) - elapsed_time=$((current_time - start_time)) - - if [ $elapsed_time -ge $timeout ]; then - log_message "Warning: Docker Desktop did not start within the specified timeout." - break - fi - - sleep 5 -done - -if is_app_running "Docker Desktop"; then - log_message "Docker Desktop is running." -else - log_message "Warning: Docker Desktop is not running. It may need to be started manually." -fi - -# Open MORAgents.app -if [ -d "/Applications/MORAgents.app" ]; then - if open -a "MORAgents.app" 2>/dev/null; then - log_message "MORAgents.app opened successfully" - else - log_message "Warning: Failed to open MORAgents.app. It may need to be opened manually." - fi -else - log_message "Error: MORAgents.app not found in /Applications" -fi - -log_message "Post-install script completed." -exit 0 diff --git a/build_assets/macOS/postinstall_intel.sh b/build_assets/macOS/postinstall_intel.sh deleted file mode 100644 index c78a2e24..00000000 --- a/build_assets/macOS/postinstall_intel.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash - -# Function to log messages -log_message() { - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# Check if running on Intel Mac -if [ "$(uname -m)" != "x86_64" ]; then - log_message "Error: This script is for Intel-based Macs only." - exit 1 -fi - -# Function to check if an application is running -is_app_running() { - app_name=$1 - pgrep -x "$app_name" >/dev/null -} - -# Function to attempt opening Docker with retries -open_docker_with_retry() { - max_attempts=5 - attempt=1 - while [ $attempt -le $max_attempts ]; do - log_message "Attempt $attempt to open Docker.app" - if [ -d "/Applications/Docker.app" ]; then - if open -a "Docker.app" 2>/dev/null; then - log_message "Docker.app opened successfully" - return 0 - else - log_message "Failed to open Docker.app, waiting before retry..." - fi - else - log_message "Docker.app not found in /Applications" - fi - sleep 10 - attempt=$((attempt+1)) - done - log_message "Failed to open Docker.app after $max_attempts attempts" - return 1 -} - -# Main script starts here -log_message "Starting post-install script" - -# Wait for a bit to ensure Docker installation is complete -log_message "Waiting for 30 seconds to ensure Docker installation is complete..." -sleep 30 - -# Attempt to open Docker -if ! open_docker_with_retry; then - log_message "Warning: Could not open Docker.app. It may need to be opened manually." -fi - -# Set the timeout duration (in seconds) -timeout=300 # 5 minutes - -# Wait for Docker Desktop to be running -log_message "Waiting for Docker Desktop to start..." -start_time=$(date +%s) -while ! is_app_running "Docker Desktop"; do - current_time=$(date +%s) - elapsed_time=$((current_time - start_time)) - - if [ $elapsed_time -ge $timeout ]; then - log_message "Warning: Docker Desktop did not start within the specified timeout." - break - fi - - sleep 5 -done - -if is_app_running "Docker Desktop"; then - log_message "Docker Desktop is running." -else - log_message "Warning: Docker Desktop is not running. It may need to be started manually." -fi - -# Open MORAgents.app -if [ -d "/Applications/MORAgents.app" ]; then - if open -a "MORAgents.app" 2>/dev/null; then - log_message "MORAgents.app opened successfully" - else - log_message "Warning: Failed to open MORAgents.app. It may need to be opened manually." - fi -else - log_message "Error: MORAgents.app not found in /Applications" -fi - -log_message "Post-install script completed." -exit 0 diff --git a/build_assets/macOS/preinstall.sh b/build_assets/macOS/preinstall.sh deleted file mode 100644 index a18eddb3..00000000 --- a/build_assets/macOS/preinstall.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -python_version=$(python3 --version 2>&1 | awk '{print $2}') -if [[ "$python_version" < "3.12.0" ]]; then - echo "Installing Python 3.12.0..." - curl -O https://www.python.org/ftp/python/3.12.0/python-3.12.0-macos11.pkg - sudo installer -pkg python-3.12.0-macos11.pkg -target / - echo "Python 3.12.0 has been successfully installed." - rm python-3.12.0-macos11.pkg -else - echo "Python version is already up to date." -fi diff --git a/build_assets/macOS/preinstall_docker.sh b/build_assets/macOS/preinstall_docker.sh deleted file mode 100644 index 57b541a8..00000000 --- a/build_assets/macOS/preinstall_docker.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -# Function to log messages -log_message() { - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# Set variables -DOCKER_DMG_URL="https://desktop.docker.com/mac/main/arm64/Docker.dmg" -DOCKER_DMG="Docker.dmg" -VOLUME_NAME="Docker" -INSTALL_PATH="/Volumes/$VOLUME_NAME/Docker.app/Contents/MacOS/install" - -# Download Docker -log_message "Downloading Docker..." -if curl -L "$DOCKER_DMG_URL" -o "$DOCKER_DMG"; then - log_message "Docker download completed." -else - log_message "Error: Failed to download Docker." - exit 1 -fi - -# Mount the DMG -log_message "Mounting Docker DMG..." -if hdiutil attach "$DOCKER_DMG"; then - log_message "Docker DMG mounted successfully." -else - log_message "Error: Failed to mount Docker DMG." - exit 1 -fi - -# Run the installer -log_message "Running Docker installer..." -if "$INSTALL_PATH" --accept-license; then - log_message "Docker installation completed." -else - log_message "Error: Docker installation failed." - hdiutil detach "/Volumes/$VOLUME_NAME" - exit 1 -fi - -# Detach the DMG -log_message "Detaching Docker DMG..." -if hdiutil detach "/Volumes/$VOLUME_NAME"; then - log_message "Docker DMG detached successfully." -else - log_message "Warning: Failed to detach Docker DMG. This is not critical." -fi - -# Clean up -log_message "Cleaning up..." -rm -f "$DOCKER_DMG" - -log_message "Docker preinstall script completed successfully." -exit 0 diff --git a/build_assets/macOS/preinstall_docker_intel.sh b/build_assets/macOS/preinstall_docker_intel.sh deleted file mode 100644 index 1ef67c59..00000000 --- a/build_assets/macOS/preinstall_docker_intel.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash - -# Function to log messages -log_message() { - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" -} - -# Check if running on Intel Mac -if [ "$(uname -m)" != "x86_64" ]; then - log_message "Error: This script is for Intel-based Macs only." - exit 1 -fi - -# Set variables -DOCKER_DMG_URL="https://desktop.docker.com/mac/main/amd64/Docker.dmg" -DOCKER_DMG="Docker.dmg" -VOLUME_NAME="Docker" -INSTALL_PATH="/Volumes/$VOLUME_NAME/Docker.app/Contents/MacOS/install" - -# Download Docker -log_message "Downloading Docker for Intel Mac..." -if curl -L "$DOCKER_DMG_URL" -o "$DOCKER_DMG"; then - log_message "Docker download completed." -else - log_message "Error: Failed to download Docker." - exit 1 -fi - -# Mount the DMG -log_message "Mounting Docker DMG..." -if hdiutil attach "$DOCKER_DMG"; then - log_message "Docker DMG mounted successfully." -else - log_message "Error: Failed to mount Docker DMG." - exit 1 -fi - -# Run the installer -log_message "Running Docker installer..." -if "$INSTALL_PATH" --accept-license; then - log_message "Docker installation completed." -else - log_message "Error: Docker installation failed." - hdiutil detach "/Volumes/$VOLUME_NAME" - exit 1 -fi - -# Detach the DMG -log_message "Detaching Docker DMG..." -if hdiutil detach "/Volumes/$VOLUME_NAME"; then - log_message "Docker DMG detached successfully." -else - log_message "Warning: Failed to detach Docker DMG. This is not critical." -fi - -# Clean up -log_message "Cleaning up..." -rm -f "$DOCKER_DMG" - -log_message "Docker preinstall script completed successfully." -exit 0 diff --git a/build_assets/macOS/preinstall_ollama.sh b/build_assets/macOS/preinstall_ollama.sh deleted file mode 100644 index a74c1d58..00000000 --- a/build_assets/macOS/preinstall_ollama.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -curl -L https://github.com/ollama/ollama/releases/download/v0.3.6/ollama-darwin -o ollama - -chmod +x ollama - -sudo mv ollama /usr/local/bin/ - -nohup /usr/local/bin/ollama serve > /dev/null 2>&1 & -/usr/local/bin/ollama pull llama3.2:3b -/usr/local/bin/ollama pull nomic-embed-text diff --git a/build_assets/macOS/welcome.html b/build_assets/macOS/welcome.html deleted file mode 100644 index 70a9c49f..00000000 --- a/build_assets/macOS/welcome.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - Welcome to MORagents v0.2.2 Installer - - - -

Welcome to MORagents v0.2.2 Installer

-

- Thank you for choosing to install MORagents on your system. This installer - will guide you through the process of setting up MORagents and its - dependencies. -

-

The installer will perform the following steps:

- -

- Please note that during the installation process, you may be prompted to - enter your system password to authorize the installation of required - components. -

-

Click "Continue" to proceed with the installation.

- - diff --git a/build_assets/windows/Packaging_Instructions_Windows.md b/build_assets/windows/Packaging_Instructions_Windows.md deleted file mode 100644 index 51d3147e..00000000 --- a/build_assets/windows/Packaging_Instructions_Windows.md +++ /dev/null @@ -1,21 +0,0 @@ -## Distribution - -For Windows: -1. Export docker images after building. See [Windows Build](./README_WINDOWS_DEV_BUILD.md) -```shell - docker save -o moragents_dockers-nginx.tar moragents_dockers-nginx:latest - docker save -o moragents_dockers-agents.tar moragents_dockers-agents:latest - -``` - -2. -```shell - pyinstaller --name="MORagents" --icon=".\images\moragents.ico" main.py -``` - -Windows Inno Setup for Wizard: -1) Install Inno Setup -2) In the GUI, enter the text found in [wizard_windows.iss](../../wizard_windows.iss) -3) Click Build > Compile -4) Hit the Play/Run button on the top -5) Installer will run and create a Desktop icon to run the application. diff --git a/build_assets/windows/README_WINDOWS_DEV_BUILD.md b/build_assets/windows/README_WINDOWS_DEV_BUILD.md deleted file mode 100644 index 82d9762e..00000000 --- a/build_assets/windows/README_WINDOWS_DEV_BUILD.md +++ /dev/null @@ -1,46 +0,0 @@ -## For Developers - -This README will guide you on building a Docker container for agent execution as well as a desktop UI app. -You may instead simply download the [pre-built app](../../README.md) - -#### Windows -1. Ensure you have Python, Pip, [Docker Desktop](https://www.docker.com/products/docker-desktop/), and Git installed on your Windows machine. Note: please install python version 3.10.0+, older versions will produce outdated versions of tkinter when running the requirements install. - -2. Clone the repository using Command Prompt or PowerShell: -```shell -> git clone https://github.com/MorpheusAIs/moragents -> cd moragents -``` - -3. Build the Docker Image for Local Agent Execution: -```shell -> docker compose -f submodules/moragents_dockers/docker-compose.yml up -``` - -4. Install Deps for UI, Recommended to use virtualenv -```shell -> python -m venv .venv -> Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -> .\.venv\Scripts\Activate.ps1 -> pip install -r requirements.txt -``` - -6. Build App for Local Installation -```shell -pyinstaller --name="MORagents" --icon="moragents.ico" main.py -``` -There is a known and common issue where pyinstaller will trigger Windows Defender (see [here](https://stackoverflow.com/questions/54733909/windows-defender-alert-users-from-my-pyinstaller-exe) for more details). If there is a security warning, perform the following: - -- Open Windows Security: Go to Start > Settings > Update & Security > Windows Security > Virus & threat protection. -- Manage Settings: Under Virus & threat protection settings, select "Manage settings." -- Add or remove exclusions: Scroll down to "Exclusions" and select "Add or remove exclusions." -- Add an exclusion: Select "Add an exclusion" and choose the folder where your project is located. - -More details located [here](https://support.microsoft.com/en-us/windows/add-an-exclusion-to-windows-security-811816c0-4dfd-af4a-47e4-c301afe13b26) - -7. The compiled executable will be located in the dist folder. You can create a shortcut to the MORagents.exe file for easy access. -8. Double-click the MORagents.exe file to open the MORagents app on your Windows machine. Make sure Docker is running before opening MORagents. - ---- - -MacOS build instructions can be found [here](../macOS/README_MACOS_DEV_BUILD.md) diff --git a/config.py b/config.py deleted file mode 100644 index cfe9b723..00000000 --- a/config.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -import sys - -from utils.host_utils import get_os_and_arch - -os_name, arch = get_os_and_arch() - -if os_name == "macOS": - repo_root = os.path.dirname(__file__) -elif os_name == "Windows": - # Run as bundled executable if condition is met, else run as regular Python script - repo_root = sys._MEIPASS if getattr(sys, "frozen", False) else os.path.dirname(__file__) -elif os_name == "Linux": - repo_root = os.path.dirname(__file__) -else: - raise RuntimeError(f"Unsupported OS: {os_name}") - - -class AgentDockerConfig: - MACOS_APPLE_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:apple-0.2.2", - "lachsbagel/moragents_dockers-agents:apple-0.2.2", - ] - MACOS_INTEL_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:amd64-0.2.2", - "lachsbagel/moragents_dockers-agents:amd64-0.2.2", - ] - WINDOWS_IMAGE_NAMES = [ - "lachsbagel/moragents_dockers-nginx:amd64-0.2.2", - "lachsbagel/moragents_dockers-agents:amd64-0.2.2", - ] - LINUX_IMAGE_NAMES = [ # TODO, may need linux specific tagged images - "lachsbagel/moragents_dockers-nginx:amd64-0.2.2", - "lachsbagel/moragents_dockers-agents:amd64-0.2.2", - ] - - @staticmethod - def get_current_image_names(): - if os_name == "macOS" and arch == "ARM64": - return AgentDockerConfig.MACOS_APPLE_IMAGE_NAMES - elif os_name == "macOS" and arch == "x86_64": - return AgentDockerConfig.MACOS_INTEL_IMAGE_NAMES - elif os_name == "Windows": - return AgentDockerConfig.WINDOWS_IMAGE_NAMES - elif os_name == "Linux": - return AgentDockerConfig.LINUX_IMAGE_NAMES - else: - raise RuntimeError(f"Unsupported OS: {os_name}") - - -class AgentDockerConfigDeprecate: - OLD_IMAGE_NAMES = [ - "morpheus/price_fetcher_agent:latest", - "moragents_dockers-nginx:latest", - "moragents_dockers-agents:latest", - "lachsbagel/moragents_dockers-nginx:apple-0.0.9", - "lachsbagel/moragents_dockers-agents:apple-0.0.9", - "lachsbagel/moragents_dockers-nginx:amd64-0.0.9", - "lachsbagel/moragents_dockers-agents:amd64-0.0.9", - "lachsbagel/moragents_dockers-nginx:apple-0.1.0", - "lachsbagel/moragents_dockers-agents:apple-0.1.0", - "lachsbagel/moragents_dockers-nginx:amd64-0.1.0", - "lachsbagel/moragents_dockers-agents:amd64-0.1.0", - "lachsbagel/moragents_dockers-nginx:apple-0.2.0", - "lachsbagel/moragents_dockers-agents:apple-0.2.0", - "lachsbagel/moragents_dockers-nginx:amd64-0.2.0", - "lachsbagel/moragents_dockers-agents:amd64-0.2.0", - "lachsbagel/moragents_dockers-nginx:apple-0.2.1", - "lachsbagel/moragents_dockers-agents:apple-0.2.1", - "lachsbagel/moragents_dockers-nginx:amd64-0.2.1", - "lachsbagel/moragents_dockers-agents:amd64-0.2.1" - ] diff --git a/docs/agent-development-guide.md b/docs/agent-development-guide.md new file mode 100644 index 00000000..ba382188 --- /dev/null +++ b/docs/agent-development-guide.md @@ -0,0 +1,225 @@ +# Agent Development Guide + +This guide walks you through the process of developing new agents for the MySuperAgent platform. + +## Agent Architecture + +Agents in MySuperAgent follow a modular architecture: + +``` +your_agent_name/ +β”œβ”€β”€ __init__.py +β”œβ”€β”€ agent.py +β”œβ”€β”€ config.py +└── tools/ + └── tools.py +``` + +Each agent consists of: + +- **agent.py**: Main agent class that extends AgentCore +- **config.py**: Configuration including tool definitions +- **tools/tools.py**: Implementation of agent-specific tools + +## Creating a New Agent + +### 1. Use the Creation Script + +```bash +./create_new_agent.sh +``` + +When prompted, enter your agent name (must start with a letter and can only contain letters, numbers, underscores, and hyphens). + +### 2. Configure Agent in Main Config + +Add your agent to the main config file in `src/config.py`: + +```python +from services.agents.your_agent_name.config import Config as YourAgentConfig + +class Config: + # ... existing config ... + + AGENT_CONFIGS = { + # ... existing agents ... + "your_agent_name": YourAgentConfig, + } +``` + +### 3. Implement Agent Logic + +In `your_agent_name/agent.py`: + +```python +class YourAgentNameAgent(AgentCore): + """Agent for handling specific operations related to your agent's purpose.""" + + def __init__(self, config: Dict[str, Any], llm: Any, embeddings: Any): + super().__init__(config, llm, embeddings) + self.tools_provided = [ + # Add your tool functions here + ] + self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) + + async def _process_request(self, request: ChatRequest) -> AgentResponse: + try: + messages = [ + SystemMessage(content="Your custom system prompt here"), + HumanMessage(content=request.prompt.content), + ] + + result = self.tool_bound_llm.invoke(messages) + return await self._handle_llm_response(result) + except Exception as e: + self.logger.error(f"Error processing request: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) + + async def _execute_tool(self, func_name: str, args: Dict[str, Any]) -> AgentResponse: + """Execute the appropriate tool based on function name.""" + tool_map = { + # Map your tool names to their implementation functions + "your_tool_name": self._your_tool_implementation, + } + + if func_name not in tool_map: + return AgentResponse.error(error_message=f"Unknown tool: {func_name}") + + try: + result = await tool_map[func_name](**args) + return AgentResponse.success(content=result) + except Exception as e: + return AgentResponse.error(error_message=str(e)) +``` + +### 4. Implement Tools + +In `your_agent_name/tools/tools.py`: + +```python +import logging +from typing import Dict, Any + +logger = logging.getLogger(__name__) + +async def your_tool_implementation(arg1: str, arg2: int) -> str: + """ + Implementation of your custom tool. + + Args: + arg1: Description of first argument + arg2: Description of second argument + + Returns: + str: Result of the tool operation + """ + try: + # Your tool implementation here + result = f"Processed {arg1} with value {arg2}" + return result + except Exception as e: + logger.error(f"Error in tool implementation: {str(e)}", exc_info=True) + raise +``` + +### 5. Configure Tools + +In `your_agent_name/config.py`: + +```python +class Config: + tools = [ + { + "name": "your_tool_name", + "description": "Description of what your tool does", + "parameters": { + "type": "object", + "properties": { + "arg1": { + "type": "string", + "description": "Description of first argument" + }, + "arg2": { + "type": "integer", + "description": "Description of second argument" + } + }, + "required": ["arg1", "arg2"] + } + } + ] +``` + +## Best Practices + +### Error Handling + +- Use the `@handle_exceptions` decorator for common exceptions +- Return appropriate `AgentResponse` types: + - `AgentResponse.success(content="Success message")` + - `AgentResponse.error(error_message="Error description")` + - `AgentResponse.needs_info(message="Additional information needed")` + +### Logging + +Always use the logger for debugging and monitoring: + +```python +self.logger.info("Processing request") +self.logger.error("Error occurred", exc_info=True) +``` + +### Input Validation + +Validate inputs early to prevent errors down the pipeline: + +```python +if not some_required_value: + return AgentResponse.needs_info(message="Please provide required value") +``` + +### Modularity + +- Keep tool implementations focused on a single responsibility +- Separate complex logic into helper functions +- Use type hints consistently for better code maintainability + +## Testing Your Agent + +Create tests in the `tests/agents/your_agent_name/` directory: + +```python +import pytest +from services.agents.your_agent_name.agent import YourAgentNameAgent +from models.service.chat_models import ChatRequest + +@pytest.mark.asyncio +async def test_agent_basic_functionality(): + # Setup test agent + agent = YourAgentNameAgent(mock_config, mock_llm, mock_embeddings) + + # Create request + request = ChatRequest(prompt="Test prompt") + + # Process request + response = await agent.chat(request) + + # Assert on response + assert response.error_message is None + assert response.content is not None +``` + +Test different aspects: + +- Basic functionality +- Tool implementations +- Error handling +- Edge cases + +## Examples + +See existing agents in the repository for real-world examples: + +- DCA Agent +- Base Agent +- Tweet Sizzler diff --git a/docs/available-apis-guide.md b/docs/available-apis-guide.md new file mode 100644 index 00000000..cfbb9310 --- /dev/null +++ b/docs/available-apis-guide.md @@ -0,0 +1,312 @@ +# API Documentation + +This document outlines the REST API endpoints available in the MySuperAgent platform. + +## Base URL + +All API endpoints are available under: + +``` +https://api.mysuperagent.io +``` + +## Authentication + +Authentication is required for all API endpoints. Use one of the following methods: + +1. **API Key Authentication**: + + - Include the API key in the request header: + + ``` + X-API-Key: your_api_key_here + ``` + +2. **Bearer Token Authentication**: + - Include a bearer token in the Authorization header: + ``` + Authorization: Bearer your_token_here + ``` + +## Endpoints + +### Agent Interaction + +#### Chat with Agent + +``` +POST /api/chat +``` + +Request an agent to process a chat message. + +**Request Body**: + +```json +{ + "prompt": "Your request here", + "agent_id": "optional_specific_agent_id", + "conversation_id": "optional_ongoing_conversation_id", + "context": { + "optional_context_parameter1": "value1", + "optional_context_parameter2": "value2" + } +} +``` + +**Response**: + +```json +{ + "id": "response_id", + "content": "Agent response message", + "tool_calls": [ + { + "tool": "tool_name", + "arguments": { + "arg1": "value1" + }, + "result": "Tool execution result" + } + ], + "conversation_id": "conversation_id", + "agent_id": "agent_that_processed_request", + "error_message": null, + "needs_info": false, + "info_needed": null +} +``` + +#### Get Available Agents + +``` +GET /api/agents +``` + +Retrieve a list of all available agents. + +**Response**: + +```json +{ + "agents": [ + { + "id": "base_agent", + "name": "Base USDC Agent", + "description": "Send USDC on Base blockchain", + "capabilities": ["send_usdc", "check_balance"] + }, + { + "id": "dca_agent", + "name": "DCA Strategy Agent", + "description": "Dollar cost averaging cryptocurrency strategy", + "capabilities": ["create_strategy", "get_strategies", "execute_strategy"] + } + ] +} +``` + +### Document Analysis + +#### Upload Document + +``` +POST /api/documents/upload +``` + +Upload a document for analysis. + +**Request Body**: + +- Form data with `file` parameter +- Optional `metadata` parameter (JSON string) + +**Response**: + +```json +{ + "document_id": "doc_123456", + "filename": "example.pdf", + "size": 1245678, + "page_count": 23, + "status": "processing" +} +``` + +#### Query Document + +``` +POST /api/documents/{document_id}/query +``` + +Ask questions about a previously uploaded document. + +**Request Body**: + +```json +{ + "query": "What is the main topic of the document?", + "include_citations": true +} +``` + +**Response**: + +```json +{ + "answer": "The main topic of the document is artificial intelligence.", + "citations": [ + { + "page": 1, + "text": "This paper explores the latest developments in artificial intelligence." + }, + { + "page": 3, + "text": "Artificial intelligence has seen rapid growth in recent years." + } + ] +} +``` + +### Web3 Interactions + +#### Get Token Balances + +``` +GET /api/wallet/{wallet_address}/balances +``` + +Retrieve token balances for a specific wallet address. + +**Response**: + +```json +{ + "eth_balance": "1.234", + "tokens": [ + { + "symbol": "USDC", + "balance": "100.00", + "chain": "Base" + }, + { + "symbol": "USDT", + "balance": "50.00", + "chain": "Ethereum" + } + ] +} +``` + +#### Submit Transaction + +``` +POST /api/transactions/submit +``` + +Submit a blockchain transaction. + +**Request Body**: + +```json +{ + "chain": "Base", + "transaction_data": { + "to": "0x1234...", + "value": "10", + "token": "USDC", + "data": "0x..." + }, + "authorization": { + "type": "signature", + "value": "0x..." + } +} +``` + +**Response**: + +```json +{ + "transaction_id": "tx_789012", + "hash": "0xabcd...", + "status": "pending", + "estimated_confirmation_time": "30 seconds" +} +``` + +## Error Handling + +All API endpoints follow consistent error handling patterns. + +**Error Response Format**: + +```json +{ + "error": { + "code": "error_code", + "message": "Human readable error message", + "details": { + "additional_information": "More details about the error" + } + } +} +``` + +**Common Error Codes**: + +- `400`: Bad Request - Missing or invalid parameters +- `401`: Unauthorized - Invalid or missing authentication +- `403`: Forbidden - Valid authentication but insufficient permissions +- `404`: Not Found - Resource not found +- `429`: Too Many Requests - Rate limit exceeded +- `500`: Internal Server Error - Server-side issue + +## Rate Limits + +API endpoints are subject to rate limits: + +- 60 requests per minute per API key +- Higher rate limits available for premium tiers + +Headers indicating rate limits are included in responses: + +``` +X-RateLimit-Limit: 60 +X-RateLimit-Remaining: 58 +X-RateLimit-Reset: 1609459200 +``` + +## Webhooks + +Configure webhooks to receive real-time updates on specific events. + +**Event Types**: + +- `agent.response_complete` +- `document.processing_complete` +- `transaction.status_change` + +**Webhook Payload Example**: + +```json +{ + "event_type": "transaction.status_change", + "timestamp": "2023-03-15T14:30:45Z", + "data": { + "transaction_id": "tx_789012", + "new_status": "confirmed", + "hash": "0xabcd..." + } +} +``` + +## SDK Support + +Official SDKs are available for: + +- Python +- JavaScript/TypeScript +- Go + +Visit [https://github.com/mysuperagent/sdks](https://github.com/mysuperagent/sdks) for SDK documentation and examples. diff --git a/docs/deployment-guide.md b/docs/deployment-guide.md new file mode 100644 index 00000000..dd1973a9 --- /dev/null +++ b/docs/deployment-guide.md @@ -0,0 +1,155 @@ +# Deployment Guide + +This document outlines the process for deploying the MySuperAgent platform and individual agents to production environments. + +## Overview + +MySuperAgent uses AWS CloudFormation for infrastructure as code (IaC) deployment, with GitHub Actions for CI/CD automation. + +## Architecture + +The platform consists of: + +- **Frontend**: React application served via CloudFront +- **Backend**: Python FastAPI service running on EC2 +- **Infrastructure**: + - Application Load Balancer + - EC2 instances + - RDS database + - S3 storage + - ACM certificates for HTTPS + - Route53 DNS configuration + +## Prerequisites + +Before deployment, ensure you have: + +1. AWS account with appropriate permissions +2. GitHub repository access +3. Required secrets configured in GitHub repository + +## Environment Setup + +### AWS Resources + +The following AWS resources should be set up: + +1. **VPC**: `vpc-0e17a977e38b866c1` +2. **Subnets**: + - `subnet-01abb34d1b3a008ac` + - `subnet-0a852de834c421b24` +3. **Security Groups**: + - ALB Security Group: `sg-0ab173f93f4f06ac1` +4. **Route53 Hosted Zone**: `Z08521642MAYLL3XZZMIN` +5. **EC2 Key Pair**: `agents-key` + +## Deployment Process + +### Infrastructure Deployment + +To deploy or update the infrastructure, use the existing GitHub Actions workflow: + +1. Navigate to the GitHub repository +2. Go to "Actions" tab +3. Select the "Deploy MySuperAgent Infrastructure" workflow +4. Click "Run workflow" +5. Choose the environment (staging/production) +6. Click "Run workflow" to execute + +This workflow will deploy the CloudFormation stack defined in `infrastructure/mysuperagent-stack.yml`. + +### Agent Deployment + +To build and push agent docker images to ECR: + +1. Navigate to the GitHub repository +2. Go to "Actions" tab +3. Select the "Reusable Docker Production Build / Push (AWS ECR)" workflow +4. Click "Run workflow" +5. Provide the required inputs: + - **app_directory**: Relative path to the application + - **app_name**: Name of the application + - **aws_account_id**: AWS account ID + - **aws_region**: AWS region + - **ecr_repository**: ECR repository name + - **image_tag**: Version tag for the image +6. Click "Run workflow" to execute + +The workflow will build and push the Docker image to ECR with the specified tag. + +## Deployment Outputs + +After successful deployment, the following outputs will be available: + +- **Frontend URL**: `https://mysuperagent.io` +- **API URL**: `https://api.mysuperagent.io` +- **Load Balancer DNS**: Available in CloudFormation outputs + +## Monitoring and Troubleshooting + +### CloudWatch Monitoring + +Monitor your deployment using: + +- CloudWatch Alarms for EC2 instance health, API response time, and error rates +- CloudWatch Logs for application logs + +### Emergency SSH Access + +**Note**: SSH access should only be used for emergency troubleshooting when the application is not functioning properly and cannot be fixed through normal deployment methods. + +If you need to access an EC2 instance directly: + +```bash +ssh -i agents-key.pem ec2-user@ +``` + +The public IP is available in the CloudFormation outputs or EC2 console. + +#### Emergency Docker Container Management + +While SSH'd into the instance, you can: + +View running containers: + +```bash +docker ps +``` + +View container logs: + +```bash +docker logs +``` + +Restart containers: + +```bash +docker restart +``` + +Pull and run a specific image version: + +```bash +docker pull .dkr.ecr..amazonaws.com/: +docker stop +docker run -d --restart always -p 8888:5000 .dkr.ecr..amazonaws.com/: +``` + +**Important**: Any changes made via SSH should be temporary. Permanent changes should be made through the proper CI/CD pipeline. + +## Security Considerations + +1. **Security Groups**: Limit access to necessary ports only +2. **IAM Roles**: Use the principle of least privilege +3. **Secrets Management**: Store secrets in GitHub Secrets or AWS Secrets Manager +4. **HTTPS**: Ensure all traffic uses HTTPS +5. **Regular Updates**: Keep dependencies and OS up to date + +## Adding New Agents + +When adding a new agent to production: + +1. Develop and test the agent locally +2. Submit a PR with comprehensive tests +3. After approval and merge to main, manually trigger the appropriate GitHub Actions workflow to build and deploy the new agent diff --git a/docs/testing-framework-guide.md b/docs/testing-framework-guide.md new file mode 100644 index 00000000..1ea8c3ee --- /dev/null +++ b/docs/testing-framework-guide.md @@ -0,0 +1,278 @@ +# Testing Framework + +This document covers testing practices for MySuperAgent platform and agent development. + +## Overview + +MySuperAgent uses a comprehensive testing framework to ensure quality and reliability: + +1. **Unit Tests**: Individual functions and components +2. **Integration Tests**: Interactions between components +3. **End-to-End Tests**: Complete user flows +4. **Agent-Specific Tests**: Validation of agent functionality + +## Testing Tools + +- **pytest**: Primary testing framework +- **pytest-asyncio**: For testing async functions +- **pytest-mock**: For mocking dependencies +- **pytest-cov**: For code coverage reports + +## Directory Structure + +``` +tests/ +β”œβ”€β”€ conftest.py # Shared fixtures +β”œβ”€β”€ unit/ +β”‚ β”œβ”€β”€ agents/ +β”‚ β”‚ └── your_agent_name/ # Tests for specific agent +β”‚ β”œβ”€β”€ services/ +β”‚ β”œβ”€β”€ models/ +β”‚ └── tools/ +β”œβ”€β”€ integration/ +β”‚ β”œβ”€β”€ agents/ +β”‚ β”œβ”€β”€ services/ +β”‚ └── api/ +└── e2e/ + └── workflows/ +``` + +## Setting Up Tests + +### Prerequisites + +Install test dependencies: + +```bash +poetry install --with test +``` + +### Writing Tests for Agents + +Create a file structure in `tests/unit/agents/your_agent_name/` that mirrors your agent's structure. + +#### Basic Agent Test + +```python +import pytest +from services.agents.your_agent_name.agent import YourAgentNameAgent +from models.service.chat_models import ChatRequest, AgentResponse + +@pytest.fixture +def mock_llm(): + # Mock the LLM service + mock = MagicMock() + mock.invoke.return_value = {"content": "Mocked response"} + mock.bind_tools.return_value = mock + return mock + +@pytest.fixture +def mock_embeddings(): + # Mock the embeddings service + return MagicMock() + +@pytest.fixture +def agent_config(): + # Return a test configuration + return { + "tools": [ + { + "name": "test_tool", + "description": "Test tool for unit tests", + "parameters": { + "type": "object", + "properties": { + "test_param": { + "type": "string", + "description": "Test parameter" + } + }, + "required": ["test_param"] + } + } + ] + } + +@pytest.fixture +def agent(mock_llm, mock_embeddings, agent_config): + return YourAgentNameAgent(agent_config, mock_llm, mock_embeddings) + +@pytest.mark.asyncio +async def test_agent_initialization(agent): + """Test that agent initializes correctly.""" + assert agent is not None + assert len(agent.tools_provided) > 0 + assert agent.tool_bound_llm is not None + +@pytest.mark.asyncio +async def test_process_request(agent, mock_llm): + """Test that agent processes requests correctly.""" + # Arrange + request = ChatRequest(prompt="Test request") + + # Act + response = await agent.chat(request) + + # Assert + assert isinstance(response, AgentResponse) + assert mock_llm.invoke.called + assert response.content is not None + assert response.error_message is None +``` + +#### Testing Tool Execution + +```python +@pytest.mark.asyncio +async def test_tool_execution(agent, mocker): + """Test tool execution function.""" + # Arrange + mocker.patch.object( + agent, + '_your_tool_implementation', + return_value="Tool executed successfully" + ) + + # Act + response = await agent._execute_tool("your_tool_name", {"arg1": "test", "arg2": 123}) + + # Assert + assert response.content == "Tool executed successfully" + assert response.error_message is None + agent._your_tool_implementation.assert_called_once_with(arg1="test", arg2=123) +``` + +### Testing Tools + +Test each tool in isolation: + +```python +from services.agents.your_agent_name.tools.tools import your_tool_implementation + +@pytest.mark.asyncio +async def test_your_tool_implementation(): + """Test the actual tool implementation.""" + # Act + result = await your_tool_implementation(arg1="test", arg2=123) + + # Assert + assert "Processed test with value 123" in result +``` + +## Mocking External Services + +For tools that interact with external services, use mocking: + +```python +@pytest.mark.asyncio +async def test_external_api_tool(mocker): + """Test a tool that calls an external API.""" + # Arrange + mock_response = MagicMock() + mock_response.json.return_value = {"data": "test_result"} + mock_response.status_code = 200 + + mock_session = MagicMock() + mock_session.get.return_value.__aenter__.return_value = mock_response + + mocker.patch("aiohttp.ClientSession", return_value=mock_session) + + # Act + from services.agents.your_agent_name.tools.tools import api_tool + result = await api_tool(query="test") + + # Assert + assert "test_result" in result + mock_session.get.assert_called_once() +``` + +## Running Tests + +### Run All Tests + +```bash +pytest +``` + +### Run Tests for a Specific Agent + +```bash +pytest tests/unit/agents/your_agent_name/ +``` + +### Run with Coverage + +```bash +pytest --cov=services.agents.your_agent_name +``` + +Generate HTML coverage report: + +```bash +pytest --cov=services.agents.your_agent_name --cov-report=html +``` + +## Integration Tests + +Integration tests verify that your agent works with the platform: + +```python +@pytest.mark.asyncio +async def test_agent_registration(client): + """Test that agent is properly registered in the platform.""" + # Arrange + from services.agent_registry import AgentRegistry + + # Act + response = await client.get("/api/agents") + data = response.json() + + # Assert + assert response.status_code == 200 + assert any(agent["id"] == "your_agent_name" for agent in data["agents"]) + +@pytest.mark.asyncio +async def test_agent_chat_endpoint(client): + """Test that agent responds through the chat API.""" + # Arrange + request_data = { + "prompt": "Test request for your agent", + "agent_id": "your_agent_name" + } + + # Act + response = await client.post("/api/chat", json=request_data) + data = response.json() + + # Assert + assert response.status_code == 200 + assert "content" in data + assert data["agent_id"] == "your_agent_name" +``` + +## Continuous Integration + +Tests run automatically in CI pipelines on: + +- Pull request creation +- Merge to main branch + +The CI pipeline: + +1. Sets up the testing environment +2. Installs dependencies +3. Runs linters and style checks +4. Executes unit tests +5. Runs integration tests +6. Generates coverage reports + +## Best Practices + +1. **Keep Tests Focused**: Each test should verify a single behavior +2. **Use Descriptive Names**: Test names should describe what they're testing +3. **Maintain Test Independence**: No test should depend on another +4. **Use Fixtures**: Share setup code through pytest fixtures +5. **Mock External Dependencies**: Don't rely on external services in unit tests +6. **Test Error Handling**: Verify that your code handles errors gracefully +7. **Maintain High Coverage**: Aim for >85% test coverage +8. **Write Both Positive and Negative Tests**: Test both successes and failures diff --git a/images/MORagents-UI.png b/images/MORagents-UI.png deleted file mode 100644 index 18deb323..00000000 Binary files a/images/MORagents-UI.png and /dev/null differ diff --git a/images/dca-strategy-agent.png b/images/dca-strategy-agent.png deleted file mode 100644 index 603622c9..00000000 Binary files a/images/dca-strategy-agent.png and /dev/null differ diff --git a/images/dca-workflows-view.png b/images/dca-workflows-view.png deleted file mode 100644 index d679c74a..00000000 Binary files a/images/dca-workflows-view.png and /dev/null differ diff --git a/images/gasless-usdc-base-agent.png b/images/gasless-usdc-base-agent.png deleted file mode 100644 index 9b9262c7..00000000 Binary files a/images/gasless-usdc-base-agent.png and /dev/null differ diff --git a/images/image-generator.png b/images/image-generator.png deleted file mode 100644 index 848f07fc..00000000 Binary files a/images/image-generator.png and /dev/null differ diff --git a/images/mor_rewards.png b/images/mor_rewards.png deleted file mode 100644 index 2e249d6e..00000000 Binary files a/images/mor_rewards.png and /dev/null differ diff --git a/images/moragents_chatpdf.png b/images/moragents_chatpdf.png deleted file mode 100644 index 1cb159ab..00000000 Binary files a/images/moragents_chatpdf.png and /dev/null differ diff --git a/images/price-fetcher-realtime-news.png b/images/price-fetcher-realtime-news.png deleted file mode 100644 index f22e895b..00000000 Binary files a/images/price-fetcher-realtime-news.png and /dev/null differ diff --git a/images/real-time-info.png b/images/real-time-info.png deleted file mode 100644 index 2857650a..00000000 Binary files a/images/real-time-info.png and /dev/null differ diff --git a/images/tweet_sizzler.png b/images/tweet_sizzler.png deleted file mode 100644 index 862c7b4e..00000000 Binary files a/images/tweet_sizzler.png and /dev/null differ diff --git a/images/wallet_integration.png b/images/wallet_integration.png deleted file mode 100644 index 62549673..00000000 Binary files a/images/wallet_integration.png and /dev/null differ diff --git a/main.py b/main.py deleted file mode 100644 index 148f39ee..00000000 --- a/main.py +++ /dev/null @@ -1,34 +0,0 @@ -import logging -import time -import webbrowser - -from runtime_setup_linux import main as linux_setup -from runtime_setup_macos import main as macos_setup -from runtime_setup_windows import main as windows_setup -from utils.host_utils import get_os_and_arch -from utils.logger_config import setup_logger - -# Configure logging -logger = setup_logger(__name__) - - -if __name__ == "__main__": - - try: - os_name, arch = get_os_and_arch() - - if os_name == "macOS": - macos_setup() - elif os_name == "Windows": - windows_setup() - elif os_name == "Linux": - linux_setup() - - except Exception as e: - logging.critical(f"Error during Docker setup: {str(e)}") - raise - - time.sleep(7) - - url = "http://localhost:3333/" - webbrowser.open(url) diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 8d47df51..00000000 --- a/pyproject.toml +++ /dev/null @@ -1,26 +0,0 @@ -[tool.black] -line-length = 100 -include = '\.pyi?$' -exclude = ''' -/( - \.git - | \.hg - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - | \.eggs - | venv - | .venv -)/ -''' - -[tool.isort] -profile = "black" -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -line_length = 100 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index d176fb78..00000000 --- a/requirements.txt +++ /dev/null @@ -1,20 +0,0 @@ -docker -tk -customtkinter -requests -setuptools -pyinstaller -cdp-sdk -safety -bandit - -# Linting dependencies -pre-commit==3.5.0 -flake8==7.0.0 -black==24.2.0 -isort==5.13.2 -types-requests==2.31.0.20240311 -types-setuptools==69.2.0.20240316 -flake8-bugbear==24.2.6 -flake8-comprehensions==3.14.0 -flake8-docstrings==1.7.0 diff --git a/runtime_setup_linux.py b/runtime_setup_linux.py deleted file mode 100644 index e1484f6d..00000000 --- a/runtime_setup_linux.py +++ /dev/null @@ -1,218 +0,0 @@ -import os -import shutil -import subprocess - -from config import AgentDockerConfig, AgentDockerConfigDeprecate -from utils.logger_config import setup_logger - -logger = setup_logger(__name__) - - -def get_docker_path(): - docker_paths = [ - "/usr/bin/docker", # Common Linux path - "/usr/local/bin/docker", # Alternative Linux path - shutil.which("docker"), - ] - for docker_path in docker_paths: - if docker_path and os.path.exists(docker_path): - return docker_path - - logger.error("Docker executable not found in PATH.") - return None - - -def check_docker_installed(docker_path): - try: - subprocess.run( - [docker_path, "--version"], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - logger.info(f"Docker was found at {docker_path}") - return True - except (subprocess.CalledProcessError, TypeError) as e: - logger.error(f"Error checking Docker installation: {str(e)}") - return False - - -def delete_docker_image(docker_path, image_name): - try: - # List all images - list_command = [docker_path, "images", "--format", "{{.Repository}}:{{.Tag}}"] - output = subprocess.check_output(list_command, universal_newlines=True) - images = output.strip().split("\n") - - # Find the image with the specified name - if image_name in images: - # Remove the image - remove_command = [docker_path, "rmi", "-f", image_name] - subprocess.run(remove_command, check=True) - logger.info(f"Image '{image_name}' deleted successfully.") - else: - pass - - except subprocess.CalledProcessError as e: - logger.warning(f"Error deleting image: {e}") - - -def list_containers_for_image(docker_path, image_name): - try: - output = subprocess.check_output( - [docker_path, "ps", "-a", "--filter", f"ancestor={image_name}", "--format", "{{.ID}}"] - ) - containers = output.decode().strip().split("\n") - return [container for container in containers if container] - except subprocess.CalledProcessError as e: - logger.error(f"Failed to list containers for image '{image_name}': {e}") - return [] - - -def remove_container(docker_path, container): - try: - subprocess.run( - [docker_path, "rm", "-f", container], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - except subprocess.CalledProcessError as e: - logger.error(f"Failed to remove container '{container}': {e}") - - -def docker_image_present_on_host(docker_path, image_name): - try: - subprocess.run( - [docker_path, "inspect", image_name], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - return True - except (subprocess.CalledProcessError, TypeError) as e: - return False - - -def remove_containers_for_image(docker_path, image_name): - containers = list_containers_for_image(docker_path, image_name) - for container in containers: - remove_container(docker_path, container) - logger.info(f"Removed container '{container}' for image '{image_name}'") - - -def remove_containers_by_name(docker_path, container_name): - try: - list_command = [docker_path, "ps", "-a", "--format", "{{.Names}}"] - output = subprocess.check_output(list_command, universal_newlines=True) - containers = output.strip().split("\n") - - if container_name in containers: - remove_command = [docker_path, "rm", "-f", container_name] - subprocess.run(remove_command, check=True) - logger.info(f"Removed container '{container_name}'") - else: - logger.info(f"Container '{container_name}' not found") - except subprocess.CalledProcessError as e: - logger.error(f"Error removing container '{container_name}': {str(e)}") - - -def migration_remove_old_images(docker_path): - for image_name in AgentDockerConfigDeprecate.OLD_IMAGE_NAMES: - if docker_image_present_on_host(docker_path, image_name): - delete_docker_image(docker_path, image_name) - logger.info(f"Deleted image '{image_name} from previous release") - - -def pull_docker_images(docker_path): - for image in AgentDockerConfig.get_current_image_names(): - try: - subprocess.run([docker_path, "pull", image], check=True) - logger.info(f"Successfully pulled image: {image}") - except subprocess.CalledProcessError as e: - logger.error(f"Failed to pull image {image}: {e}") - raise - - -def start_ollama_server(): - ollama_path = "/usr/local/bin/ollama" # This path might need to be adjusted for Linux - - try: - # Start Ollama server - logger.info("Starting Ollama server...") - subprocess.Popen( - [ollama_path, "serve"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL - ) - logger.info("Ollama server started successfully.") - except Exception as e: - logger.info("Failed to start Ollama server.") - logger.error(f"Failed to start Ollama server: {e}") - - -def docker_setup(): - docker_path = get_docker_path() - logger.info(f"Docker path: {docker_path}") - - if not check_docker_installed(docker_path): - logger.critical("Docker is not installed.") - raise RuntimeError("Docker is not installed.") - - # Remove old images and containers - logger.info("Checking whether old images need removal.") - migration_remove_old_images(docker_path) - - for image_name in AgentDockerConfig.get_current_image_names(): - remove_containers_for_image(docker_path, image_name) - - remove_containers_by_name(docker_path, "agents") - remove_containers_by_name(docker_path, "nginx") - - # Pull the latest images - pull_docker_images(docker_path) - - # Spin up Agent container - subprocess.run( - [ - docker_path, - "run", - "-d", - "--name", - "agents", - "-p", - "8080:5000", - "--restart", - "always", - "-v", - "/var/lib/agents:/var/lib/agents", - "-v", - "/app/src:/app/src", # Adjusted volume paths for Linux - AgentDockerConfig.get_current_image_names()[1], # agents image - ], - check=True, - ) - - # Spin up Nginx container - subprocess.run( - [ - docker_path, - "run", - "-d", - "--name", - "nginx", - "-p", - "3333:80", - AgentDockerConfig.get_current_image_names()[0], # nginx image - ], - check=True, - ) - - -def main(): - # main() called every time the app is opened (from main.py). Put all app open code here. - logger.info("Starting app...") - start_ollama_server() - docker_setup() - - -if __name__ == "__main__": - main() diff --git a/runtime_setup_macos.py b/runtime_setup_macos.py deleted file mode 100644 index 26cfc1f9..00000000 --- a/runtime_setup_macos.py +++ /dev/null @@ -1,217 +0,0 @@ -import os -import shutil -import subprocess - -from config import AgentDockerConfig, AgentDockerConfigDeprecate -from utils.logger_config import setup_logger - -logger = setup_logger(__name__) - - -def get_docker_path(): - docker_paths = [ - "/Applications/Docker.app/Contents/Resources/bin/docker", - shutil.which("docker"), - ] - for docker_path in docker_paths: - if os.path.exists(docker_path): - return docker_path - - logger.error("Docker executable not found in PATH.") - return None - - -def check_docker_installed(docker_path): - try: - subprocess.run( - [docker_path, "--version"], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - logger.info(f"Docker was found at {docker_path}") - return True - except (subprocess.CalledProcessError, TypeError) as e: - logger.error(f"Error checking Docker installation: {str(e)}") - return False - - -def delete_docker_image(docker_path, image_name): - try: - # List all images - list_command = [docker_path, "images", "--format", "{{.Repository}}:{{.Tag}}"] - output = subprocess.check_output(list_command, universal_newlines=True) - images = output.strip().split("\n") - - # Find the image with the specified name - if image_name in images: - # Remove the image - remove_command = [docker_path, "rmi", "-f", image_name] - subprocess.run(remove_command, check=True) - logger.info(f"Image '{image_name}' deleted successfully.") - else: - pass - - except subprocess.CalledProcessError as e: - logger.warning(f"Error deleting image: {e}") - - -def list_containers_for_image(docker_path, image_name): - try: - output = subprocess.check_output( - [docker_path, "ps", "-a", "--filter", f"ancestor={image_name}", "--format", "{{.ID}}"] - ) - containers = output.decode().strip().split("\n") - return [container for container in containers if container] - except subprocess.CalledProcessError as e: - logger.error(f"Failed to list containers for image '{image_name}': {e}") - return [] - - -def remove_container(docker_path, container): - try: - subprocess.run( - [docker_path, "rm", "-f", container], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - except subprocess.CalledProcessError as e: - logger.error(f"Failed to remove container '{container}': {e}") - - -def docker_image_present_on_host(docker_path, image_name): - try: - subprocess.run( - [docker_path, "inspect", image_name], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - return True - except (subprocess.CalledProcessError, TypeError) as e: - return False - - -def remove_containers_for_image(docker_path, image_name): - containers = list_containers_for_image(docker_path, image_name) - for container in containers: - remove_container(docker_path, container) - logger.info(f"Removed container '{container}' for image '{image_name}'") - - -def remove_containers_by_name(docker_path, container_name): - try: - list_command = [docker_path, "ps", "-a", "--format", "{{.Names}}"] - output = subprocess.check_output(list_command, universal_newlines=True) - containers = output.strip().split("\n") - - if container_name in containers: - remove_command = [docker_path, "rm", "-f", container_name] - subprocess.run(remove_command, check=True) - logger.info(f"Removed container '{container_name}'") - else: - logger.info(f"Container '{container_name}' not found") - except subprocess.CalledProcessError as e: - logger.error(f"Error removing container '{container_name}': {str(e)}") - - -def migration_remove_old_images(docker_path): - for image_name in AgentDockerConfigDeprecate.OLD_IMAGE_NAMES: - if docker_image_present_on_host(docker_path, image_name): - delete_docker_image(docker_path, image_name) - logger.info(f"Deleted image '{image_name} from previous release") - - -def pull_docker_images(docker_path): - for image in AgentDockerConfig.get_current_image_names(): - try: - subprocess.run([docker_path, "pull", image], check=True) - logger.info(f"Successfully pulled image: {image}") - except subprocess.CalledProcessError as e: - logger.error(f"Failed to pull image {image}: {e}") - raise - - -def start_ollama_server(): - ollama_path = "/usr/local/bin/ollama" - - try: - # Start Ollama server - logger.info("Starting Ollama server...") - subprocess.Popen( - [ollama_path, "serve"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL - ) - logger.info("Ollama server started successfully.") - except Exception as e: - logger.info("Failed to start Ollama server.") - logger.error(f"Failed to start Ollama server: {e}") - - -def docker_setup(): - docker_path = get_docker_path() - logger.info(f"Docker path: {docker_path}") - - if not check_docker_installed(docker_path): - logger.critical("Docker is not installed.") - raise RuntimeError("Docker is not installed.") - - # Remove old images and containers - logger.info("Checking whether old images need removal.") - migration_remove_old_images(docker_path) - - for image_name in AgentDockerConfig.get_current_image_names(): - remove_containers_for_image(docker_path, image_name) - - remove_containers_by_name(docker_path, "agents") - remove_containers_by_name(docker_path, "nginx") - - # Pull the latest images - pull_docker_images(docker_path) - - # Spin up Agent container - subprocess.run( - [ - docker_path, - "run", - "-d", - "--name", - "agents", - "-p", - "8080:5000", - "--restart", - "always", - "-v", - "/var/lib/agents", - "-v", - "/app/src", - AgentDockerConfig.get_current_image_names()[1], # agents image - ], - check=True, - ) - - # Spin up Nginx container - subprocess.run( - [ - docker_path, - "run", - "-d", - "--name", - "nginx", - "-p", - "3333:80", - AgentDockerConfig.get_current_image_names()[0], # nginx image - ], - check=True, - ) - - -def main(): - # main() called every time the app is opened (from main.py). Put all app open code here. - logger.info("Starting app...") - start_ollama_server() - docker_setup() - - -if __name__ == "__main__": - main() diff --git a/runtime_setup_windows.py b/runtime_setup_windows.py deleted file mode 100644 index 43024fc9..00000000 --- a/runtime_setup_windows.py +++ /dev/null @@ -1,210 +0,0 @@ -import subprocess -import time - -from config import AgentDockerConfig, AgentDockerConfigDeprecate -from utils.logger_config import setup_logger - -logger = setup_logger(__name__) - -docker_path = "docker" - - -def check_docker_installed(): - try: - subprocess.run( - [docker_path, "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - return True - except (subprocess.CalledProcessError, FileNotFoundError): - return False - - -def start_docker(): - try: - subprocess.run(["C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"]) - except FileNotFoundError: - logger.error("Docker Desktop not found. Please install Docker Desktop.") - raise - - while True: - try: - output = subprocess.check_output(["docker", "info"], stderr=subprocess.PIPE) - if "Server Version" in output.decode(): - print("Docker engine is running.") - break - except (subprocess.CalledProcessError, FileNotFoundError): - pass - logger.info("Waiting for Docker engine to start...") - time.sleep(2) - - -def delete_docker_image(image_name): - try: - list_command = [docker_path, "images", "--format", "{{.Repository}}:{{.Tag}}"] - output = subprocess.check_output(list_command, universal_newlines=True) - images = output.strip().split("\n") - - if image_name in images: - remove_command = [docker_path, "rmi", "-f", image_name] - subprocess.run(remove_command, check=True) - logger.info(f"Image '{image_name}' deleted successfully.") - except subprocess.CalledProcessError as e: - logger.warning(f"Error deleting image: {e}") - - -def list_containers_for_image(image_name): - try: - output = subprocess.check_output( - [docker_path, "ps", "-a", "--filter", f"ancestor={image_name}", "--format", "{{.ID}}"] - ) - containers = output.decode().strip().split("\n") - return [container for container in containers if container] - except subprocess.CalledProcessError as e: - logger.error(f"Failed to list containers for image '{image_name}': {e}") - return [] - - -def remove_container(container): - try: - subprocess.run( - [docker_path, "rm", "-f", container], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - except subprocess.CalledProcessError as e: - logger.error(f"Failed to remove container '{container}': {e}") - - -def docker_image_present_on_host(image_name): - try: - subprocess.run( - [docker_path, "inspect", image_name], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - return True - except (subprocess.CalledProcessError, TypeError): - return False - - -def remove_containers_for_image(image_name): - containers = list_containers_for_image(image_name) - for container in containers: - remove_container(container) - logger.info(f"Removed container '{container}' for image '{image_name}'") - - -def remove_containers_by_name(container_name): - try: - list_command = [docker_path, "ps", "-a", "--format", "{{.Names}}"] - output = subprocess.check_output(list_command, universal_newlines=True) - containers = output.strip().split("\n") - - if container_name in containers: - remove_command = [docker_path, "rm", "-f", container_name] - subprocess.run(remove_command, check=True) - logger.info(f"Removed container '{container_name}'") - else: - logger.info(f"Not removing container '{container_name}' as it was not found") - except subprocess.CalledProcessError as e: - logger.error(f"Error removing container '{container_name}': {str(e)}") - - -def migration_remove_old_images(): - for image_name in AgentDockerConfigDeprecate.OLD_IMAGE_NAMES: - if docker_image_present_on_host(image_name): - delete_docker_image(image_name) - logger.info(f"Deleted image '{image_name}' from previous release") - - -def pull_docker_images(): - for image_name in AgentDockerConfig.get_current_image_names(): - try: - subprocess.run([docker_path, "pull", image_name], check=True) - logger.info(f"Successfully pulled image: {image_name}") - except subprocess.CalledProcessError as e: - logger.error(f"Failed to pull image {image_name}: {e}") - raise - - -def start_ollama_server(): - ollama_path = "ollama" - - try: - print(f"Attempting to start Ollama server using: {ollama_path}") - subprocess.Popen( - [ollama_path, "serve"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - creationflags=subprocess.CREATE_NO_WINDOW, - ) - print("Ollama server started successfully.") - except Exception as e: - print(f"Failed to start Ollama server: {e}") - - -def docker_setup(): - if not check_docker_installed(): - print("Docker is not installed. Please install Docker Desktop.") - return - - start_docker() - - logger.info("Checking whether old images need removal.") - migration_remove_old_images() - - pull_docker_images() - - for image_name in AgentDockerConfig.get_current_image_names(): - remove_containers_for_image(image_name) - - remove_containers_by_name("agents") - remove_containers_by_name("nginx") - - # Spin up Agent container - subprocess.run( - [ - docker_path, - "run", - "-d", - "--name", - "agents", - "-p", - "8080:5000", - "--restart", - "always", - "-v", - "/var/lib/agents", - "-v", - "/app/src", - AgentDockerConfig.get_current_image_names()[1], # agents image - ], - check=True, - ) - - # Spin up Nginx container - subprocess.run( - [ - docker_path, - "run", - "-d", - "--name", - "nginx", - "-p", - "3333:80", - AgentDockerConfig.get_current_image_names()[0], # nginx image - ], - check=True, - ) - - -def main(): - # main() called every time the app is opened (from main.py). Put all app open code here. - start_ollama_server() - docker_setup() - - -if __name__ == "__main__": - docker_setup() diff --git a/static/demo.gif b/static/demo.gif new file mode 100644 index 00000000..e2c3590b Binary files /dev/null and b/static/demo.gif differ diff --git a/images/moragents.icns b/static/moragents.icns old mode 100644 new mode 100755 similarity index 100% rename from images/moragents.icns rename to static/moragents.icns diff --git a/images/moragents.ico b/static/moragents.ico old mode 100644 new mode 100755 similarity index 100% rename from images/moragents.ico rename to static/moragents.ico diff --git a/images/moragents.png b/static/moragents.png old mode 100644 new mode 100755 similarity index 100% rename from images/moragents.png rename to static/moragents.png diff --git a/images/morpheus-ecosystem@3x_green.png b/static/morpheus-ecosystem@3x_green.png old mode 100644 new mode 100755 similarity index 100% rename from images/morpheus-ecosystem@3x_green.png rename to static/morpheus-ecosystem@3x_green.png diff --git a/images/tweet_sizzler_instructions/dashboard_permissions.png b/static/tweet_sizzler_instructions/dashboard_permissions.png old mode 100644 new mode 100755 similarity index 100% rename from images/tweet_sizzler_instructions/dashboard_permissions.png rename to static/tweet_sizzler_instructions/dashboard_permissions.png diff --git a/images/tweet_sizzler_instructions/user_authentication_settings.png b/static/tweet_sizzler_instructions/user_authentication_settings.png old mode 100644 new mode 100755 similarity index 100% rename from images/tweet_sizzler_instructions/user_authentication_settings.png rename to static/tweet_sizzler_instructions/user_authentication_settings.png diff --git a/submodules/agents/.dockerignore b/submodules/agents/.dockerignore new file mode 100644 index 00000000..4fc95ea2 --- /dev/null +++ b/submodules/agents/.dockerignore @@ -0,0 +1,3 @@ +.venv +__pycache__ +*.pyc diff --git a/submodules/agents/.vscode/settings.json b/submodules/agents/.vscode/settings.json new file mode 100644 index 00000000..dd61265e --- /dev/null +++ b/submodules/agents/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.analysis.extraPaths": ["./src"], + "python.autoComplete.extraPaths": ["./src"] +} diff --git a/submodules/agents/README.md b/submodules/agents/README.md new file mode 100755 index 00000000..fedd2395 --- /dev/null +++ b/submodules/agents/README.md @@ -0,0 +1,137 @@ +# MySuperAgent:agents + +## Overview + +MySuperAgent:agents is the server that powers the MySuperAgent.io app. It's a FastAPI-based AI chat application featuring intelligent responses from various language models and embeddings. The system includes file uploading and a delegator system to manage multiple specialized agents. The application can run locally and supports containerization with Docker. + +## Pre-requisites + +- [Download Ollama](https://ollama.com/) for your operating system +- After installation, pull these two required models: + +```sh +ollama pull llama3.2:3b +ollama pull nomic-embed-text +``` + +## Getting Started + +### Building from Source + +1. Clone the repository: + +```sh +git clone https://github.com/yourusername/mysuperagent.git +cd mysuperagent +``` + +2. Install Poetry (if not already installed): + +```sh +curl -sSL https://install.python-poetry.org | python3 - +``` + +3. Install dependencies: + +```sh +poetry install +``` + +4. Run the application: + +```sh +cd src +poetry run uvicorn app:app --host 0.0.0.0 --port 5000 --reload +``` + +### Using Docker Compose + +1. Ensure you're in the root project folder: + +```sh +# Make sure you're in the main project directory, not submodules +``` + +2. Build images and launch containers: + + For Intel/AMD/x86_64: + + ```sh + docker-compose up + ``` + + For Apple Silicon (M1, M2, etc.): + + ```sh + docker compose -f build/docker-compose-apple.yml up + ``` + +3. Open in the browser: `http://localhost:3333/` + +The Docker build will download the model. The first time an agent is called, the model will be loaded into memory and shared between all agents. + +## API Endpoints + +The application provides several key API endpoints: + +### Chat API + +- **POST /api/v1/chat**: Handles chat requests and delegates to appropriate agent + + - Accepts a `ChatRequest` object + - Returns the AI-generated response + +- **POST /api/v1/generate-title**: Generates a title for a conversation based on chat history + - Accepts a `GenerateConversationTitleRequest` object + - Returns a JSON response with the generated title + +### Agent Management API + +- **GET /agents/available**: Gets the list of currently available agents + + - Returns selected and available agents + +- **POST /agents/selected**: Sets which agents should be selected + + - Accepts a JSON object with an "agents" array + - Returns success status and the selected agents + +- **GET /agents/commands**: Gets the list of available agent commands + - Returns commands, descriptions, and human-readable names for each agent + +## Creating a New Agent + +To create a new agent, follow the instructions in the README.md located in the `src/services/agents` directory. + +## Code Quality & Linting + +We use several tools to maintain code quality: + +### Python + +1. **black** - Code formatting +2. **isort** - Import sorting +3. **flake8** - Style guide enforcement + +### Frontend + +1. **eslint** - JavaScript/TypeScript linting + +### Running Linters + +Linters will automatically run on staged files when you commit changes. You can also run them manually: + +```bash +# In root directory +pip install -r requirements.txt +pre-commit install + +# Run all pre-commit hooks on staged files +pre-commit run + +# Run on specific files +pre-commit run --files path/to/file.py + +# Run all hooks on all files +pre-commit run --all-files +``` diff --git a/build_assets/__init__.py b/submodules/agents/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from build_assets/__init__.py rename to submodules/agents/__init__.py diff --git a/submodules/agents/alembic.ini b/submodules/agents/alembic.ini new file mode 100644 index 00000000..472914b0 --- /dev/null +++ b/submodules/agents/alembic.ini @@ -0,0 +1,116 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = postgresql+psycopg2://neo:trinity@postgres:5678/morpheus_db + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/submodules/agents/alembic/README b/submodules/agents/alembic/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/submodules/agents/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/submodules/agents/alembic/env.py b/submodules/agents/alembic/env.py new file mode 100644 index 00000000..c84b5b96 --- /dev/null +++ b/submodules/agents/alembic/env.py @@ -0,0 +1,84 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +import sys +import os + +sys.path.append("./src") + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +from models.models import Base + +target_metadata = [Base.metadata] + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. +# DB_URL = os.getenv("db_url") +DB_URL = "postgresql+psycopg2://neo:trinity@localhost:5678/morpheus_db" +if DB_URL: + config.set_main_option("sqlalchemy.url", "postgresql+psycopg2://neo:trinity@localhost:5678/morpheus_db") # type: ignore + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/submodules/agents/alembic/script.py.mako b/submodules/agents/alembic/script.py.mako new file mode 100644 index 00000000..fbc4b07d --- /dev/null +++ b/submodules/agents/alembic/script.py.mako @@ -0,0 +1,26 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/submodules/agents/alembic/versions/17c8d2dc869c_create_initial_tables.py b/submodules/agents/alembic/versions/17c8d2dc869c_create_initial_tables.py new file mode 100644 index 00000000..be6fed5e --- /dev/null +++ b/submodules/agents/alembic/versions/17c8d2dc869c_create_initial_tables.py @@ -0,0 +1,78 @@ +"""create initial tables + +Revision ID: 17c8d2dc869c +Revises: +Create Date: 2025-02-12 00:44:20.305582 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '17c8d2dc869c' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('users', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('wallet_address', sa.String(length=255), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('wallet_address') + ) + op.create_table('conversations', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('conversation_id', sa.String(length=255), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('has_uploaded_file', sa.Boolean(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('conversation_id', 'user_id') + ) + op.create_table('user_settings', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('settings_key', sa.String(length=255), nullable=False), + sa.Column('settings_value', sa.JSON(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id'), + unique_constraints=[('user_id', 'settings_key')] + ) + op.create_table('chat_messages', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('conversation_id', sa.Integer(), nullable=False), + sa.Column('role', sa.String(length=50), nullable=False), + sa.Column('content', sa.Text(), nullable=False), + sa.Column('agent_name', sa.String(length=255), nullable=True), + sa.Column('error_message', sa.Text(), nullable=True), + sa.Column('message_metadata', sa.JSON(), nullable=True), + sa.Column('requires_action', sa.Boolean(), nullable=False), + sa.Column('action_type', sa.String(length=255), nullable=True), + sa.Column('timestamp', sa.Float(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('chat_messages') + op.drop_table('user_settings') + op.drop_table('conversations') + op.drop_table('users') + # ### end Alembic commands ### diff --git a/submodules/agents/build/Dockerfile b/submodules/agents/build/Dockerfile new file mode 100755 index 00000000..ba191ac1 --- /dev/null +++ b/submodules/agents/build/Dockerfile @@ -0,0 +1,55 @@ +FROM --platform=linux/arm64 python:3.12-bullseye + +# Set the working directory in the container +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y gcc g++ procps curl && rm -rf /var/lib/apt/lists/* + +# Install Poetry +RUN curl -sSL https://install.python-poetry.org | python3 - && \ + ln -s /root/.local/bin/poetry /usr/local/bin/poetry + +# Copy pyproject.toml first for lock file generation +COPY pyproject.toml ./ + +# Generate lock file if it doesn't exist +RUN poetry lock --no-update || true + +# Copy lock file and install dependencies +COPY poetry.lock* ./ + +# Install Python dependencies using Poetry +# Remove the virtualenvs.create false line and let Poetry use its default virtualenv +RUN poetry install --no-interaction --no-ansi --no-root + +# Chrome installation for Debian Bullseye +RUN apt-get update && apt-get install -y \ + wget \ + gnupg2 \ + apt-transport-https \ + ca-certificates \ + unzip \ + xvfb \ + libnss3 \ + libxss1 \ + libasound2 \ + libatk-bridge2.0-0 \ + libgtk-3-0 \ + libgbm1 + +# Install ChromiumDriver instead of Chrome +RUN apt-get install -y chromium chromium-driver + +# Set environment variables for Chrome +ENV CHROME_BIN=/usr/bin/chromium +ENV CHROMEDRIVER_BIN=/usr/bin/chromedriver + +COPY . . + +# Expose the port your application listens on +EXPOSE 5000 + +# Use the full path to the virtualenv's uvicorn +WORKDIR /app/src +CMD ["poetry", "run", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5000", "--reload"] \ No newline at end of file diff --git a/submodules/agents/build/lint.sh b/submodules/agents/build/lint.sh new file mode 100755 index 00000000..4c84b7a2 --- /dev/null +++ b/submodules/agents/build/lint.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +## parse command line args #################################################### +skip_lint=0 +skip_mypy=0 + +# Check optional arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --skip-pylint) + skip_lint=1 + shift + ;; + --skip-mypy) + skip_mypy + shift + ;; + *) + # Ignore unknown options + shift + ;; + esac +done + +if [ $skip_lint -eq 0 ]; then + git diff --name-only main | grep -E '\.py$' | grep '^apps/bluebirds_platform/sequences' | sed 's#^apps/bluebirds_platform/sequences/##' | grep -Ev '^migrations/' | xargs poetry run pylint +fi + +if [ $skip_mypy -eq 0 ]; then + git diff --name-only main | grep -E '\.py$' | grep '^apps/bluebirds_platform/sequences' | sed 's#^apps/bluebirds_platform/sequences/##' | grep -Ev '^migrations/' | xargs poetry run mypy +fi diff --git a/submodules/agents/conftest.py b/submodules/agents/conftest.py new file mode 100755 index 00000000..4754fade --- /dev/null +++ b/submodules/agents/conftest.py @@ -0,0 +1,101 @@ +import asyncio +import sys + +import pytest +from langchain_ollama import ChatOllama +from langchain_community.embeddings import OllamaEmbeddings + +from src.models.service.chat_models import ChatRequest, ChatMessage +from config import AppConfig + + +def pytest_addoption(parser): + """Add custom command line options""" + parser.addoption("--benchmark", action="store_true", help="run benchmark tests") + parser.addoption("--unit", action="store_true", help="run unit tests") + + +def pytest_runtest_setup(item): + """Skip tests based on custom command line options""" + markers = {marker.name for marker in item.iter_markers()} + benchmark_opt = item.config.getoption("--benchmark") + unit_opt = item.config.getoption("--unit") + + # Handle benchmark tests + if "benchmark" in markers and not benchmark_opt: + pytest.skip(reason="skipping benchmark tests") + + # Handle unit tests + if "unit" in markers and not unit_opt: + pytest.skip(reason="skipping unit tests") + + +# See https://github.com/pytest-dev/pytest-asyncio/issues/68 +# See https://github.com/pytest-dev/pytest-asyncio/issues/257 +# Also need ProactorEventLoop on older versions of Python with Windows so +# that asyncio subprocess works properly +@pytest.fixture(scope="session") +def event_loop(): + """Create an instance of the default event loop for each test session""" + if sys.version_info < (3, 8) and sys.platform == "win32": + loop = asyncio.ProactorEventLoop() + else: + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() + + +@pytest.fixture(scope="session") +def llm(): + try: + return ChatOllama( + model=AppConfig.OLLAMA_MODEL, + base_url="http://localhost:11434", + seed=42, + ) + except Exception as e: + pytest.skip(f"Ollama service not available: {str(e)}") + + +@pytest.fixture(scope="session") +def embeddings(): + """Shared fixture for Ollama embeddings""" + return OllamaEmbeddings(model=AppConfig.OLLAMA_EMBEDDING_MODEL, base_url=AppConfig.OLLAMA_URL) + + +@pytest.fixture +def make_chat_request(): + """ + Factory fixture to create ChatRequest objects with default values. + + Usage: + def test_something(make_chat_request): + request = make_chat_request( + content="What's my balance?", + agent_name="base" + ) + """ + + def _make_chat_request( + content: str, + agent_name: str = "base", + conversation_id: str = "test_conv", + chain_id: str = "1", + wallet_address: str = "0x123", + role: str = "user", + ) -> ChatRequest: + return ChatRequest( + conversation_id=conversation_id, + prompt=ChatMessage(role=role, content=content, agentName=agent_name), + chain_id=chain_id, + wallet_address=wallet_address, + ) + + return _make_chat_request + + +# Optional: Add common test data fixtures +@pytest.fixture +def mock_wallet(): + """Fixture for a mock wallet with common test values""" + return {"address": "0x123", "private_key": "0xabc"} diff --git a/submodules/agents/mypy.ini b/submodules/agents/mypy.ini new file mode 100644 index 00000000..6ad2d941 --- /dev/null +++ b/submodules/agents/mypy.ini @@ -0,0 +1,25 @@ +[mypy] +python_version = 3.12 +warn_return_any = True +warn_unused_configs = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +check_untyped_defs = True +disallow_untyped_decorators = True +no_implicit_optional = True +warn_redundant_casts = True +warn_unused_ignores = True +warn_no_return = True +warn_unreachable = True +namespace_packages = True +explicit_package_bases = True +files = src, test +ignore_missing_imports = True +exclude = ^migrations/ +cache_dir = ./.mypy_cache + +[mypy.plugins.sqlalchemy.ext.*] +init_hooks = sqlalchemy.ext.declarative.declarative_base:init_declarative_base + +[mypy-sqlalchemy.*] +ignore_missing_imports = True diff --git a/submodules/agents/poetry.lock b/submodules/agents/poetry.lock new file mode 100755 index 00000000..ae04c213 --- /dev/null +++ b/submodules/agents/poetry.lock @@ -0,0 +1,4850 @@ +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. + +[[package]] +name = "aiofiles" +version = "24.1.0" +description = "File support for asyncio." +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, + {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.4.0" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, + {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, +] + +[[package]] +name = "aiohttp" +version = "3.10.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, + {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, + {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, + {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, + {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, + {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, + {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, + {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, + {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, + {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, + {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, + {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, + {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, + {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "alembic" +version = "1.14.1" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "alembic-1.14.1-py3-none-any.whl", hash = "sha256:1acdd7a3a478e208b0503cd73614d5e4c6efafa4e73518bb60e4f2846a37b1c5"}, + {file = "alembic-1.14.1.tar.gz", hash = "sha256:496e888245a53adf1498fcab31713a469c65836f8de76e01399aa1c3e90dd213"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["backports.zoneinfo", "tzdata"] + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.8.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.9" +files = [ + {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, + {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "asn1crypto" +version = "1.5.1" +description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" +optional = false +python-versions = "*" +files = [ + {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, + {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, +] + +[[package]] +name = "astroid" +version = "2.15.8" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, + {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, +] + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""} + +[[package]] +name = "attrs" +version = "24.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "autopep8" +version = "2.3.2" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +optional = false +python-versions = ">=3.9" +files = [ + {file = "autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128"}, + {file = "autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758"}, +] + +[package.dependencies] +pycodestyle = ">=2.12.0" + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "bip-utils" +version = "2.9.3" +description = "Generation of mnemonics, seeds, private/public keys and addresses for different types of cryptocurrencies" +optional = false +python-versions = ">=3.7" +files = [ + {file = "bip_utils-2.9.3-py3-none-any.whl", hash = "sha256:ee26b8417a576c7f89b847da37316db01a5cece1994c1609d37fbeefb91ad45e"}, + {file = "bip_utils-2.9.3.tar.gz", hash = "sha256:72a8c95484b57e92311b0b2a3d5195b0ce4395c19a0b157d4a289e8b1300f48a"}, +] + +[package.dependencies] +cbor2 = ">=5.1.2,<6.0.0" +coincurve = {version = ">=19.0.1", markers = "python_version >= \"3.12\""} +crcmod = ">=1.7,<2.0" +ecdsa = ">=0.17,<1.0" +ed25519-blake2b = {version = ">=1.4.1,<2.0.0", markers = "python_version >= \"3.12\""} +py-sr25519-bindings = {version = ">=0.2.0,<2.0.0", markers = "python_version >= \"3.11\""} +pycryptodome = ">=3.15,<4.0" +pynacl = ">=1.5,<2.0" + +[package.extras] +develop = ["coverage (>=5.3)", "flake8 (>=3.8)", "isort (>=5.8)", "mypy (>=0.900)", "prospector[with-mypy,with-pyroma] (>=1.7)", "pytest (>=7.0)", "pytest-cov (>=2.10)"] + +[[package]] +name = "bitarray" +version = "2.9.2" +description = "efficient arrays of booleans -- C extension" +optional = false +python-versions = "*" +files = [ + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:917905de565d9576eb20f53c797c15ba88b9f4f19728acabec8d01eee1d3756a"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b35bfcb08b7693ab4bf9059111a6e9f14e07d57ac93cd967c420db58ab9b71e1"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ea1923d2e7880f9e1959e035da661767b5a2e16a45dfd57d6aa831e8b65ee1bf"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0b63a565e8a311cc8348ff1262d5784df0f79d64031d546411afd5dd7ef67d"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf0620da2b81946d28c0b16f3e3704d38e9837d85ee4f0652816e2609aaa4fed"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79a9b8b05f2876c7195a2b698c47528e86a73c61ea203394ff8e7a4434bda5c8"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:345c76b349ff145549652436235c5532e5bfe9db690db6f0a6ad301c62b9ef21"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e2936f090bf3f4d1771f44f9077ebccdbc0415d2b598d51a969afcb519df505"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f9346e98fc2abcef90b942973087e2462af6d3e3710e82938078d3493f7fef52"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6ec283d4741befb86e8c3ea2e9ac1d17416c956d392107e45263e736954b1f7"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:962892646599529917ef26266091e4cb3077c88b93c3833a909d68dcc971c4e3"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e8da5355d7d75a52df5b84750989e34e39919ec7e59fafc4c104cc1607ab2d31"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:603e7d640e54ad764d2b4da6b61e126259af84f253a20f512dd10689566e5478"}, + {file = "bitarray-2.9.2-cp310-cp310-win32.whl", hash = "sha256:f00079f8e69d75c2a417de7961a77612bb77ef46c09bc74607d86de4740771ef"}, + {file = "bitarray-2.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:1bb33673e7f7190a65f0a940c1ef63266abdb391f4a3e544a47542d40a81f536"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fe71fd4b76380c2772f96f1e53a524da7063645d647a4fcd3b651bdd80ca0f2e"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d527172919cdea1e13994a66d9708a80c3d33dedcf2f0548e4925e600fef3a3a"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:052c5073bdcaa9dd10628d99d37a2f33ec09364b86dd1f6281e2d9f8d3db3060"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e064caa55a6ed493aca1eda06f8b3f689778bc780a75e6ad7724642ba5dc62f7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:508069a04f658210fdeee85a7a0ca84db4bcc110cbb1d21f692caa13210f24a7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4da73ebd537d75fa7bccfc2228fcaedea0803f21dd9d0bf0d3b67fef3c4af294"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cb378eaa65cd43098f11ff5d27e48ee3b956d2c00d2d6b5bfc2a09fe183be47"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d14c790b91f6cbcd9b718f88ed737c78939980c69ac8c7f03dd7e60040c12951"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eea9318293bc0ea6447e9ebfba600a62f3428bea7e9c6d42170ae4f481dbab3"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b76ffec27c7450b8a334f967366a9ebadaea66ee43f5b530c12861b1a991f503"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:76b76a07d4ee611405045c6950a1e24c4362b6b44808d4ad6eea75e0dbc59af4"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c7d16beeaaab15b075990cd26963d6b5b22e8c5becd131781514a00b8bdd04bd"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60df43e868a615c7e15117a1e1c2e5e11f48f6457280eba6ddf8fbefbec7da99"}, + {file = "bitarray-2.9.2-cp311-cp311-win32.whl", hash = "sha256:e788608ed7767b7b3bbde6d49058bccdf94df0de9ca75d13aa99020cc7e68095"}, + {file = "bitarray-2.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:a23397da092ef0a8cfe729571da64c2fc30ac18243caa82ac7c4f965087506ff"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:90e3a281ffe3897991091b7c46fca38c2675bfd4399ffe79dfeded6c52715436"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bed637b674db5e6c8a97a4a321e3e4d73e72d50b5c6b29950008a93069cc64cd"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e49066d251dbbe4e6e3a5c3937d85b589e40e2669ad0eef41a00f82ec17d844b"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4344e96642e2211fb3a50558feff682c31563a4c64529a931769d40832ca79"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aeb60962ec4813c539a59fbd4f383509c7222b62c3fb1faa76b54943a613e33a"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f7982f10581bb16553719e5e8f933e003f5b22f7d25a68bdb30fac630a6ff"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71d1cabdeee0cdda4669168618f0e46b7dace207b29da7b63aaa1adc2b54081"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0ef2d0a6f1502d38d911d25609b44c6cc27bee0a4363dd295df78b075041b60"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6f71d92f533770fb027388b35b6e11988ab89242b883f48a6fe7202d238c61f8"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba0734aa300757c924f3faf8148e1b8c247176a0ac8e16aefdf9c1eb19e868f7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:d91406f413ccbf4af6ab5ae7bc78f772a95609f9ddd14123db36ef8c37116d95"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:87abb7f80c0a042f3fe8e5264da1a2756267450bb602110d5327b8eaff7682e7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b558ce85579b51a2e38703877d1e93b7728a7af664dd45a34e833534f0b755d"}, + {file = "bitarray-2.9.2-cp312-cp312-win32.whl", hash = "sha256:dac2399ee2889fbdd3472bfc2ede74c34cceb1ccf29a339964281a16eb1d3188"}, + {file = "bitarray-2.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:48a30d718d1a6dfc22a49547450107abe8f4afdf2abdcbe76eb9ed88edc49498"}, + {file = "bitarray-2.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2c6be1b651fad8f3adb7a5aa12c65b612cd9b89530969af941844ae680f7d981"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5b399ae6ab975257ec359f03b48fc00b1c1cd109471e41903548469b8feae5c"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b3543c8a1cb286ad105f11c25d8d0f712f41c5c55f90be39f0e5a1376c7d0b0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03adaacb79e2fb8f483ab3a67665eec53bb3fd0cd5dbd7358741aef124688db3"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae5b0657380d2581e13e46864d147a52c1e2bbac9f59b59c576e42fa7d10cf0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1f4bf6ea8eb9d7f30808c2e9894237a96650adfecbf5f3643862dc5982f89e"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a8873089be2aa15494c0f81af1209f6e1237d762c5065bc4766c1b84321e1b50"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:677e67f50e2559efc677a4366707070933ad5418b8347a603a49a070890b19bc"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a620d8ce4ea2f1c73c6b6b1399e14cb68c6915e2be3fad5808c2998ed55b4acf"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:64115ccabbdbe279c24c367b629c6b1d3da9ed36c7420129e27c338a3971bfee"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d6fb422772e75385b76ad1c52f45a68bd4efafd8be8d0061c11877be74c4d43"}, + {file = "bitarray-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:852e202875dd6dfd6139ce7ec4e98dac2b17d8d25934dc99900831e81c3adaef"}, + {file = "bitarray-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:7dfefdcb0dc6a3ba9936063cec65a74595571b375beabe18742b3d91d087eefd"}, + {file = "bitarray-2.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b306c4cf66912511422060f7f5e1149c8bdb404f8e00e600561b0749fdd45659"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a09c4f81635408e3387348f415521d4b94198c562c23330f560596a6aaa26eaf"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5361413fd2ecfdf44dc8f065177dc6aba97fa80a91b815586cb388763acf7f8d"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8a9475d415ef1eaae7942df6f780fa4dcd48fce32825eda591a17abba869299"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b87baa7bfff9a5878fcc1bffe49ecde6e647a72a64b39a69cd8a2992a43a34"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb6b86cfdfc503e92cb71c68766a24565359136961642504a7cc9faf936d9c88"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd56b8ae87ebc71bcacbd73615098e8a8de952ecbb5785b6b4e2b07da8a06e1f"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3fa909cfd675004aed8b4cc9df352415933656e0155a6209d878b7cb615c787e"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b069ca9bf728e0c5c5b60e00a89df9af34cc170c695c3bfa3b372d8f40288efb"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6067f2f07a7121749858c7daa93c8774325c91590b3e81a299621e347740c2ae"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:321841cdad1dd0f58fe62e80e9c9c7531f8ebf8be93f047401e930dc47425b1e"}, + {file = "bitarray-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:54e16e32e60973bb83c315de9975bc1bcfc9bd50bb13001c31da159bc49b0ca1"}, + {file = "bitarray-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f4dcadb7b8034aa3491ee8f5a69b3d9ba9d7d1e55c3cc1fc45be313e708277f8"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c8919fdbd3bb596b104388b56ae4b266eb28da1f2f7dff2e1f9334a21840fe96"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eb7a9d8a2e400a1026de341ad48e21670a6261a75b06df162c5c39b0d0e7c8f4"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6ec84668dd7b937874a2b2c293cd14ba84f37be0d196dead852e0ada9815d807"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2de9a31c34e543ae089fd2a5ced01292f725190e379921384f695e2d7184bd3"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9521f49ae121a17c0a41e5112249e6fa7f6a571245b1118de81fb86e7c1bc1ce"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6cc6545d6d76542aee3d18c1c9485fb7b9812b8df4ebe52c4535ec42081b48f"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:856bbe1616425f71c0df5ef2e8755e878d9504d5a531acba58ab4273c52c117a"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4bba8042ea6ab331ade91bc435d81ad72fddb098e49108610b0ce7780c14e68"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a035da89c959d98afc813e3c62f052690d67cfd55a36592f25d734b70de7d4b0"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d70b1579da7fb71be5a841a1f965d19aca0ef27f629cfc07d06b09aafd0a333"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:405b83bed28efaae6d86b6ab287c75712ead0adbfab2a1075a1b7ab47dad4d62"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7eb8be687c50da0b397d5e0ab7ca200b5ebb639e79a9f5e285851d1944c94be9"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eceb551dfeaf19c609003a69a0cf8264b0efd7abc3791a11dfabf4788daf0d19"}, + {file = "bitarray-2.9.2-cp38-cp38-win32.whl", hash = "sha256:bb198c6ed1edbcdaf3d1fa3c9c9d1cdb7e179a5134ef5ee660b53cdec43b34e7"}, + {file = "bitarray-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:648d2f2685590b0103c67a937c2fb9e09bcc8dfb166f0c7c77bd341902a6f5b3"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ea816dc8f8e65841a8bbdd30e921edffeeb6f76efe6a1eb0da147b60d539d1cf"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d0e32530f941c41eddfc77600ec89b65184cb909c549336463a738fab3ed285"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a22266fb416a3b6c258bf7f83c9fe531ba0b755a56986a81ad69dc0f3bcc070"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc6d3e80dd8239850f2604833ff3168b28909c8a9357abfed95632cccd17e3e7"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f135e804986b12bf14f2cd1eb86674c47dea86c4c5f0fa13c88978876b97ebe6"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87580c7f7d14f7ec401eda7adac1e2a25e95153e9c339872c8ae61b3208819a1"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b433e26993127732ac7b66a7821b2537c3044355798de7c5fcb0af34b8296f"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e497c535f2a9b68c69d36631bf2dba243e05eb343b00b9c7bbdc8c601c6802d"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e40b3cb9fa1edb4e0175d7c06345c49c7925fe93e39ef55ecb0bc40c906b0c09"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f2f8692f95c9e377eb19ca519d30d1f884b02feb7e115f798de47570a359e43f"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f0b84fc50b6dbeced4fa390688c07c10a73222810fb0e08392bd1a1b8259de36"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d656ad38c942e38a470ddbce26b5020e08e1a7ea86b8fd413bb9024b5189993a"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ab0f1dbfe5070db98771a56aa14797595acd45a1af9eadfb193851a270e7996"}, + {file = "bitarray-2.9.2-cp39-cp39-win32.whl", hash = "sha256:0a99b23ac845a9ea3157782c97465e6ae026fe0c7c4c1ed1d88f759fd6ea52d9"}, + {file = "bitarray-2.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:9bbcfc7c279e8d74b076e514e669b683f77b4a2a328585b3f16d4c5259c91222"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:43847799461d8ba71deb4d97b47250c2c2fb66d82cd3cb8b4caf52bb97c03034"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f44381b0a4bdf64416082f4f0e7140377ae962c0ced6f983c6d7bbfc034040"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a484061616fb4b158b80789bd3cb511f399d2116525a8b29b6334c68abc2310f"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ff9e38356cc803e06134cf8ae9758e836ccd1b793135ef3db53c7c5d71e93bc"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b44105792fbdcfbda3e26ee88786790fda409da4c71f6c2b73888108cf8f062f"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7e913098de169c7fc890638ce5e171387363eb812579e637c44261460ac00aa2"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6fe315355cdfe3ed22ef355b8bdc81a805ca4d0949d921576560e5b227a1112"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f708e91fdbe443f3bec2df394ed42328fb9b0446dff5cb4199023ac6499e09fd"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b7b09489b71f9f1f64c0fa0977e250ec24500767dab7383ba9912495849cadf"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:128cc3488176145b9b137fdcf54c1c201809bbb8dd30b260ee40afe915843b43"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21f21e7f56206be346bdbda2a6bdb2165a5e6a11821f88fd4911c5a6bbbdc7e2"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f4dd3af86dd8a617eb6464622fb64ca86e61ce99b59b5c35d8cd33f9c30603d"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6465de861aff7a2559f226b37982007417eab8c3557543879987f58b453519bd"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbaf2bb71d6027152d603f1d5f31e0dfd5e50173d06f877bec484e5396d4594b"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f32948c86e0d230a296686db28191b67ed229756f84728847daa0c7ab7406e3"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be94e5a685e60f9d24532af8fe5c268002e9016fa80272a94727f435de3d1003"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5cc9381fd54f3c23ae1039f977bfd6d041a5c3c1518104f616643c3a5a73b15"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd926e8ae4d1ed1ac4a8f37212a62886292f692bc1739fde98013bf210c2d175"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:461a3dafb9d5fda0bb3385dc507d78b1984b49da3fe4c6d56c869a54373b7008"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:393cb27fd859af5fd9c16eb26b1c59b17b390ff66b3ae5d0dd258270191baf13"}, + {file = "bitarray-2.9.2.tar.gz", hash = "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e"}, +] + +[[package]] +name = "black" +version = "24.10.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +files = [ + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "boto3" +version = "1.37.0" +description = "The AWS SDK for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "boto3-1.37.0-py3-none-any.whl", hash = "sha256:03bd8c93b226f07d944fd6b022e11a307bff94ab6a21d51675d7e3ea81ee8424"}, + {file = "boto3-1.37.0.tar.gz", hash = "sha256:01015b38017876d79efd7273f35d9a4adfba505237159621365bed21b9b65eca"}, +] + +[package.dependencies] +botocore = ">=1.37.0,<1.38.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.11.0,<0.12.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.37.0" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">=3.8" +files = [ + {file = "botocore-1.37.0-py3-none-any.whl", hash = "sha256:d01661f38c0edac87424344cdf4169f3ab9bc1bf1b677c8b230d025eb66c54a3"}, + {file = "botocore-1.37.0.tar.gz", hash = "sha256:b129d091a8360b4152ab65327186bf4e250de827c4a9b7ddf40a72b1acf1f3c1"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} + +[package.extras] +crt = ["awscrt (==0.23.8)"] + +[[package]] +name = "cbor2" +version = "5.6.4" +description = "CBOR (de)serializer with extensive tag support" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cbor2-5.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c40c68779a363f47a11ded7b189ba16767391d5eae27fac289e7f62b730ae1fc"}, + {file = "cbor2-5.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0625c8d3c487e509458459de99bf052f62eb5d773cc9fc141c6a6ea9367726d"}, + {file = "cbor2-5.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de7137622204168c3a57882f15dd09b5135bda2bcb1cf8b56b58d26b5150dfca"}, + {file = "cbor2-5.6.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3545e1e62ec48944b81da2c0e0a736ca98b9e4653c2365cae2f10ae871e9113"}, + {file = "cbor2-5.6.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6749913cd00a24eba17406a0bfc872044036c30a37eb2fcde7acfd975317e8a"}, + {file = "cbor2-5.6.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:57db966ab08443ee54b6f154f72021a41bfecd4ba897fe108728183ad8784a2a"}, + {file = "cbor2-5.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:380e0c7f4db574dcd86e6eee1b0041863b0aae7efd449d49b0b784cf9a481b9b"}, + {file = "cbor2-5.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5c763d50a1714e0356b90ad39194fc8ef319356b89fb001667a2e836bfde88e3"}, + {file = "cbor2-5.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58a7ac8861857a9f9b0de320a4808a2a5f68a2599b4c14863e2748d5a4686c99"}, + {file = "cbor2-5.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d715b2f101730335e84a25fe0893e2b6adf049d6d44da123bf243b8c875ffd8"}, + {file = "cbor2-5.6.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f53a67600038cb9668720b309fdfafa8c16d1a02570b96d2144d58d66774318"}, + {file = "cbor2-5.6.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f898bab20c4f42dca3688c673ff97c2f719b1811090430173c94452603fbcf13"}, + {file = "cbor2-5.6.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e5d50fb9f47d295c1b7f55592111350424283aff4cc88766c656aad0300f11f"}, + {file = "cbor2-5.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:7f9d867dcd814ab8383ad132eb4063e2b69f6a9f688797b7a8ca34a4eadb3944"}, + {file = "cbor2-5.6.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e0860ca88edf8aaec5461ce0e498eb5318f1bcc70d93f90091b7a1f1d351a167"}, + {file = "cbor2-5.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c38a0ed495a63a8bef6400158746a9cb03c36f89aeed699be7ffebf82720bf86"}, + {file = "cbor2-5.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c8d8c2f208c223a61bed48dfd0661694b891e423094ed30bac2ed75032142aa"}, + {file = "cbor2-5.6.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cd2ce6136e1985da989e5ba572521023a320dcefad5d1fff57fba261de80ca"}, + {file = "cbor2-5.6.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7facce04aed2bf69ef43bdffb725446fe243594c2451921e89cc305bede16f02"}, + {file = "cbor2-5.6.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f9c8ee0d89411e5e039a4f3419befe8b43c0dd8746eedc979e73f4c06fe0ef97"}, + {file = "cbor2-5.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:9b45d554daa540e2f29f1747df9f08f8d98ade65a67b1911791bc193d33a5923"}, + {file = "cbor2-5.6.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a5cb2c16687ccd76b38cfbfdb34468ab7d5635fb92c9dc5e07831c1816bd0a9"}, + {file = "cbor2-5.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6f985f531f7495527153c4f66c8c143e4cf8a658ec9e87b14bc5438e0a8d0911"}, + {file = "cbor2-5.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d9c7b4bd7c3ea7e5587d4f1bbe073b81719530ddadb999b184074f064896e2"}, + {file = "cbor2-5.6.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d06184dcdc275c389fee3cd0ea80b5e1769763df15f93ecd0bf4c281817365"}, + {file = "cbor2-5.6.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e9ba7116f201860fb4c3e80ef36be63851ec7e4a18af70fea22d09cab0b000bf"}, + {file = "cbor2-5.6.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:341468ae58bdedaa05c907ab16e90dd0d5c54d7d1e66698dfacdbc16a31e815b"}, + {file = "cbor2-5.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:bcb4994be1afcc81f9167c220645d878b608cae92e19f6706e770f9bc7bbff6c"}, + {file = "cbor2-5.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41c43abffe217dce70ae51c7086530687670a0995dfc90cc35f32f2cf4d86392"}, + {file = "cbor2-5.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:227a7e68ba378fe53741ed892b5b03fe472b5bd23ef26230a71964accebf50a2"}, + {file = "cbor2-5.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13521b7c9a0551fcc812d36afd03fc554fa4e1b193659bb5d4d521889aa81154"}, + {file = "cbor2-5.6.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4816d290535d20c7b7e2663b76da5b0deb4237b90275c202c26343d8852b8a"}, + {file = "cbor2-5.6.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1e98d370106821335efcc8fbe4136ea26b4747bf29ca0e66512b6c4f6f5cc59f"}, + {file = "cbor2-5.6.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:68743a18e16167ff37654a29321f64f0441801dba68359c82dc48173cc6c87e1"}, + {file = "cbor2-5.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:7ba5e9c6ed17526d266a1116c045c0941f710860c5f2495758df2e0d848c1b6d"}, + {file = "cbor2-5.6.4-py3-none-any.whl", hash = "sha256:fe411c4bf464f5976605103ebcd0f60b893ac3e4c7c8d8bc8f4a0cb456e33c60"}, + {file = "cbor2-5.6.4.tar.gz", hash = "sha256:1c533c50dde86bef1c6950602054a0ffa3c376e8b0e20c7b8f5b108793f6983e"}, +] + +[package.extras] +benchmarks = ["pytest-benchmark (==4.0.0)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)", "typing-extensions"] +test = ["coverage (>=7)", "hypothesis", "pytest"] + +[[package]] +name = "cdp-sdk" +version = "0.15.0" +description = "CDP Python SDK" +optional = false +python-versions = "<4.0,>=3.10" +files = [ + {file = "cdp_sdk-0.15.0-py3-none-any.whl", hash = "sha256:f640c1cad0aee829ad24ee1b41dc3f40f74472a1da4d53e257ba996527a64f95"}, + {file = "cdp_sdk-0.15.0.tar.gz", hash = "sha256:f7b015b2d3811d69db4ea5cb29cefc33fb47e7d2ce587f70222742ab602f610c"}, +] + +[package.dependencies] +bip-utils = ">=2.9.3,<3.0.0" +coincurve = ">=20.0.0,<21.0.0" +cryptography = ">=44.0.0,<45.0.0" +pydantic = ">=2.10.3,<3.0.0" +pyjwt = ">=2.10.1,<3.0.0" +python-dateutil = ">=2.9.0.post0,<3.0.0" +urllib3 = ">=2.2.3,<3.0.0" +web3 = ">=7.6.0,<8.0.0" + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "ckzg" +version = "2.0.1" +description = "Python bindings for C-KZG-4844" +optional = false +python-versions = "*" +files = [ + {file = "ckzg-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b7f9ba6d215f8981c5545f952aac84875bd564a63da02fb22a3d1321662ecdc0"}, + {file = "ckzg-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8fdec3ff96399acba9baeef9e1b0b5258c08f73245780e6c69f7b73def5e8d0a"}, + {file = "ckzg-2.0.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1644369af9900a9f109d417d6760693edf134118f3100d0c68f56667de775b80"}, + {file = "ckzg-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0a2146f122d489ac7e67ae0c0743f8d0db1718e6aeed8f05717340594fe07dd"}, + {file = "ckzg-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:979841be50f2782b447762db38e9bc927ae251f6ca86c54a26561a52068ee779"}, + {file = "ckzg-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4516d86647ee4e8ea9470f4adf68fbebb6dc1bdedff7d9592c2504fe53145908"}, + {file = "ckzg-2.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:91866fc58a29b4829201efd9ffadfac3ffeca6359254a54a360ff6a189c34bf5"}, + {file = "ckzg-2.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed35508dac059b2c0a7994383bc7a92eaf35d0b9ce790016819e2619e0f4b8a9"}, + {file = "ckzg-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:449c4fe38017351eca362106420eeb2d28d50b7e54aa8668b3af29a8ab780132"}, + {file = "ckzg-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:260608a22e2f2cadcd31f4495832d45d6460438c38faba9761b92df885a99d88"}, + {file = "ckzg-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e1015f99c50215098751b07d7e459ba9a2790d3692ca81552eed29996128e90d"}, + {file = "ckzg-2.0.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dd350d97554c161dc5b8c7b32c2dc8e659632c374f60e2669fb3c9b5b294827"}, + {file = "ckzg-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eec7724fa8dc4ae95757efe4a87e7b2d4b880cb348c72ce7355fc0c4f64bc298"}, + {file = "ckzg-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3fa0f4398fa67fb71f0a2b34a652cc89e6e0e6af1340b0dc771db1a5f3e089c"}, + {file = "ckzg-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f865a0297aabeeb638187a46f7df445763360417b9df4dea60560d512c2cda09"}, + {file = "ckzg-2.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b6ec738350771dbf5974fb70cc8bbb20a4df784af770f7e655922adc08a2171"}, + {file = "ckzg-2.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9b4b669fc77edeb16adc182efc32b3737b36f741a2e33a170d40619e8b171a94"}, + {file = "ckzg-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:decb97f4a17c7338b2130dcc4b045df4cc0e7785ece872c764b554c7c73a99ff"}, + {file = "ckzg-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:285cf3121b8a8c5609c5b706314f68d2ba2784ab02c5bb7487c6ae1714ecb27f"}, + {file = "ckzg-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f927bc41c2551b0ef0056a649a7ebed29d9665680a10795f4cee5002c69ddb7"}, + {file = "ckzg-2.0.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd9fb690c88919f30c9f3ab7cc46a7ecd734d5ff4c9ccea383c119b9b7cc4da"}, + {file = "ckzg-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fabc3bd41b306d1c7025d561c3281a007c2aca8ceaf998582dc3894904d9c73e"}, + {file = "ckzg-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eb50c53efdb9c34f762bd0c8006cf79bc92a9daf47aa6b541e496988484124f"}, + {file = "ckzg-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7960cc62f959403293fb53a3c2404778369ae7cefc6d7f202e5e00567cf98c4b"}, + {file = "ckzg-2.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d721bcd492294c70eca39da0b0a433c29b6a571dbac2f7084bab06334904af06"}, + {file = "ckzg-2.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dde2391d025b5033ef0eeacf62b11ecfe446aea25682b5f547a907766ad0a8cb"}, + {file = "ckzg-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fab8859d9420f6f7df4e094ee3639bc49d18c8dab0df81bee825e2363dd67a09"}, + {file = "ckzg-2.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9747d92883199d4f8f3a3d7018134745fddcf692dfe67115434e4b32609ea785"}, + {file = "ckzg-2.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b2cf58fb9e165da97f0ffe9f4a6efb73992645fac8e0fa223a6cc7ec486a434a"}, + {file = "ckzg-2.0.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d25d006899d76bb8c9d3e8b27981dd6b66a78f9826e33c1bf981af6577a69a19"}, + {file = "ckzg-2.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04bf0b32f04f5ea5e4b8518e292d3321bc05596fde95f9c3b4f504e5e4bc780"}, + {file = "ckzg-2.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0cf3dccd72376bff10e1833641cc9d642f34f60ca63972626d9dfcfdc8e77f"}, + {file = "ckzg-2.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:770809c7e93087470cc524724419b0f85590edb033c7c73ba94aef70b36ca18b"}, + {file = "ckzg-2.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e31b59b8124148d5e21f7e41b35532d7af98260c44a77c3917958adece84296d"}, + {file = "ckzg-2.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:174f0c356df644d6e349ce03b7284d83dbec859e11ca5d1b1b3bace8b8fbc65d"}, + {file = "ckzg-2.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:30e375cd45142e56b5dbfdec05ce4deb2368d7f7dedfc7408ba37d5639af05ff"}, + {file = "ckzg-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:abdee71958b214730a8341b16bdd413d0fab1b1a2504fbdb7b0ef2aeee9f9d22"}, + {file = "ckzg-2.0.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b4442667058db791325fe231f22e4fc7aaa3495d535d75af5595bc5f4f86036"}, + {file = "ckzg-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c3c9aa9d4477ad52f3561b717e776c1a8a442d9d8b06600c7d8a2857d1ecf05"}, + {file = "ckzg-2.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68e0a9cde35f11e80b4e560d22990f2f29dd200a95d3141acde137cb6c883f9a"}, + {file = "ckzg-2.0.1-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:4508a089e53330866d3360000d76483400eeab5f8057b8e1f3e344ce2cc0097b"}, + {file = "ckzg-2.0.1-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:828cecee16ec576dcf4386beac4eedfd058fd32ee90827f2282e7156a53600be"}, + {file = "ckzg-2.0.1-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:bd437ec1dfb4f5609979328b5f465a74307f45d46d24234868c67d44da96903b"}, + {file = "ckzg-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:70406b10acf68469ac62110047044a6c1a998f5d5fcd6e27cb3ec2d5760d0490"}, + {file = "ckzg-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2f53fba88febac17e82a96eb83dc38ecf4b28abcdd15c0246534c358bd3b26c4"}, + {file = "ckzg-2.0.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be8e0d5015e7755af4ddaab9ae1a4084f72c84b2cbb53628f4366aeed46cc380"}, + {file = "ckzg-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:261414121091042d29f28fc319d7c9a7f950f91f8bf54c010b581ee6a0499473"}, + {file = "ckzg-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524e1e66edd2be2c38b660824aa7b5d4525b41b30ac029d80738a8eee491aeb5"}, + {file = "ckzg-2.0.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:4a12a1d8ef8f475d9f0af9a538e1674057e007806cb1204bb269ea00d9f8c1e5"}, + {file = "ckzg-2.0.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:4cc4bb5f62417a58065deeaf124e178cb1787ef3228e6032600d1e0a2775765b"}, + {file = "ckzg-2.0.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:e7b015f5615bcb82fa0d935481a209fc1dcd9308fb52fb1a7e5400108df67a94"}, + {file = "ckzg-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0518933ff3b9550f9dd60d833cdb74e8e97cc1cc58f0560b706916606dfd47d0"}, + {file = "ckzg-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ac0bca0795990076cde1930ecec307379b5303e34367c6e6e8a16bdba5a7ba5"}, + {file = "ckzg-2.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8086d23a41020ede312843bda7ea4ee0c9831265379027904106f99f2f8ed469"}, + {file = "ckzg-2.0.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31d1b141d41fa51aeac9440c936b812e885aef5719adfbd3a27550d8dc433997"}, + {file = "ckzg-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60a58e4d8cb91bad669ca111b7ccdd05c32de6787fdb571bb599625b043ad75b"}, + {file = "ckzg-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633e143385622d7a43fcb5c4f400ec5ec15df0b1c74ab7d6449a41a7abed24ad"}, + {file = "ckzg-2.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4876313614ea01f9a0039b5ca2c754340ba40aa8405f8756912d90ae55718011"}, + {file = "ckzg-2.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:19c86c8102200484074afac06b3946b457ba9915636de187f63854522be2e3bd"}, + {file = "ckzg-2.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:564abf27878f129781e1df4d33b1c4e264e5b25f89c1bdf95b7d6256e4bceb6c"}, + {file = "ckzg-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:bc2da29bb970d3f5de04fb60797dbb4490c010ffc683cbc6016349dd6fa60d14"}, + {file = "ckzg-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9c1869671140ae7e698520b678b594ebd26fb59ef476711403541597d7d32c01"}, + {file = "ckzg-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1dd2aec2c61e8cc2ec815900f6768c6fe74b8fd29810e79b57c4150c6db32fb6"}, + {file = "ckzg-2.0.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9632ef17285dbdd3fcd9780f599c266da736d9b2897decc4ea02ba8690bdf72"}, + {file = "ckzg-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5747d7926873e3af0f6af5fca666feb0097d06cab525950e2664a6fbcb90165d"}, + {file = "ckzg-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75484ffb78aaebaeb3a30f1194a9143b904312b0f365fc4101e58e1bf5f89f66"}, + {file = "ckzg-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b2f72bc861b8bee9bac3314c58586d1ab2d23530f932a8f0a8562c8a4a6a45f9"}, + {file = "ckzg-2.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6f85e5802fea5b77f52fc3a14c8dec18a3f2b7c7070c811a4608940834f563cc"}, + {file = "ckzg-2.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:583a0b6b531a16974676439b23e7defb3dfe9732f18d13d2316152019c538af1"}, + {file = "ckzg-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:fafb9ac36b3398f8091d40773d9a450e5f74883dad8ca4ee22d472e7a231ef4d"}, + {file = "ckzg-2.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a12e96f20dce35e5222f898a5c8355054ef7c5ee038eeb97dbb694640b57577b"}, + {file = "ckzg-2.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4e0ebc55253addaa24dd2cd871bbe3b8f57855f32b5f74e70bf2cb76b6f7da54"}, + {file = "ckzg-2.0.1-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f917a7bf363a3735db30559e1ed63cf1ccf414234433ba687fa72c007abd756"}, + {file = "ckzg-2.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30f08c984286853271d4adae219e9ba87275a15047dbaa262ab8dd6c01be97b0"}, + {file = "ckzg-2.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fa1ea4888417e1f109fd5e57965788fb7f53b674329b937a65604a3c1ca1d03"}, + {file = "ckzg-2.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0b249914aeaf05cabc71c5c3797e3d6c126cb2c64192b7eb6755ef6aa5ab2f11"}, + {file = "ckzg-2.0.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a038e26baf650e1c733dcaa066ec948e75556b0c485e8c790c9a758875c71a93"}, + {file = "ckzg-2.0.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d6deb2c822122bdd32b555fa3b9216c86a355f24a2cc6a46b9b5743b412b60c"}, + {file = "ckzg-2.0.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50f6f2fbceba9ece3fbc1d2613a246f4e6ec4d787f542859e70c358928c0e4a1"}, + {file = "ckzg-2.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33ca40ef30129e2347bff3c95ad093403a0d5703476705ab92c92fbffe89bd5a"}, + {file = "ckzg-2.0.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:700b989c2f7089edc8fac6dfbd1b4677e85b966216ebedee8eb5e7894765c188"}, + {file = "ckzg-2.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f11933c007c3df02446a81957ac6e2488058b969e2eff5357c98ab569a0c7999"}, + {file = "ckzg-2.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3dbc9580eccecbd485f22e48f6044c48cbe6d838a7b7514cce179c085c65a960"}, + {file = "ckzg-2.0.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad6eb83f343fea6dd9a13fd1bce87b9cd26abeeb72f0674a62d26e40fe0b8aca"}, + {file = "ckzg-2.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:269f82b992facbd20461310cf5784551c77d11017b7d4b85d741d70359be6794"}, + {file = "ckzg-2.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:895d67cfd43130652e1ae39b90465b392d9a72c7c7e6f250eaf14689bfda6351"}, + {file = "ckzg-2.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:369cf1aeaf336c31f2050a7f54ae21cf46f4b2db23ebb013fff621144ab361bb"}, + {file = "ckzg-2.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:24fda2637598a467e7b11ff664805ee7fdf4f6c7b0c043d6d0a6ccb69b5681ee"}, + {file = "ckzg-2.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea27baabe5b22b92901c428768eacf93b992ac7681f93768ab24818ad26ccfed"}, + {file = "ckzg-2.0.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a33f71e382020f2bc4ead2bd6881a9bd3811d929f272da239ac01ad615a00802"}, + {file = "ckzg-2.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926507c569727bb4c851a1eea702c5e902267de96e06ce2d685019f973f72968"}, + {file = "ckzg-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f5f29518b0a4555d8f2a28559209bd1d4080547aa629ff9ee51799346573b3f"}, + {file = "ckzg-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4595db84ce63c227e4448de0f7b39d3043e3477d78394ff651708c37fee6c486"}, + {file = "ckzg-2.0.1.tar.gz", hash = "sha256:62c5adc381637affa7e1df465c57750b356a761b8a3164c3106589b02532b9c9"}, +] + +[[package]] +name = "click" +version = "8.1.8" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "coincurve" +version = "20.0.0" +description = "Cross-platform Python CFFI bindings for libsecp256k1" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coincurve-20.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d559b22828638390118cae9372a1bb6f6594f5584c311deb1de6a83163a0919b"}, + {file = "coincurve-20.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33d7f6ebd90fcc550f819f7f2cce2af525c342aac07f0ccda46ad8956ad9d99b"}, + {file = "coincurve-20.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22d70dd55d13fd427418eb41c20fde0a20a5e5f016e2b1bb94710701e759e7e0"}, + {file = "coincurve-20.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f18d481eaae72c169f334cde1fd22011a884e0c9c6adc3fdc1fd13df8236a3"}, + {file = "coincurve-20.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de1ec57f43c3526bc462be58fb97910dc1fdd5acab6c71eda9f9719a5bd7489"}, + {file = "coincurve-20.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a6f007c44c726b5c0b3724093c0d4fb8e294f6b6869beb02d7473b21777473a3"}, + {file = "coincurve-20.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0ff1f3b81330db5092c24da2102e4fcba5094f14945b3eb40746456ceabdd6d9"}, + {file = "coincurve-20.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82f7de97694d9343f26bd1c8e081b168e5f525894c12445548ce458af227f536"}, + {file = "coincurve-20.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:e905b4b084b4f3b61e5a5d58ac2632fd1d07b7b13b4c6d778335a6ca1dafd7a3"}, + {file = "coincurve-20.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:3657bb5ed0baf1cf8cf356e7d44aa90a7902cc3dd4a435c6d4d0bed0553ad4f7"}, + {file = "coincurve-20.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:44087d1126d43925bf9a2391ce5601bf30ce0dba4466c239172dc43226696018"}, + {file = "coincurve-20.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccf0ba38b0f307a9b3ce28933f6c71dc12ef3a0985712ca09f48591afd597c8"}, + {file = "coincurve-20.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:566bc5986debdf8572b6be824fd4de03d533c49f3de778e29f69017ae3fe82d8"}, + {file = "coincurve-20.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4d70283168e146f025005c15406086513d5d35e89a60cf4326025930d45013a"}, + {file = "coincurve-20.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:763c6122dd7d5e7a81c86414ce360dbe9a2d4afa1ca6c853ee03d63820b3d0c5"}, + {file = "coincurve-20.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f00c361c356bcea386d47a191bb8ac60429f4b51c188966a201bfecaf306ff7f"}, + {file = "coincurve-20.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4af57bdadd2e64d117dd0b33cfefe76e90c7a6c496a7b034fc65fd01ec249b15"}, + {file = "coincurve-20.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a26437b7cbde13fb6e09261610b788ca2a0ca2195c62030afd1e1e0d1a62e035"}, + {file = "coincurve-20.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:ed51f8bba35e6c7676ad65539c3dbc35acf014fc402101fa24f6b0a15a74ab9e"}, + {file = "coincurve-20.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:594b840fc25d74118407edbbbc754b815f1bba9759dbf4f67f1c2b78396df2d3"}, + {file = "coincurve-20.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4df4416a6c0370d777aa725a25b14b04e45aa228da1251c258ff91444643f688"}, + {file = "coincurve-20.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1ccc3e4db55abf3fc0e604a187fdb05f0702bc5952e503d9a75f4ae6eeb4cb3a"}, + {file = "coincurve-20.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8335b1658a2ef5b3eb66d52647742fe8c6f413ad5b9d5310d7ea6d8060d40f"}, + {file = "coincurve-20.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ac025e485a0229fd5394e0bf6b4a75f8a4f6cee0dcf6f0b01a2ef05c5210ff"}, + {file = "coincurve-20.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e46e3f1c21b3330857bcb1a3a5b942f645c8bce912a8a2b252216f34acfe4195"}, + {file = "coincurve-20.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:df9ff9b17a1d27271bf476cf3fa92df4c151663b11a55d8cea838b8f88d83624"}, + {file = "coincurve-20.0.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4155759f071375699282e03b3d95fb473ee05c022641c077533e0d906311e57a"}, + {file = "coincurve-20.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0530b9dd02fc6f6c2916716974b79bdab874227f560c422801ade290e3fc5013"}, + {file = "coincurve-20.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:eacf9c0ce8739c84549a89c083b1f3526c8780b84517ee75d6b43d276e55f8a0"}, + {file = "coincurve-20.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:52a67bfddbd6224dfa42085c88ad176559801b57d6a8bd30d92ee040de88b7b3"}, + {file = "coincurve-20.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e951b1d695b62376f60519a84c4facaf756eeb9c5aff975bea0942833f185d"}, + {file = "coincurve-20.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e9e548db77f4ea34c0d748dddefc698adb0ee3fab23ed19f80fb2118dac70f6"}, + {file = "coincurve-20.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cdbf0da0e0809366fdfff236b7eb6e663669c7b1f46361a4c4d05f5b7e94c57"}, + {file = "coincurve-20.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d72222b4ecd3952e8ffcbf59bc7e0d1b181161ba170b60e5c8e1f359a43bbe7e"}, + {file = "coincurve-20.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9add43c4807f0c17a940ce4076334c28f51d09c145cd478400e89dcfb83fb59d"}, + {file = "coincurve-20.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc94cceea6ec8863815134083e6221a034b1ecef822d0277cf6ad2e70009b7f"}, + {file = "coincurve-20.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ffbdfef6a6d147988eabaed681287a9a7e6ba45ecc0a8b94ba62ad0a7656d97"}, + {file = "coincurve-20.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13335c19c7e5f36eaba2a53c68073d981980d7dc7abfee68d29f2da887ccd24e"}, + {file = "coincurve-20.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:7fbfb8d16cf2bea2cf48fc5246d4cb0a06607d73bb5c57c007c9aed7509f855e"}, + {file = "coincurve-20.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4870047704cddaae7f0266a549c927407c2ba0ec92d689e3d2b511736812a905"}, + {file = "coincurve-20.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81ce41263517b0a9f43cd570c87720b3c13324929584fa28d2e4095969b6015d"}, + {file = "coincurve-20.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:572083ccce6c7b514d482f25f394368f4ae888f478bd0b067519d33160ea2fcc"}, + {file = "coincurve-20.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee5bc78a31a2f1370baf28aaff3949bc48f940a12b0359d1cd2c4115742874e6"}, + {file = "coincurve-20.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2895d032e281c4e747947aae4bcfeef7c57eabfd9be22886c0ca4e1365c7c1f"}, + {file = "coincurve-20.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d3e2f21957ada0e1742edbde117bb41758fa8691b69c8d186c23e9e522ea71cd"}, + {file = "coincurve-20.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c2baa26b1aad1947ca07b3aa9e6a98940c5141c6bdd0f9b44d89e36da7282ffa"}, + {file = "coincurve-20.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7eacc7944ddf9e2b7448ecbe84753841ab9874b8c332a4f5cc3b2f184db9f4a2"}, + {file = "coincurve-20.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:c293c095dc690178b822cadaaeb81de3cc0d28f8bdf8216ed23551dcce153a26"}, + {file = "coincurve-20.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:11a47083a0b7092d3eb50929f74ffd947c4a5e7035796b81310ea85289088c7a"}, + {file = "coincurve-20.0.0.tar.gz", hash = "sha256:872419e404300302e938849b6b92a196fabdad651060b559dc310e52f8392829"}, +] + +[package.dependencies] +asn1crypto = "*" +cffi = ">=1.3.0" + +[package.extras] +dev = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "crcmod" +version = "1.7" +description = "CRC Generator" +optional = false +python-versions = "*" +files = [ + {file = "crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e"}, +] + +[[package]] +name = "cryptography" +version = "44.0.0" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +files = [ + {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, + {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, + {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, + {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, + {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, + {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, + {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "cytoolz" +version = "0.12.3" +description = "Cython implementation of Toolz: High performance functional utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5"}, + {file = "cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd"}, + {file = "cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2"}, + {file = "cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605"}, + {file = "cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, + {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, + {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, + {file = "cytoolz-0.12.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win32.whl", hash = "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788"}, + {file = "cytoolz-0.12.3-cp38-cp38-win32.whl", hash = "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb"}, + {file = "cytoolz-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa"}, + {file = "cytoolz-0.12.3-cp39-cp39-win32.whl", hash = "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329"}, + {file = "cytoolz-0.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb"}, + {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, +] + +[package.dependencies] +toolz = ">=0.8.0" + +[package.extras] +cython = ["cython"] + +[[package]] +name = "dataclasses-json" +version = "0.6.7" +description = "Easily serialize dataclasses to and from JSON." +optional = false +python-versions = "<4.0,>=3.7" +files = [ + {file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"}, + {file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"}, +] + +[package.dependencies] +marshmallow = ">=3.18.0,<4.0.0" +typing-inspect = ">=0.4.0,<1" + +[[package]] +name = "dill" +version = "0.3.9" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, + {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "diskcache" +version = "5.6.3" +description = "Disk Cache -- Disk and file backed persistent cache." +optional = false +python-versions = ">=3" +files = [ + {file = "diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19"}, + {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, +] + +[[package]] +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, +] + +[[package]] +name = "ecdsa" +version = "0.19.0" +description = "ECDSA cryptographic signature library (pure python)" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6" +files = [ + {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, + {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, +] + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + +[[package]] +name = "ed25519-blake2b" +version = "1.4.1" +description = "Ed25519 public-key signatures (BLAKE2b fork)" +optional = false +python-versions = "*" +files = [ + {file = "ed25519-blake2b-1.4.1.tar.gz", hash = "sha256:731e9f93cd1ac1a64649575f3519a99ffe0bb1e4cf7bf5f5f0be513a39df7363"}, +] + +[[package]] +name = "eth-abi" +version = "5.1.0" +description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_abi-5.1.0-py3-none-any.whl", hash = "sha256:84cac2626a7db8b7d9ebe62b0fdca676ab1014cc7f777189e3c0cd721a4c16d8"}, + {file = "eth_abi-5.1.0.tar.gz", hash = "sha256:33ddd756206e90f7ddff1330cc8cac4aa411a824fe779314a0a52abea2c8fc14"}, +] + +[package.dependencies] +eth-typing = ">=3.0.0" +eth-utils = ">=2.0.0" +parsimonious = ">=0.10.0,<0.11.0" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-timeout (>=2.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-timeout (>=2.0.0)", "pytest-xdist (>=2.4.0)"] +tools = ["hypothesis (>=4.18.2,<5.0.0)"] + +[[package]] +name = "eth-account" +version = "0.13.3" +description = "eth-account: Sign Ethereum transactions and messages with local private keys" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_account-0.13.3-py3-none-any.whl", hash = "sha256:c8f3dae3403b8647f386fcc081fb8c2a0970991cf3e00af7e7ebd73f95d6a319"}, + {file = "eth_account-0.13.3.tar.gz", hash = "sha256:03d6af5d314e64b3dd53283e15b24736c5caa24542e5edac0455d6ff87d8b1e0"}, +] + +[package.dependencies] +bitarray = ">=2.4.0" +ckzg = ">=2.0.0" +eth-abi = ">=4.0.0-b.2" +eth-keyfile = ">=0.7.0" +eth-keys = ">=0.4.0" +eth-rlp = ">=2.1.0" +eth-utils = ">=2.0.0" +hexbytes = ">=1.2.0" +pydantic = ">=2.0.0" +rlp = ">=1.0.0" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "coverage", "hypothesis (>=4.18.0,<5)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-hash" +version = "0.7.0" +description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" +optional = false +python-versions = ">=3.8, <4" +files = [ + {file = "eth-hash-0.7.0.tar.gz", hash = "sha256:bacdc705bfd85dadd055ecd35fd1b4f846b671add101427e089a4ca2e8db310a"}, + {file = "eth_hash-0.7.0-py3-none-any.whl", hash = "sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f"}, +] + +[package.dependencies] +pycryptodome = {version = ">=3.6.6,<4", optional = true, markers = "extra == \"pycryptodome\""} + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +pycryptodome = ["pycryptodome (>=3.6.6,<4)"] +pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-keyfile" +version = "0.8.1" +description = "eth-keyfile: A library for handling the encrypted keyfiles used to store ethereum private keys" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_keyfile-0.8.1-py3-none-any.whl", hash = "sha256:65387378b82fe7e86d7cb9f8d98e6d639142661b2f6f490629da09fddbef6d64"}, + {file = "eth_keyfile-0.8.1.tar.gz", hash = "sha256:9708bc31f386b52cca0969238ff35b1ac72bd7a7186f2a84b86110d3c973bec1"}, +] + +[package.dependencies] +eth-keys = ">=0.4.0" +eth-utils = ">=2" +pycryptodome = ">=3.6.6,<4" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-keys" +version = "0.5.1" +description = "eth-keys: Common API for Ethereum key operations" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_keys-0.5.1-py3-none-any.whl", hash = "sha256:ad13d920a2217a49bed3a1a7f54fb0980f53caf86d3bbab2139fd3330a17b97e"}, + {file = "eth_keys-0.5.1.tar.gz", hash = "sha256:2b587e4bbb9ac2195215a7ab0c0fb16042b17d4ec50240ed670bbb8f53da7a48"}, +] + +[package.dependencies] +eth-typing = ">=3" +eth-utils = ">=2" + +[package.extras] +coincurve = ["coincurve (>=12.0.0)"] +dev = ["asn1tools (>=0.146.2)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "coincurve (>=12.0.0)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "ipython", "pre-commit (>=3.4.0)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["asn1tools (>=0.146.2)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)"] + +[[package]] +name = "eth-rlp" +version = "2.1.0" +description = "eth-rlp: RLP definitions for common Ethereum objects in Python" +optional = false +python-versions = ">=3.8, <4" +files = [ + {file = "eth-rlp-2.1.0.tar.gz", hash = "sha256:d5b408a8cd20ed496e8e66d0559560d29bc21cee482f893936a1f05d0dddc4a0"}, + {file = "eth_rlp-2.1.0-py3-none-any.whl", hash = "sha256:6f476eb7e37d81feaba5d98aed887e467be92648778c44b19fe594aea209cde1"}, +] + +[package.dependencies] +eth-utils = ">=2.0.0" +hexbytes = ">=1.2.0" +rlp = ">=0.6.0" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-hash[pycryptodome]", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-typing" +version = "5.0.0" +description = "eth-typing: Common type annotations for ethereum python packages" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_typing-5.0.0-py3-none-any.whl", hash = "sha256:c7ebc8595e7b65175bb4b4176c2b548ab21b13329f2058e84d4f8c289ba9f577"}, + {file = "eth_typing-5.0.0.tar.gz", hash = "sha256:87ce7cee75665c09d2dcff8de1b496609d5e32fcd2e2b1d8fc0370c29eedcdc0"}, +] + +[package.dependencies] +typing-extensions = ">=4.5.0" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-utils" +version = "5.0.0" +description = "eth-utils: Common utility functions for python code that interacts with Ethereum" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_utils-5.0.0-py3-none-any.whl", hash = "sha256:99c44eca11db74dbb881a1d70b24cd80436fc62fe527d2f5c3e3cf7932aba7b2"}, + {file = "eth_utils-5.0.0.tar.gz", hash = "sha256:a5eb9555f43f4579eb83cb84f9dda9f3d6663bbd4a5a6b693f8d35045f305a1f"}, +] + +[package.dependencies] +cytoolz = {version = ">=0.10.1", markers = "implementation_name == \"cpython\""} +eth-hash = ">=0.3.1" +eth-typing = ">=5.0.0" +hexbytes = ">=1.0.0" +toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} + +[package.extras] +dev = ["build (>=0.9.0)", "bump-my-version (>=0.19.0)", "eth-hash[pycryptodome]", "hypothesis (>=4.43.0)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["hypothesis (>=4.43.0)", "mypy (==1.10.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eval-type-backport" +version = "0.2.2" +description = "Like `typing._eval_type`, but lets older Python versions use newer typing features." +optional = false +python-versions = ">=3.8" +files = [ + {file = "eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a"}, + {file = "eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "faiss-cpu" +version = "1.8.0.post1" +description = "A library for efficient similarity search and clustering of dense vectors." +optional = false +python-versions = ">=3.8" +files = [ + {file = "faiss_cpu-1.8.0.post1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:fd84721eb599aa1da19b1b36345bb8705a60bb1d2887bbbc395a29e3d36a1a62"}, + {file = "faiss_cpu-1.8.0.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b78ff9079d15fd0f156bf5dd8a2975a8abffac1854a86ece263eec1500a2e836"}, + {file = "faiss_cpu-1.8.0.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de25c943d1789e35fe06a20884c88cd32aedbb1a33bb8da2238cdea7bd9633f"}, + {file = "faiss_cpu-1.8.0.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adae0f1b144e7216da696f14bc4991ca4300c94baaa59247c3d322588e661c95"}, + {file = "faiss_cpu-1.8.0.post1-cp310-cp310-win_amd64.whl", hash = "sha256:00345290680a444a4b4cb2d98a3844bb5c401a2160fee547c7631d759fd2ec3e"}, + {file = "faiss_cpu-1.8.0.post1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:8d4bade10cb63e9f9ff261751edd7eb097b1f4bf30be4d0d25d6f688559d795e"}, + {file = "faiss_cpu-1.8.0.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20bd43eca3b7d77e71ea56b7a558cc28e900d8abff417eb285e2d92e95d934d4"}, + {file = "faiss_cpu-1.8.0.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8542a87743a7f94ac656fd3e9592ad57e58b04d961ad2fe654a22a8ca59defdb"}, + {file = "faiss_cpu-1.8.0.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed46928de3dc20170b10fec89c54075a11383c2aaf4f119c63e0f6ae5a507d74"}, + {file = "faiss_cpu-1.8.0.post1-cp311-cp311-win_amd64.whl", hash = "sha256:4fa5fc8ea210b919aa469e27d6687e50052db906e7fec3f2257178b1384fa18b"}, + {file = "faiss_cpu-1.8.0.post1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:96aec0d08a3099883af3a9b6356cfe736e8bd879318a940a27e9d1ae6f33d788"}, + {file = "faiss_cpu-1.8.0.post1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:92b06147fa84732ecdc965922e8ef50dc7011ef8be65821ff4abb2118cb5dce0"}, + {file = "faiss_cpu-1.8.0.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:709ef9394d1148aef70dbe890edbde8c282a4a2e06a8b69ab64f65e90f5ba572"}, + {file = "faiss_cpu-1.8.0.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:327a9c30971bf72cd8392b15eb4aff5d898c453212eae656dfaa3ba555b9ca0c"}, + {file = "faiss_cpu-1.8.0.post1-cp312-cp312-win_amd64.whl", hash = "sha256:8756f1d93faba56349883fa2f5d47fe36bb2f11f789200c6b1c691ef805485f2"}, + {file = "faiss_cpu-1.8.0.post1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:f4a3045909c447bf1955b70083891e80f2c87c5427f20cae25245e08ec5c9e52"}, + {file = "faiss_cpu-1.8.0.post1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8842b7fc921ca1fafdb0845f2ba029e79df04eebae72ab135239f93478a9b7a2"}, + {file = "faiss_cpu-1.8.0.post1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d5a9799634e32c3862d5436d1e78112ed9a38f319e4523f5916e55d86adda8f"}, + {file = "faiss_cpu-1.8.0.post1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a70923b0fbbb40f647e20bcbcbfd472277e6d84bb23ff12d2a94b6841806b55"}, + {file = "faiss_cpu-1.8.0.post1-cp38-cp38-win_amd64.whl", hash = "sha256:ce652df3c4dd50c88ac9235d072f30ce60694dc422c5f523bbbcab320e8f3097"}, + {file = "faiss_cpu-1.8.0.post1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:83ef04b17b19189dd6601a941bdf4bfa9de0740dbcd80305aeba51a1b1955f80"}, + {file = "faiss_cpu-1.8.0.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c50c8697077470ede7f1939ef8dc8a846ec19cf1893b543f6b67f9af03b0a122"}, + {file = "faiss_cpu-1.8.0.post1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ce428a7a67fe5c64047280e5e12a8dbdecf7002f9d127b26cf1db354e9fe76"}, + {file = "faiss_cpu-1.8.0.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f3b36b80380bae523e3198cfb4a137867055945ce7bf10d18fe9f0284f2fb47"}, + {file = "faiss_cpu-1.8.0.post1-cp39-cp39-win_amd64.whl", hash = "sha256:4fcc67a2353f08a20c1ab955de3cde14ef3b447761b26244a5aa849c15cbc9b3"}, + {file = "faiss_cpu-1.8.0.post1.tar.gz", hash = "sha256:5686af34414678c3d49c4fa8d774df7156e9cb48d7029071e56230e74b01cc13"}, +] + +[package.dependencies] +numpy = ">=1.0,<2.0" +packaging = "*" + +[[package]] +name = "fastapi" +version = "0.115.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.115.0-py3-none-any.whl", hash = "sha256:17ea427674467486e997206a5ab25760f6b09e069f099b96f5b55a32fb6f1631"}, + {file = "fastapi-0.115.0.tar.gz", hash = "sha256:f93b4ca3529a8ebc6fc3fcf710e5efa8de3df9b41570958abf1d97d843138004"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.37.2,<0.39.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "feedparser" +version = "6.0.11" +description = "Universal feed parser, handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds" +optional = false +python-versions = ">=3.6" +files = [ + {file = "feedparser-6.0.11-py3-none-any.whl", hash = "sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45"}, + {file = "feedparser-6.0.11.tar.gz", hash = "sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5"}, +] + +[package.dependencies] +sgmllib3k = "*" + +[[package]] +name = "filelock" +version = "3.17.0" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.9" +files = [ + {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, + {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] +typing = ["typing-extensions (>=4.12.2)"] + +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + +[[package]] +name = "greenlet" +version = "3.1.1" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, + {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, + {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, + {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, + {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, + {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "hexbytes" +version = "1.2.1" +description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "hexbytes-1.2.1-py3-none-any.whl", hash = "sha256:e64890b203a31f4a23ef11470ecfcca565beaee9198df623047df322b757471a"}, + {file = "hexbytes-1.2.1.tar.gz", hash = "sha256:515f00dddf31053db4d0d7636dd16061c1d896c3109b8e751005db4ca46bcca7"}, +] + +[package.extras] +dev = ["build (>=0.9.0)", "bump-my-version (>=0.19.0)", "eth-utils (>=2.0.0)", "hypothesis (>=3.44.24,<=6.31.6)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-utils (>=2.0.0)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "httpcore" +version = "1.0.7" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.27.2" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, + {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jinja2" +version = "3.1.5" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jiter" +version = "0.8.2" +description = "Fast iterable JSON parser." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jiter-0.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ca8577f6a413abe29b079bc30f907894d7eb07a865c4df69475e868d73e71c7b"}, + {file = "jiter-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b25bd626bde7fb51534190c7e3cb97cee89ee76b76d7585580e22f34f5e3f393"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c826a221851a8dc028eb6d7d6429ba03184fa3c7e83ae01cd6d3bd1d4bd17d"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d35c864c2dff13dfd79fb070fc4fc6235d7b9b359efe340e1261deb21b9fcb66"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f557c55bc2b7676e74d39d19bcb8775ca295c7a028246175d6a8b431e70835e5"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:580ccf358539153db147e40751a0b41688a5ceb275e6f3e93d91c9467f42b2e3"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af102d3372e917cffce49b521e4c32c497515119dc7bd8a75665e90a718bbf08"}, + {file = "jiter-0.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cadcc978f82397d515bb2683fc0d50103acff2a180552654bb92d6045dec2c49"}, + {file = "jiter-0.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba5bdf56969cad2019d4e8ffd3f879b5fdc792624129741d3d83fc832fef8c7d"}, + {file = "jiter-0.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3b94a33a241bee9e34b8481cdcaa3d5c2116f575e0226e421bed3f7a6ea71cff"}, + {file = "jiter-0.8.2-cp310-cp310-win32.whl", hash = "sha256:6e5337bf454abddd91bd048ce0dca5134056fc99ca0205258766db35d0a2ea43"}, + {file = "jiter-0.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:4a9220497ca0cb1fe94e3f334f65b9b5102a0b8147646118f020d8ce1de70105"}, + {file = "jiter-0.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2dd61c5afc88a4fda7d8b2cf03ae5947c6ac7516d32b7a15bf4b49569a5c076b"}, + {file = "jiter-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c710d657c8d1d2adbbb5c0b0c6bfcec28fd35bd6b5f016395f9ac43e878a15"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9584de0cd306072635fe4b89742bf26feae858a0683b399ad0c2509011b9dc0"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a90a923338531b7970abb063cfc087eebae6ef8ec8139762007188f6bc69a9f"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21974d246ed0181558087cd9f76e84e8321091ebfb3a93d4c341479a736f099"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32475a42b2ea7b344069dc1e81445cfc00b9d0e3ca837f0523072432332e9f74"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9931fd36ee513c26b5bf08c940b0ac875de175341cbdd4fa3be109f0492586"}, + {file = "jiter-0.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0820f4a3a59ddced7fce696d86a096d5cc48d32a4183483a17671a61edfddc"}, + {file = "jiter-0.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ffc86ae5e3e6a93765d49d1ab47b6075a9c978a2b3b80f0f32628f39caa0c88"}, + {file = "jiter-0.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5127dc1abd809431172bc3fbe8168d6b90556a30bb10acd5ded41c3cfd6f43b6"}, + {file = "jiter-0.8.2-cp311-cp311-win32.whl", hash = "sha256:66227a2c7b575720c1871c8800d3a0122bb8ee94edb43a5685aa9aceb2782d44"}, + {file = "jiter-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:cde031d8413842a1e7501e9129b8e676e62a657f8ec8166e18a70d94d4682855"}, + {file = "jiter-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e6ec2be506e7d6f9527dae9ff4b7f54e68ea44a0ef6b098256ddf895218a2f8f"}, + {file = "jiter-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76e324da7b5da060287c54f2fabd3db5f76468006c811831f051942bf68c9d44"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:180a8aea058f7535d1c84183c0362c710f4750bef66630c05f40c93c2b152a0f"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025337859077b41548bdcbabe38698bcd93cfe10b06ff66617a48ff92c9aec60"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecff0dc14f409599bbcafa7e470c00b80f17abc14d1405d38ab02e4b42e55b57"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd9fee7d0775ebaba131f7ca2e2d83839a62ad65e8e02fe2bd8fc975cedeb9e"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14601dcac4889e0a1c75ccf6a0e4baf70dbc75041e51bcf8d0e9274519df6887"}, + {file = "jiter-0.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92249669925bc1c54fcd2ec73f70f2c1d6a817928480ee1c65af5f6b81cdf12d"}, + {file = "jiter-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e725edd0929fa79f8349ab4ec7f81c714df51dc4e991539a578e5018fa4a7152"}, + {file = "jiter-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf55846c7b7a680eebaf9c3c48d630e1bf51bdf76c68a5f654b8524335b0ad29"}, + {file = "jiter-0.8.2-cp312-cp312-win32.whl", hash = "sha256:7efe4853ecd3d6110301665a5178b9856be7e2a9485f49d91aa4d737ad2ae49e"}, + {file = "jiter-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:83c0efd80b29695058d0fd2fa8a556490dbce9804eac3e281f373bbc99045f6c"}, + {file = "jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84"}, + {file = "jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef"}, + {file = "jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1"}, + {file = "jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9"}, + {file = "jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05"}, + {file = "jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a"}, + {file = "jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865"}, + {file = "jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca"}, + {file = "jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0"}, + {file = "jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566"}, + {file = "jiter-0.8.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9e1fa156ee9454642adb7e7234a383884452532bc9d53d5af2d18d98ada1d79c"}, + {file = "jiter-0.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cf5dfa9956d96ff2efb0f8e9c7d055904012c952539a774305aaaf3abdf3d6c"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e52bf98c7e727dd44f7c4acb980cb988448faeafed8433c867888268899b298b"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a2ecaa3c23e7a7cf86d00eda3390c232f4d533cd9ddea4b04f5d0644faf642c5"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:08d4c92bf480e19fc3f2717c9ce2aa31dceaa9163839a311424b6862252c943e"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99d9a1eded738299ba8e106c6779ce5c3893cffa0e32e4485d680588adae6db8"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20be8b7f606df096e08b0b1b4a3c6f0515e8dac296881fe7461dfa0fb5ec817"}, + {file = "jiter-0.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d33f94615fcaf872f7fd8cd98ac3b429e435c77619777e8a449d9d27e01134d1"}, + {file = "jiter-0.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:317b25e98a35ffec5c67efe56a4e9970852632c810d35b34ecdd70cc0e47b3b6"}, + {file = "jiter-0.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fc9043259ee430ecd71d178fccabd8c332a3bf1e81e50cae43cc2b28d19e4cb7"}, + {file = "jiter-0.8.2-cp38-cp38-win32.whl", hash = "sha256:fc5adda618205bd4678b146612ce44c3cbfdee9697951f2c0ffdef1f26d72b63"}, + {file = "jiter-0.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cd646c827b4f85ef4a78e4e58f4f5854fae0caf3db91b59f0d73731448a970c6"}, + {file = "jiter-0.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e41e75344acef3fc59ba4765df29f107f309ca9e8eace5baacabd9217e52a5ee"}, + {file = "jiter-0.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f22b16b35d5c1df9dfd58843ab2cd25e6bf15191f5a236bed177afade507bfc"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7200b8f7619d36aa51c803fd52020a2dfbea36ffec1b5e22cab11fd34d95a6d"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70bf4c43652cc294040dbb62256c83c8718370c8b93dd93d934b9a7bf6c4f53c"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9d471356dc16f84ed48768b8ee79f29514295c7295cb41e1133ec0b2b8d637d"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:859e8eb3507894093d01929e12e267f83b1d5f6221099d3ec976f0c995cb6bd9"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaa58399c01db555346647a907b4ef6d4f584b123943be6ed5588c3f2359c9f4"}, + {file = "jiter-0.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f2d5ed877f089862f4c7aacf3a542627c1496f972a34d0474ce85ee7d939c27"}, + {file = "jiter-0.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:03c9df035d4f8d647f8c210ddc2ae0728387275340668fb30d2421e17d9a0841"}, + {file = "jiter-0.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8bd2a824d08d8977bb2794ea2682f898ad3d8837932e3a74937e93d62ecbb637"}, + {file = "jiter-0.8.2-cp39-cp39-win32.whl", hash = "sha256:ca29b6371ebc40e496995c94b988a101b9fbbed48a51190a4461fcb0a68b4a36"}, + {file = "jiter-0.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:1c0dfbd1be3cbefc7510102370d86e35d1d53e5a93d48519688b1bf0f761160a"}, + {file = "jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d"}, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + +[[package]] +name = "joblib" +version = "1.4.2" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, + {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "3.0.0" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, +] + +[[package]] +name = "langchain" +version = "0.3.2" +description = "Building applications with LLMs through composability" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "langchain-0.3.2-py3-none-any.whl", hash = "sha256:cf005dcba132e46fb5e8d3dfaf7f8751bffd2d73e738c36be58f41edc7e3a4b8"}, + {file = "langchain-0.3.2.tar.gz", hash = "sha256:dc330e6eb10d81d23ba0305d18358702c73cc59e95c410eca6c6779aab4ddc9b"}, +] + +[package.dependencies] +aiohttp = ">=3.8.3,<4.0.0" +langchain-core = ">=0.3.8,<0.4.0" +langchain-text-splitters = ">=0.3.0,<0.4.0" +langsmith = ">=0.1.17,<0.2.0" +numpy = {version = ">=1.26.0,<2.0.0", markers = "python_version >= \"3.12\""} +pydantic = ">=2.7.4,<3.0.0" +PyYAML = ">=5.3" +requests = ">=2,<3" +SQLAlchemy = ">=1.4,<3" +tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0" + +[[package]] +name = "langchain-cerebras" +version = "0.5.0" +description = "An integration package connecting Cerebras and LangChain" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "langchain_cerebras-0.5.0-py3-none-any.whl", hash = "sha256:f129df699d6eb71d8e2602d7abe9ae75c289168b2e52d33cdefe5d4982e027b9"}, + {file = "langchain_cerebras-0.5.0.tar.gz", hash = "sha256:24cfe76358cd8c779f33c77708df08cc5fec81ed13797830396be0f52abdd80a"}, +] + +[package.dependencies] +langchain-core = ">=0.3.29,<0.4.0" +langchain-openai = ">=0.3.0,<0.4.0" + +[[package]] +name = "langchain-community" +version = "0.3.1" +description = "Community contributed LangChain integrations." +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "langchain_community-0.3.1-py3-none-any.whl", hash = "sha256:627eb26c16417764762ac47dd0d3005109f750f40242a88bb8f2958b798bcf90"}, + {file = "langchain_community-0.3.1.tar.gz", hash = "sha256:c964a70628f266a61647e58f2f0434db633d4287a729f100a81dd8b0654aec93"}, +] + +[package.dependencies] +aiohttp = ">=3.8.3,<4.0.0" +dataclasses-json = ">=0.5.7,<0.7" +langchain = ">=0.3.1,<0.4.0" +langchain-core = ">=0.3.6,<0.4.0" +langsmith = ">=0.1.125,<0.2.0" +numpy = {version = ">=1.26.0,<2.0.0", markers = "python_version >= \"3.12\""} +pydantic-settings = ">=2.4.0,<3.0.0" +PyYAML = ">=5.3" +requests = ">=2,<3" +SQLAlchemy = ">=1.4,<3" +tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0" + +[[package]] +name = "langchain-core" +version = "0.3.31" +description = "Building applications with LLMs through composability" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "langchain_core-0.3.31-py3-none-any.whl", hash = "sha256:882e64ad95887c951dce8e835889e43263b11848c394af3b73e06912624bd743"}, + {file = "langchain_core-0.3.31.tar.gz", hash = "sha256:5ffa56354c07de9efaa4139609659c63e7d9b29da2c825f6bab9392ec98300df"}, +] + +[package.dependencies] +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.1.125,<0.4" +packaging = ">=23.2,<25" +pydantic = [ + {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""}, + {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, +] +PyYAML = ">=5.3" +tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" +typing-extensions = ">=4.7" + +[[package]] +name = "langchain-ollama" +version = "0.2.2" +description = "An integration package connecting Ollama and LangChain" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "langchain_ollama-0.2.2-py3-none-any.whl", hash = "sha256:8a1ee72dbb6ea3b3ace1d9dd317e472d667a8ed491328550da59f4893a6796f8"}, + {file = "langchain_ollama-0.2.2.tar.gz", hash = "sha256:2d9bcb06ffdbe43c7c6906c46e710d36d33b6b99cd4975cbf54060f13e51c875"}, +] + +[package.dependencies] +langchain-core = ">=0.3.27,<0.4.0" +ollama = ">=0.4.4,<1" + +[[package]] +name = "langchain-openai" +version = "0.3.2" +description = "An integration package connecting OpenAI and LangChain" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "langchain_openai-0.3.2-py3-none-any.whl", hash = "sha256:8674183805e26d3ae3f78cc44f79fe0b2066f61e2de0e7e18be3b86f0d3b2759"}, + {file = "langchain_openai-0.3.2.tar.gz", hash = "sha256:c2c80ac0208eb7cefdef96f6353b00fa217979ffe83f0a21cc8666001df828c1"}, +] + +[package.dependencies] +langchain-core = ">=0.3.31,<0.4.0" +openai = ">=1.58.1,<2.0.0" +tiktoken = ">=0.7,<1" + +[[package]] +name = "langchain-text-splitters" +version = "0.3.0" +description = "LangChain text splitting utilities" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "langchain_text_splitters-0.3.0-py3-none-any.whl", hash = "sha256:e84243e45eaff16e5b776cd9c81b6d07c55c010ebcb1965deb3d1792b7358e83"}, + {file = "langchain_text_splitters-0.3.0.tar.gz", hash = "sha256:f9fe0b4d244db1d6de211e7343d4abc4aa90295aa22e1f0c89e51f33c55cd7ce"}, +] + +[package.dependencies] +langchain-core = ">=0.3.0,<0.4.0" + +[[package]] +name = "langchain-together" +version = "0.3.0" +description = "An integration package connecting Together AI and LangChain" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "langchain_together-0.3.0-py3-none-any.whl", hash = "sha256:4dcb4f6858c910c23d2268da1ed5f54e8cd01224ecf086dc7a8adbacdc6cb686"}, + {file = "langchain_together-0.3.0.tar.gz", hash = "sha256:c8a96377e49c065526435f766c6e1c7da3f7d054361326f079de8bd368ea76f2"}, +] + +[package.dependencies] +aiohttp = ">=3.9.1,<4.0.0" +langchain-core = ">=0.3.29,<0.4.0" +langchain-openai = ">=0.3,<0.4" +requests = ">=2,<3" + +[[package]] +name = "langsmith" +version = "0.1.147" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +optional = false +python-versions = "<4.0,>=3.8.1" +files = [ + {file = "langsmith-0.1.147-py3-none-any.whl", hash = "sha256:7166fc23b965ccf839d64945a78e9f1157757add228b086141eb03a60d699a15"}, + {file = "langsmith-0.1.147.tar.gz", hash = "sha256:2e933220318a4e73034657103b3b1a3a6109cc5db3566a7e8e03be8d6d7def7a"}, +] + +[package.dependencies] +httpx = ">=0.23.0,<1" +orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""} +pydantic = [ + {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, + {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, +] +requests = ">=2,<3" +requests-toolbelt = ">=1.0.0,<2.0.0" + +[package.extras] +langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] + +[[package]] +name = "lazy-object-proxy" +version = "1.10.0" +description = "A fast and thorough lazy object proxy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, + {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, +] + +[[package]] +name = "llama-cpp-python" +version = "0.2.90" +description = "Python bindings for the llama.cpp library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "llama_cpp_python-0.2.90.tar.gz", hash = "sha256:419b041c62dbdb9f7e67883a6ef2f247d583d08417058776be0bff05b4ec9e3d"}, +] + +[package.dependencies] +diskcache = ">=5.6.1" +jinja2 = ">=2.11.3" +numpy = ">=1.20.0" +typing-extensions = ">=4.5.0" + +[package.extras] +all = ["llama_cpp_python[dev,server,test]"] +dev = ["black (>=23.3.0)", "httpx (>=0.24.1)", "mkdocs (>=1.4.3)", "mkdocs-material (>=9.1.18)", "mkdocstrings[python] (>=0.22.0)", "pytest (>=7.4.0)", "twine (>=4.0.2)"] +server = ["PyYAML (>=5.1)", "fastapi (>=0.100.0)", "pydantic-settings (>=2.0.1)", "sse-starlette (>=1.6.1)", "starlette-context (>=0.3.6,<0.4)", "uvicorn (>=0.22.0)"] +test = ["fastapi (>=0.100.0)", "httpx (>=0.24.1)", "pydantic-settings (>=2.0.1)", "pytest (>=7.4.0)", "scipy (>=1.10)", "sse-starlette (>=1.6.1)", "starlette-context (>=0.3.6,<0.4)"] + +[[package]] +name = "mako" +version = "1.3.8" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"}, + {file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "marshmallow" +version = "3.25.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.9" +files = [ + {file = "marshmallow-3.25.1-py3-none-any.whl", hash = "sha256:ec5d00d873ce473b7f2ffcb7104286a376c354cab0c2fa12f5573dab03e87210"}, + {file = "marshmallow-3.25.1.tar.gz", hash = "sha256:f4debda3bb11153d81ac34b0d582bf23053055ee11e791b54b4b35493468040a"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<5.0)", "tox"] +docs = ["autodocsumm (==0.2.14)", "furo (==2024.8.6)", "sphinx (==8.1.3)", "sphinx-copybutton (==0.5.2)", "sphinx-issues (==5.0.0)", "sphinxext-opengraph (==0.9.1)"] +tests = ["pytest", "simplejson"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "multidict" +version = "6.1.0" +description = "multidict implementation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, + {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, + {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, + {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, + {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, + {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, + {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, + {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, + {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, + {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, + {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, + {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, + {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, + {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, + {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, +] + +[[package]] +name = "mypy" +version = "1.14.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, + {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, + {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, + {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, + {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, + {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, + {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, + {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, + {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, + {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, + {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, + {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, + {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, + {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, + {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, + {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "ollama" +version = "0.4.6" +description = "The official Python client for Ollama." +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "ollama-0.4.6-py3-none-any.whl", hash = "sha256:cbb4ebe009e10dd12bdd82508ab415fd131945e185753d728a7747c9ebe762e9"}, + {file = "ollama-0.4.6.tar.gz", hash = "sha256:b00717651c829f96094ed4231b9f0d87e33cc92dc235aca50aeb5a2a4e6e95b7"}, +] + +[package.dependencies] +httpx = ">=0.27.0,<0.28.0" +pydantic = ">=2.9.0,<3.0.0" + +[[package]] +name = "openai" +version = "1.61.1" +description = "The official Python library for the openai API" +optional = false +python-versions = ">=3.8" +files = [ + {file = "openai-1.61.1-py3-none-any.whl", hash = "sha256:72b0826240ce26026ac2cd17951691f046e5be82ad122d20a8e1b30ca18bd11e"}, + {file = "openai-1.61.1.tar.gz", hash = "sha256:ce1851507218209961f89f3520e06726c0aa7d0512386f0f977e3ac3e4f2472e"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +jiter = ">=0.4.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +tqdm = ">4" +typing-extensions = ">=4.11,<5" + +[package.extras] +datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] +realtime = ["websockets (>=13,<15)"] + +[[package]] +name = "orjson" +version = "3.10.15" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.8" +files = [ + {file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c2c79fa308e6edb0ffab0a31fd75a7841bf2a79a20ef08a3c6e3b26814c8ca8"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cb85490aa6bf98abd20607ab5c8324c0acb48d6da7863a51be48505646c814"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763dadac05e4e9d2bc14938a45a2d0560549561287d41c465d3c58aec818b164"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a330b9b4734f09a623f74a7490db713695e13b67c959713b78369f26b3dee6bf"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a61a4622b7ff861f019974f73d8165be1bd9a0855e1cad18ee167acacabeb061"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd271247691574416b3228db667b84775c497b245fa275c6ab90dc1ffbbd2b3"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4759b109c37f635aa5c5cc93a1b26927bfde24b254bcc0e1149a9fada253d2d"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e992fd5cfb8b9f00bfad2fd7a05a4299db2bbe92e6440d9dd2fab27655b3182"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f95fb363d79366af56c3f26b71df40b9a583b07bbaaf5b317407c4d58497852e"}, + {file = "orjson-3.10.15-cp310-cp310-win32.whl", hash = "sha256:f9875f5fea7492da8ec2444839dcc439b0ef298978f311103d0b7dfd775898ab"}, + {file = "orjson-3.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:17085a6aa91e1cd70ca8533989a18b5433e15d29c574582f76f821737c8d5806"}, + {file = "orjson-3.10.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c4cc83960ab79a4031f3119cc4b1a1c627a3dc09df125b27c4201dff2af7eaa6"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddbeef2481d895ab8be5185f2432c334d6dec1f5d1933a9c83014d188e102cef"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e590a0477b23ecd5b0ac865b1b907b01b3c5535f5e8a8f6ab0e503efb896334"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6be38bd103d2fd9bdfa31c2720b23b5d47c6796bcb1d1b598e3924441b4298d"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4f6edb1578960ed628a3b998fa54d78d9bb3e2eb2cfc5c2a09732431c678d0"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0482b21d0462eddd67e7fce10b89e0b6ac56570424662b685a0d6fccf581e13"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bb5cc3527036ae3d98b65e37b7986a918955f85332c1ee07f9d3f82f3a6899b5"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d569c1c462912acdd119ccbf719cf7102ea2c67dd03b99edcb1a3048651ac96b"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1e6d33efab6b71d67f22bf2962895d3dc6f82a6273a965fab762e64fa90dc399"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c33be3795e299f565681d69852ac8c1bc5c84863c0b0030b2b3468843be90388"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eea80037b9fae5339b214f59308ef0589fc06dc870578b7cce6d71eb2096764c"}, + {file = "orjson-3.10.15-cp311-cp311-win32.whl", hash = "sha256:d5ac11b659fd798228a7adba3e37c010e0152b78b1982897020a8e019a94882e"}, + {file = "orjson-3.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:cf45e0214c593660339ef63e875f32ddd5aa3b4adc15e662cdb80dc49e194f8e"}, + {file = "orjson-3.10.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d11c0714fc85bfcf36ada1179400862da3288fc785c30e8297844c867d7505a"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba5a1e85d554e3897fa9fe6fbcff2ed32d55008973ec9a2b992bd9a65d2352d"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7723ad949a0ea502df656948ddd8b392780a5beaa4c3b5f97e525191b102fff0"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fd9bc64421e9fe9bd88039e7ce8e58d4fead67ca88e3a4014b143cec7684fd4"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dadba0e7b6594216c214ef7894c4bd5f08d7c0135f4dd0145600be4fbcc16767"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48f59114fe318f33bbaee8ebeda696d8ccc94c9e90bc27dbe72153094e26f41"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:035fb83585e0f15e076759b6fedaf0abb460d1765b6a36f48018a52858443514"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d13b7fe322d75bf84464b075eafd8e7dd9eae05649aa2a5354cfa32f43c59f17"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7066b74f9f259849629e0d04db6609db4cf5b973248f455ba5d3bd58a4daaa5b"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88dc3f65a026bd3175eb157fea994fca6ac7c4c8579fc5a86fc2114ad05705b7"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b342567e5465bd99faa559507fe45e33fc76b9fb868a63f1642c6bc0735ad02a"}, + {file = "orjson-3.10.15-cp312-cp312-win32.whl", hash = "sha256:0a4f27ea5617828e6b58922fdbec67b0aa4bb844e2d363b9244c47fa2180e665"}, + {file = "orjson-3.10.15-cp312-cp312-win_amd64.whl", hash = "sha256:ef5b87e7aa9545ddadd2309efe6824bd3dd64ac101c15dae0f2f597911d46eaa"}, + {file = "orjson-3.10.15-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bae0e6ec2b7ba6895198cd981b7cca95d1487d0147c8ed751e5632ad16f031a6"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93ce145b2db1252dd86af37d4165b6faa83072b46e3995ecc95d4b2301b725a"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c203f6f969210128af3acae0ef9ea6aab9782939f45f6fe02d05958fe761ef9"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8918719572d662e18b8af66aef699d8c21072e54b6c82a3f8f6404c1f5ccd5e0"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f71eae9651465dff70aa80db92586ad5b92df46a9373ee55252109bb6b703307"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e117eb299a35f2634e25ed120c37c641398826c2f5a3d3cc39f5993b96171b9e"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13242f12d295e83c2955756a574ddd6741c81e5b99f2bef8ed8d53e47a01e4b7"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7946922ada8f3e0b7b958cc3eb22cfcf6c0df83d1fe5521b4a100103e3fa84c8"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b7155eb1623347f0f22c38c9abdd738b287e39b9982e1da227503387b81b34ca"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:208beedfa807c922da4e81061dafa9c8489c6328934ca2a562efa707e049e561"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eca81f83b1b8c07449e1d6ff7074e82e3fd6777e588f1a6632127f286a968825"}, + {file = "orjson-3.10.15-cp313-cp313-win32.whl", hash = "sha256:c03cd6eea1bd3b949d0d007c8d57049aa2b39bd49f58b4b2af571a5d3833d890"}, + {file = "orjson-3.10.15-cp313-cp313-win_amd64.whl", hash = "sha256:fd56a26a04f6ba5fb2045b0acc487a63162a958ed837648c5781e1fe3316cfbf"}, + {file = "orjson-3.10.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e8afd6200e12771467a1a44e5ad780614b86abb4b11862ec54861a82d677746"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9a18c500f19273e9e104cca8c1f0b40a6470bcccfc33afcc088045d0bf5ea6"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb00b7bfbdf5d34a13180e4805d76b4567025da19a197645ca746fc2fb536586"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33aedc3d903378e257047fee506f11e0833146ca3e57a1a1fb0ddb789876c1e1"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0099ae6aed5eb1fc84c9eb72b95505a3df4267e6962eb93cdd5af03be71c98"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c864a80a2d467d7786274fce0e4f93ef2a7ca4ff31f7fc5634225aaa4e9e98c"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c25774c9e88a3e0013d7d1a6c8056926b607a61edd423b50eb5c88fd7f2823ae"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e78c211d0074e783d824ce7bb85bf459f93a233eb67a5b5003498232ddfb0e8a"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:43e17289ffdbbac8f39243916c893d2ae41a2ea1a9cbb060a56a4d75286351ae"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:781d54657063f361e89714293c095f506c533582ee40a426cb6489c48a637b81"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6875210307d36c94873f553786a808af2788e362bd0cf4c8e66d976791e7b528"}, + {file = "orjson-3.10.15-cp38-cp38-win32.whl", hash = "sha256:305b38b2b8f8083cc3d618927d7f424349afce5975b316d33075ef0f73576b60"}, + {file = "orjson-3.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:5dd9ef1639878cc3efffed349543cbf9372bdbd79f478615a1c633fe4e4180d1"}, + {file = "orjson-3.10.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ffe19f3e8d68111e8644d4f4e267a069ca427926855582ff01fc012496d19969"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d433bf32a363823863a96561a555227c18a522a8217a6f9400f00ddc70139ae2"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da03392674f59a95d03fa5fb9fe3a160b0511ad84b7a3914699ea5a1b3a38da2"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a63bb41559b05360ded9132032239e47983a39b151af1201f07ec9370715c82"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3766ac4702f8f795ff3fa067968e806b4344af257011858cc3d6d8721588b53f"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1c73dcc8fadbd7c55802d9aa093b36878d34a3b3222c41052ce6b0fc65f8e8"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b299383825eafe642cbab34be762ccff9fd3408d72726a6b2a4506d410a71ab3"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:abc7abecdbf67a173ef1316036ebbf54ce400ef2300b4e26a7b843bd446c2480"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3614ea508d522a621384c1d6639016a5a2e4f027f3e4a1c93a51867615d28829"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:295c70f9dc154307777ba30fe29ff15c1bcc9dfc5c48632f37d20a607e9ba85a"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:63309e3ff924c62404923c80b9e2048c1f74ba4b615e7584584389ada50ed428"}, + {file = "orjson-3.10.15-cp39-cp39-win32.whl", hash = "sha256:a2f708c62d026fb5340788ba94a55c23df4e1869fec74be455e0b2f5363b8507"}, + {file = "orjson-3.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:efcf6c735c3d22ef60c4aa27a5238f1a477df85e9b15f2142f9d669beb2d13fd"}, + {file = "orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e"}, +] + +[[package]] +name = "outcome" +version = "1.3.0.post0" +description = "Capture the outcome of Python function calls." +optional = false +python-versions = ">=3.7" +files = [ + {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, + {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, +] + +[package.dependencies] +attrs = ">=19.2.0" + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "parsimonious" +version = "0.10.0" +description = "(Soon to be) the fastest pure-Python PEG parser I could muster" +optional = false +python-versions = "*" +files = [ + {file = "parsimonious-0.10.0-py3-none-any.whl", hash = "sha256:982ab435fabe86519b57f6b35610aa4e4e977e9f02a14353edf4bbc75369fc0f"}, + {file = "parsimonious-0.10.0.tar.gz", hash = "sha256:8281600da180ec8ae35427a4ab4f7b82bfec1e3d1e52f80cb60ea82b9512501c"}, +] + +[package.dependencies] +regex = ">=2022.3.15" + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pillow" +version = "11.1.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"}, + {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269"}, + {file = "pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49"}, + {file = "pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a"}, + {file = "pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96"}, + {file = "pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f"}, + {file = "pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"}, + {file = "pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c"}, + {file = "pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6"}, + {file = "pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf"}, + {file = "pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3"}, + {file = "pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9"}, + {file = "pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c"}, + {file = "pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547"}, + {file = "pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab"}, + {file = "pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9"}, + {file = "pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe"}, + {file = "pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8"}, + {file = "pillow-11.1.0-cp39-cp39-win32.whl", hash = "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5"}, + {file = "pillow-11.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f"}, + {file = "pillow-11.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0"}, + {file = "pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + +[[package]] +name = "platformdirs" +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "psycopg2-binary" +version = "2.9.10" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5"}, +] + +[[package]] +name = "py-sr25519-bindings" +version = "0.2.0" +description = "Python bindings for sr25519 library" +optional = false +python-versions = "*" +files = [ + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:86cc1a571852a4f2ade827ebf211e066b23ab805d3e864cbe213a3d8cd53f7d5"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:453c9088e39dd04b07bf3ada6c473a5349c4dfd965009a35124b2c807117eda8"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f12122a18b688e4a4bf0e74d3969d9e3f6f83d2b01fe88ab5f19c969e95192a2"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2815ecc958f6edbad79fee76899bd33b8950caa7106c1db08c828ec90e16fa7"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfe52e73d7f0237820f7a935397d5004733a1d890464701f2c3c71be6033c442"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_28_armv7l.whl", hash = "sha256:df7e2fad636831919bfa479cd4b6fffdd429cde778da72b1834c1434dadaf982"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f4ebeb2aac26a39160f2fad8ffc40ff98da835af57618c0446637bf182b9c927"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:942a6b52e871d6e152dda80a60ed338dccedc69b6375e080e496bf886f2556c0"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b24307c34a06209d0e34ca15ab4c0275617538dfdac1eac8aa25e792fa9f4108"}, + {file = "py_sr25519_bindings-0.2.0-cp310-none-win32.whl", hash = "sha256:2e06a2d1119a2ad063f11448bb27ec4f4ba77416043d98ae28ef30624cf0e12d"}, + {file = "py_sr25519_bindings-0.2.0-cp310-none-win_amd64.whl", hash = "sha256:16b36d9fe8bda873ab8376f3a4d0894b8d4ab2d702665afc3ab3ca69f0dc9495"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:54e8c41081a4c23eca4b19f52de2514c48ddec6f49844dff7ad4cfac0bc11712"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6c73bd1a87849db9cd0e664b2d2e14208183dd8d11ac083d70e688fc28283a71"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47d21382ea24f7f25e72cdddaca2f013ce46cc7983bcfebc611c795cea177eff"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1448cf55bbf6f52d2e24766a8a84ba6d77100a991897e8519711ccd7409830"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:392b8b9875c89c9302930ad3d59567b62176f33adeee96a55ff61ba17fb7aac2"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7b56b5cbbfb36b41ddfa462989a03386590ac036f3a755ef64fffeb2fed88654"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8f06ea3237e06666e3a4ff4719b4fba415472943831b229428753c37d5ecd1b4"}, + {file = "py_sr25519_bindings-0.2.0-cp311-none-win_amd64.whl", hash = "sha256:d62af30b2022f5fa787e46c06823c35a21abe791bf55012f498f9ba8e4baabc8"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ceafa0c31b49f2128461eb2c6ea18dc5d0bfae7218a100be7153f271e46bac49"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c8dedb8525556591738a64310875df70ea67886e5a40f2799bd96ef8848936cf"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ce149796923696f5cfc6263f135674a14fe2d513fd35b2bfa73226b940aff648"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d71ca3ba22f98f4c208d509f735fe4eb5aa9e3547a507733a95828adde6cab"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8e20ee0856e8a60682566df955b81e7631670136607da627ab6892df34790d"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0efd5487e3f6d6053cfc4a891b10f729d69263897270d0354f409ee2106fc9b7"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ae7f2164d510458740145d20912d5d7a5c45e8fcde7cebd4057f60811ecc276f"}, + {file = "py_sr25519_bindings-0.2.0-cp312-none-win32.whl", hash = "sha256:92382456c6f176c07e0d554c71d483853387885ce17714f8a4b50fdcf7552297"}, + {file = "py_sr25519_bindings-0.2.0-cp312-none-win_amd64.whl", hash = "sha256:48ee4e14a77f815f3996beecb7d7abf422b756e9163ee4df739c1aded8a3e8ba"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-macosx_10_7_x86_64.whl", hash = "sha256:c3de899a1e911b8945f09e6389f8d2df68924c12c78e3e66fedb15f1e4ff56ad"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:758761b605f90e4238304df7520155a3358b13cc55ee18c5113632da17343163"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f63580a224607e68b861eb03421465091c3104b6309e5fca7448f5aa6dbda60"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b999075b76cae8e84d5f19f2c8f28d3f24c93ba858ad49e58bcf22afe0406b"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_28_armv7l.whl", hash = "sha256:5102c94e97d316009ad4482f24d9a933fc0b7eb0bb88e6a784a820cd1bd25827"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b477b18940f472d4e25e141f19503a6e55aadff31b4822228a491c9638096baf"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:7e69bf7bdc9920013c1a2bea25a8b02df9588d9856cb20270f4d8d95b8e83f52"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:dc436a34e17037833c3909062722ee3d46e28288972c87f619d163d00054d68e"}, + {file = "py_sr25519_bindings-0.2.0-cp36-none-win32.whl", hash = "sha256:fc27c847dd4df727388aaadc3870aeb472f2d5c35717536d319792fe08f6120a"}, + {file = "py_sr25519_bindings-0.2.0-cp36-none-win_amd64.whl", hash = "sha256:0441381c2a6f532831d560a1f2ae8a917c7190cf27f5428d9b0528fa28a72e2d"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:e1471134450e6189b7e38d245ab16b06f3de900b6d07aa66b1e6973cdbc00d01"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:302bd20e75d900d98e7961934b03958e8acc8784eed594ab48f9bb298396c734"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e09ac91f4b2e2b9c50e268f6ee292d9fa447c5b7cc6327cfeae7750d716f49e"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_28_armv7l.whl", hash = "sha256:28b904739147c4f797627bd3b44d1e64d061533253abd1882c6d3b8944e7bbd8"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0931ac85331aae33bef67460a3cce554ef5c1f7dfec0ebe2f5b9ea57c5bee65c"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd8da64f9e42ff973b394ed9164f1e9a454279a058eed08ac8d006fcbd61093b"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:297ad50e3cace5c89dbf5bd916b714aac3ebe6bc76f85382dac228cbeb71449e"}, + {file = "py_sr25519_bindings-0.2.0-cp37-none-win32.whl", hash = "sha256:422d62ca74ebe5065eca88607552b9a5f1dc4abff0c597cc3793dd8adfb8c4ea"}, + {file = "py_sr25519_bindings-0.2.0-cp37-none-win_amd64.whl", hash = "sha256:d1b0ed9a4dded60f671f34fdd81c974dad159e98f43bcab21833f984e05920f9"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:37f11ffee535c624bf5ddc6109c2cdca9a2dbd10f7d310bcd1dd97f6121c532f"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:4e1b553a6b1cc1b0aa9da2d7157329713cc7f299acb12a052d326f9b594b145c"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f0b6dcf1328027dba1f9236bd3432cc3cce1de55a12c1a3a4ea7a8dc3ab3e857"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:458c7e6d7447bd267a6f870a8801e995d710952566a0a52634f408bf804cf27a"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d64253d7d08fd6073e7b79bba9cff78687e76698cc210d3c6f236b90766b9421"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_28_armv7l.whl", hash = "sha256:a9aac20a196416b8daf764704a9cee71ddee16bc705d12b5c6bcb6f51e81ac6e"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e162687189cf765f602178aa195a2be4284107622141ff746e92e14e266cf3b7"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d44ab4d150c9bdd1641ccad49942ecf2d0ef61bd66a7da41094bb4a9cbaca529"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:251ff9cef5dafd96ff241b77ff471912c40249b6df31e71c6c32de6a26a8dbc6"}, + {file = "py_sr25519_bindings-0.2.0-cp38-none-win32.whl", hash = "sha256:ca9794f9d4fc37cdc8cbb6724d5432a064d22c26ecde312928154b6bc691f4d3"}, + {file = "py_sr25519_bindings-0.2.0-cp38-none-win_amd64.whl", hash = "sha256:6406cb0aeb5cbb8cfaa37d59d15d7640c0d812a1cbb55657bee52fd3d9e92aa9"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:b9da73447c8f5b8392a8229c2b65d742709c6aa2d0c6b32e39b635fb245145f1"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:7f00236a802d6d3f3705713d5352ba968c0ce353a20519c445e66ce19869bfdc"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d473199c0dbad846b0723c6663b1b6a04040ccdca700cb1609acac3e621f2087"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44bede0dd42f75cf849d3ccb4e443d6425218035bc00a6330b11dc2cc1146f3b"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_28_armv7l.whl", hash = "sha256:a8e462d2442726d9db07854dc2eb640b1a8a548948b1ff3aa580771ab739bab8"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:55b1f67fdaeab91481fda54432dffdf87ed516d26461d31e70911c7ea55d6164"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ec11493d59075ba75fe0bc0312d502ffdc45b641a46fb084bf8b04906597688b"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:101ee46368da149ad332aea225d4ff2907dffce574e8f8f7fe56f5c29211f333"}, + {file = "py_sr25519_bindings-0.2.0-cp39-none-win32.whl", hash = "sha256:909f13f63f67f1e5595d4d495cf8a3c95e392626c08f94550cbf8f0e8ea1c743"}, + {file = "py_sr25519_bindings-0.2.0-cp39-none-win_amd64.whl", hash = "sha256:b3f86e4aad6c2b8ff74af76f38fde7fbaf9dd83bc4a7c259709092008c3b8e5d"}, + {file = "py_sr25519_bindings-0.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38db0ee90bd676b9df7ddd03fcb2113b5a5e9d9c984d82426728acc0e9d54277"}, + {file = "py_sr25519_bindings-0.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dfe767069d5c5e8a313e77b6bd681ea4f6b5988b09b6b4c9399e255fe4a7c53"}, + {file = "py_sr25519_bindings-0.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8951d1a6e310a682a6253d547e44a9e7a606476dbc18dea3f121d98bdb81042"}, + {file = "py_sr25519_bindings-0.2.0.tar.gz", hash = "sha256:0c2fe92b7cdcebf6c5611a90054f8ba6ea90b68b8832896d2dc565537bc40b0c"}, +] + +[[package]] +name = "pyarrow" +version = "19.0.1" +description = "Python library for Apache Arrow" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pyarrow-19.0.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:fc28912a2dc924dddc2087679cc8b7263accc71b9ff025a1362b004711661a69"}, + {file = "pyarrow-19.0.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:fca15aabbe9b8355800d923cc2e82c8ef514af321e18b437c3d782aa884eaeec"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad76aef7f5f7e4a757fddcdcf010a8290958f09e3470ea458c80d26f4316ae89"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d03c9d6f2a3dffbd62671ca070f13fc527bb1867b4ec2b98c7eeed381d4f389a"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:65cf9feebab489b19cdfcfe4aa82f62147218558d8d3f0fc1e9dea0ab8e7905a"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:41f9706fbe505e0abc10e84bf3a906a1338905cbbcf1177b71486b03e6ea6608"}, + {file = "pyarrow-19.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6cb2335a411b713fdf1e82a752162f72d4a7b5dbc588e32aa18383318b05866"}, + {file = "pyarrow-19.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:cc55d71898ea30dc95900297d191377caba257612f384207fe9f8293b5850f90"}, + {file = "pyarrow-19.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:7a544ec12de66769612b2d6988c36adc96fb9767ecc8ee0a4d270b10b1c51e00"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0148bb4fc158bfbc3d6dfe5001d93ebeed253793fff4435167f6ce1dc4bddeae"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f24faab6ed18f216a37870d8c5623f9c044566d75ec586ef884e13a02a9d62c5"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4982f8e2b7afd6dae8608d70ba5bd91699077323f812a0448d8b7abdff6cb5d3"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:49a3aecb62c1be1d822f8bf629226d4a96418228a42f5b40835c1f10d42e4db6"}, + {file = "pyarrow-19.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:008a4009efdb4ea3d2e18f05cd31f9d43c388aad29c636112c2966605ba33466"}, + {file = "pyarrow-19.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:80b2ad2b193e7d19e81008a96e313fbd53157945c7be9ac65f44f8937a55427b"}, + {file = "pyarrow-19.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:ee8dec072569f43835932a3b10c55973593abc00936c202707a4ad06af7cb294"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d5d1ec7ec5324b98887bdc006f4d2ce534e10e60f7ad995e7875ffa0ff9cb14"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ad4c0eb4e2a9aeb990af6c09e6fa0b195c8c0e7b272ecc8d4d2b6574809d34"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d383591f3dcbe545f6cc62daaef9c7cdfe0dff0fb9e1c8121101cabe9098cfa6"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b4c4156a625f1e35d6c0b2132635a237708944eb41df5fbe7d50f20d20c17832"}, + {file = "pyarrow-19.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:5bd1618ae5e5476b7654c7b55a6364ae87686d4724538c24185bbb2952679960"}, + {file = "pyarrow-19.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e45274b20e524ae5c39d7fc1ca2aa923aab494776d2d4b316b49ec7572ca324c"}, + {file = "pyarrow-19.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d9dedeaf19097a143ed6da37f04f4051aba353c95ef507764d344229b2b740ae"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ebfb5171bb5f4a52319344ebbbecc731af3f021e49318c74f33d520d31ae0c4"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a21d39fbdb948857f67eacb5bbaaf36802de044ec36fbef7a1c8f0dd3a4ab2"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:99bc1bec6d234359743b01e70d4310d0ab240c3d6b0da7e2a93663b0158616f6"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1b93ef2c93e77c442c979b0d596af45e4665d8b96da598db145b0fec014b9136"}, + {file = "pyarrow-19.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:d9d46e06846a41ba906ab25302cf0fd522f81aa2a85a71021826f34639ad31ef"}, + {file = "pyarrow-19.0.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c0fe3dbbf054a00d1f162fda94ce236a899ca01123a798c561ba307ca38af5f0"}, + {file = "pyarrow-19.0.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:96606c3ba57944d128e8a8399da4812f56c7f61de8c647e3470b417f795d0ef9"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f04d49a6b64cf24719c080b3c2029a3a5b16417fd5fd7c4041f94233af732f3"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a9137cf7e1640dce4c190551ee69d478f7121b5c6f323553b319cac936395f6"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7c1bca1897c28013db5e4c83944a2ab53231f541b9e0c3f4791206d0c0de389a"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:58d9397b2e273ef76264b45531e9d552d8ec8a6688b7390b5be44c02a37aade8"}, + {file = "pyarrow-19.0.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:b9766a47a9cb56fefe95cb27f535038b5a195707a08bf61b180e642324963b46"}, + {file = "pyarrow-19.0.1-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:6c5941c1aac89a6c2f2b16cd64fe76bcdb94b2b1e99ca6459de4e6f07638d755"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd44d66093a239358d07c42a91eebf5015aa54fccba959db899f932218ac9cc8"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:335d170e050bcc7da867a1ed8ffb8b44c57aaa6e0843b156a501298657b1e972"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:1c7556165bd38cf0cd992df2636f8bcdd2d4b26916c6b7e646101aff3c16f76f"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:699799f9c80bebcf1da0983ba86d7f289c5a2a5c04b945e2f2bcf7e874a91911"}, + {file = "pyarrow-19.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8464c9fbe6d94a7fe1599e7e8965f350fd233532868232ab2596a71586c5a429"}, + {file = "pyarrow-19.0.1.tar.gz", hash = "sha256:3bf266b485df66a400f282ac0b6d1b500b9d2ae73314a153dbe97d6d5cc8a99e"}, +] + +[package.extras] +test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] + +[[package]] +name = "pycodestyle" +version = "2.12.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pycryptodome" +version = "3.20.0" +description = "Cryptographic library for Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, + {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, +] + +[[package]] +name = "pydantic" +version = "2.10.6" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, + {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.27.2" +typing-extensions = ">=4.12.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-settings" +version = "2.7.1" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd"}, + {file = "pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93"}, +] + +[package.dependencies] +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" + +[package.extras] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + +[[package]] +name = "pygments" +version = "2.19.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyjwt" +version = "2.10.1" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pylint" +version = "2.17.7" +description = "python code static checker" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, + {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, +] + +[package.dependencies] +astroid = ">=2.15.8,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pymupdf" +version = "1.22.5" +description = "Python bindings for the PDF toolkit and renderer MuPDF" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyMuPDF-1.22.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:640b8e4cb116dd87a3c854e49808a4f63625e663a7bc5b1efc971db5b4775367"}, + {file = "PyMuPDF-1.22.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:17efbbf0e2d99d24cfc302fac512928eb294f10b7b67d597d04dafd012812e4e"}, + {file = "PyMuPDF-1.22.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc9b9bf0f2beea3911750d2d66247608be8cbad33b7a050cacec9e4c105a1ca"}, + {file = "PyMuPDF-1.22.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7734a32a91eea4b502b8f9d2915cdba0a372226e14fb983876d763110dcefef"}, + {file = "PyMuPDF-1.22.5-cp310-cp310-win32.whl", hash = "sha256:c2fd70ca9961f7871810dce1b7d0a42a69eb8ff2d786621123952bd505a6867e"}, + {file = "PyMuPDF-1.22.5-cp310-cp310-win_amd64.whl", hash = "sha256:add310c96df6933cfb4ce3821c9c7b5c133e8aa609a4c9416e1c7af546163488"}, + {file = "PyMuPDF-1.22.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:017aaba511526facfc928e9d95d2c10d28a2821b05b9039bf422031a7da8584e"}, + {file = "PyMuPDF-1.22.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6fe5e44a14864d921fb96669a82f9635846806176f77f1d73c61feb84ebf4d84"}, + {file = "PyMuPDF-1.22.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e74d766f79e41e10c51865233042ab2cc4612ca7942812dca0603f4d0f8f73d"}, + {file = "PyMuPDF-1.22.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8175452fcc99a0af6429d8acd87682a3a70c5879d73532c7327f71ce508a35"}, + {file = "PyMuPDF-1.22.5-cp311-cp311-win32.whl", hash = "sha256:42f59f4999d7f8b35c850050bd965e98c081a7d9b92d5f9dcf30203b30d06876"}, + {file = "PyMuPDF-1.22.5-cp311-cp311-win_amd64.whl", hash = "sha256:3d71c47aa14b73f2df7d03be8c547a05df6c6898d8c63a0f752b26f206eefd3c"}, + {file = "PyMuPDF-1.22.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4bcad7ea4b3ab82c46fe8da27ec738d38c213ed9935ef67d98ed09574d9a234e"}, + {file = "PyMuPDF-1.22.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b04a83ddcb3f7c935c75a1f7f6050c85fe4062a2ea64c47ee6bda788d037761"}, + {file = "PyMuPDF-1.22.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d02ee28663077f15d529b04d27588b174fa937daf73a294df279bbf70c468f5c"}, + {file = "PyMuPDF-1.22.5-cp37-cp37m-win32.whl", hash = "sha256:411fc35f6dae16ec940b6b0406e84be6ff29f93b30908ea1427e2a4bd594d4ba"}, + {file = "PyMuPDF-1.22.5-cp37-cp37m-win_amd64.whl", hash = "sha256:7c8c0f686865e330de90b93d53b100f7f07c2f10f5449ceb721121f459f7cc4a"}, + {file = "PyMuPDF-1.22.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:64ae9f81b8fe0a3e6386a24887a92736793479c5918ecac3b7deac2d02abf1f2"}, + {file = "PyMuPDF-1.22.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7562436dadf8382e59ac3739fbbf9d5b2d807fafc7f28cb884863430e0de6505"}, + {file = "PyMuPDF-1.22.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c22046e5f2cf0d72f9809a967340db1b238fefe58322896bc7c3f3d1d10b42"}, + {file = "PyMuPDF-1.22.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa601dc4116c17a6b09255b031b5a1891e3ac18b50ec536452a725a6b75db8d"}, + {file = "PyMuPDF-1.22.5-cp38-cp38-win32.whl", hash = "sha256:3d0fe749e648f5245059d5f771fb50c1a988a1d2e82268b56377b2176a9fee5d"}, + {file = "PyMuPDF-1.22.5-cp38-cp38-win_amd64.whl", hash = "sha256:4fbc5bfe6ecc53929e3fd0db9846fb7da084ddb4b1fc1063857245fa783974d9"}, + {file = "PyMuPDF-1.22.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:87b36e0797ab7fbb7ef594c7a6e0febc7ffb4101a42ea796726a8288391a3769"}, + {file = "PyMuPDF-1.22.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:01119edb7e4c3dd8c154d237b8ac927bd359eea8d31468f9a89aa308b5bca04e"}, + {file = "PyMuPDF-1.22.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fde02fcb387863873b56730f4b9f65515d87c92c12299f0f0a74b3ccdfe35062"}, + {file = "PyMuPDF-1.22.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30c55814bbf6461aef9b34cb524d1d14857d5ec6ccfbb78ecfb1d07dfc40eeb8"}, + {file = "PyMuPDF-1.22.5-cp39-cp39-win32.whl", hash = "sha256:0542178c3a399282903705a8cc298e7f33f4770605e0a9db344aff5d375bcf0b"}, + {file = "PyMuPDF-1.22.5-cp39-cp39-win_amd64.whl", hash = "sha256:f8ca46a6987e14f58ec8dfda2d2376bacd113c1fec5f58bebf90838bb4408ab9"}, + {file = "PyMuPDF-1.22.5.tar.gz", hash = "sha256:5ec8d5106752297529d0d68d46cfc4ce99914aabd99be843f1599a1842d63fe9"}, +] + +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + +[[package]] +name = "pyshorteners" +version = "1.0.1" +description = "A Python lib to wrap and consume the most used shorteners APIs" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyshorteners-1.0.1.tar.gz", hash = "sha256:22045c997c4283124768c165623a267cf9ba2a7d416101e2e5fccfc244d34544"}, +] + +[package.dependencies] +requests = "*" + +[package.extras] +dev = ["pre-commit"] +docs = ["sphinx"] + +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, + {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, + {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, +] + +[[package]] +name = "pytest" +version = "8.3.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.24.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-multipart" +version = "0.0.12" +description = "A streaming multipart parser for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python_multipart-0.0.12-py3-none-any.whl", hash = "sha256:43dcf96cf65888a9cd3423544dd0d75ac10f7aa0c3c28a175bbcd00c9ce1aebf"}, + {file = "python_multipart-0.0.12.tar.gz", hash = "sha256:045e1f98d719c1ce085ed7f7e1ef9d8ccc8c02ba02b5566d5f7521410ced58cb"}, +] + +[[package]] +name = "pytz" +version = "2024.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + +[[package]] +name = "pyunormalize" +version = "16.0.0" +description = "Unicode normalization forms (NFC, NFKC, NFD, NFKD). A library independent of the Python core Unicode database." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyunormalize-16.0.0-py3-none-any.whl", hash = "sha256:c647d95e5d1e2ea9a2f448d1d95d8518348df24eab5c3fd32d2b5c3300a49152"}, + {file = "pyunormalize-16.0.0.tar.gz", hash = "sha256:2e1dfbb4a118154ae26f70710426a52a364b926c9191f764601f5a8cb12761f7"}, +] + +[[package]] +name = "pywin32" +version = "308" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, + {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, + {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, + {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, + {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, + {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, + {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, + {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, + {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, + {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, + {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, + {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, + {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, + {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, + {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, + {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, + {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, + {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "regex" +version = "2024.9.11" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +files = [ + {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"}, + {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"}, + {file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"}, + {file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"}, + {file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"}, + {file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"}, + {file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"}, + {file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"}, + {file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"}, + {file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"}, + {file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"}, + {file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"}, + {file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"}, + {file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"}, + {file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"}, + {file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +description = "OAuthlib authentication support for Requests." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "rich" +version = "13.9.4" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rlp" +version = "4.0.1" +description = "rlp: A package for Recursive Length Prefix encoding and decoding" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "rlp-4.0.1-py3-none-any.whl", hash = "sha256:ff6846c3c27b97ee0492373aa074a7c3046aadd973320f4fffa7ac45564b0258"}, + {file = "rlp-4.0.1.tar.gz", hash = "sha256:bcefb11013dfadf8902642337923bd0c786dc8a27cb4c21da6e154e52869ecb1"}, +] + +[package.dependencies] +eth-utils = ">=2" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "hypothesis (==5.19.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +rust-backend = ["rusty-rlp (>=0.2.1)"] +test = ["hypothesis (==5.19.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "s3transfer" +version = "0.11.2" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">=3.8" +files = [ + {file = "s3transfer-0.11.2-py3-none-any.whl", hash = "sha256:be6ecb39fadd986ef1701097771f87e4d2f821f27f6071c872143884d2950fbc"}, + {file = "s3transfer-0.11.2.tar.gz", hash = "sha256:3b39185cb72f5acc77db1a58b6e25b977f28d20496b6e58d6813d75f464d632f"}, +] + +[package.dependencies] +botocore = ">=1.36.0,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.36.0,<2.0a.0)"] + +[[package]] +name = "scikit-learn" +version = "1.5.1" +description = "A set of python modules for machine learning and data mining" +optional = false +python-versions = ">=3.9" +files = [ + {file = "scikit_learn-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:781586c414f8cc58e71da4f3d7af311e0505a683e112f2f62919e3019abd3745"}, + {file = "scikit_learn-1.5.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5b213bc29cc30a89a3130393b0e39c847a15d769d6e59539cd86b75d276b1a7"}, + {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff4ba34c2abff5ec59c803ed1d97d61b036f659a17f55be102679e88f926fac"}, + {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:161808750c267b77b4a9603cf9c93579c7a74ba8486b1336034c2f1579546d21"}, + {file = "scikit_learn-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:10e49170691514a94bb2e03787aa921b82dbc507a4ea1f20fd95557862c98dc1"}, + {file = "scikit_learn-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:154297ee43c0b83af12464adeab378dee2d0a700ccd03979e2b821e7dd7cc1c2"}, + {file = "scikit_learn-1.5.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b5e865e9bd59396220de49cb4a57b17016256637c61b4c5cc81aaf16bc123bbe"}, + {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909144d50f367a513cee6090873ae582dba019cb3fca063b38054fa42704c3a4"}, + {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689b6f74b2c880276e365fe84fe4f1befd6a774f016339c65655eaff12e10cbf"}, + {file = "scikit_learn-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a07f90846313a7639af6a019d849ff72baadfa4c74c778821ae0fad07b7275b"}, + {file = "scikit_learn-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5944ce1faada31c55fb2ba20a5346b88e36811aab504ccafb9f0339e9f780395"}, + {file = "scikit_learn-1.5.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0828673c5b520e879f2af6a9e99eee0eefea69a2188be1ca68a6121b809055c1"}, + {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508907e5f81390e16d754e8815f7497e52139162fd69c4fdbd2dfa5d6cc88915"}, + {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97625f217c5c0c5d0505fa2af28ae424bd37949bb2f16ace3ff5f2f81fb4498b"}, + {file = "scikit_learn-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:da3f404e9e284d2b0a157e1b56b6566a34eb2798205cba35a211df3296ab7a74"}, + {file = "scikit_learn-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88e0672c7ac21eb149d409c74cc29f1d611d5158175846e7a9c2427bd12b3956"}, + {file = "scikit_learn-1.5.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:7b073a27797a283187a4ef4ee149959defc350b46cbf63a84d8514fe16b69855"}, + {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b59e3e62d2be870e5c74af4e793293753565c7383ae82943b83383fdcf5cc5c1"}, + {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd8d3a19d4bd6dc5a7d4f358c8c3a60934dc058f363c34c0ac1e9e12a31421d"}, + {file = "scikit_learn-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:5f57428de0c900a98389c4a433d4a3cf89de979b3aa24d1c1d251802aa15e44d"}, + {file = "scikit_learn-1.5.1.tar.gz", hash = "sha256:0ea5d40c0e3951df445721927448755d3fe1d80833b0b7308ebff5d2a45e6414"}, +] + +[package.dependencies] +joblib = ">=1.2.0" +numpy = ">=1.19.5" +scipy = ">=1.6.0" +threadpoolctl = ">=3.1.0" + +[package.extras] +benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-gallery (>=0.16.0)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)"] +examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] +install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] +maintenance = ["conda-lock (==2.5.6)"] +tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.2.1)", "scikit-image (>=0.17.2)"] + +[[package]] +name = "scipy" +version = "1.15.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "scipy-1.15.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:c64ded12dcab08afff9e805a67ff4480f5e69993310e093434b10e85dc9d43e1"}, + {file = "scipy-1.15.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5b190b935e7db569960b48840e5bef71dc513314cc4e79a1b7d14664f57fd4ff"}, + {file = "scipy-1.15.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:4b17d4220df99bacb63065c76b0d1126d82bbf00167d1730019d2a30d6ae01ea"}, + {file = "scipy-1.15.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:63b9b6cd0333d0eb1a49de6f834e8aeaefe438df8f6372352084535ad095219e"}, + {file = "scipy-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f151e9fb60fbf8e52426132f473221a49362091ce7a5e72f8aa41f8e0da4f25"}, + {file = "scipy-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e10b1dd56ce92fba3e786007322542361984f8463c6d37f6f25935a5a6ef52"}, + {file = "scipy-1.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5dff14e75cdbcf07cdaa1c7707db6017d130f0af9ac41f6ce443a93318d6c6e0"}, + {file = "scipy-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:f82fcf4e5b377f819542fbc8541f7b5fbcf1c0017d0df0bc22c781bf60abc4d8"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:5bd8d27d44e2c13d0c1124e6a556454f52cd3f704742985f6b09e75e163d20d2"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:be3deeb32844c27599347faa077b359584ba96664c5c79d71a354b80a0ad0ce0"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:5eb0ca35d4b08e95da99a9f9c400dc9f6c21c424298a0ba876fdc69c7afacedf"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:74bb864ff7640dea310a1377d8567dc2cb7599c26a79ca852fc184cc851954ac"}, + {file = "scipy-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:667f950bf8b7c3a23b4199db24cb9bf7512e27e86d0e3813f015b74ec2c6e3df"}, + {file = "scipy-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395be70220d1189756068b3173853029a013d8c8dd5fd3d1361d505b2aa58fa7"}, + {file = "scipy-1.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce3a000cd28b4430426db2ca44d96636f701ed12e2b3ca1f2b1dd7abdd84b39a"}, + {file = "scipy-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:3fe1d95944f9cf6ba77aa28b82dd6bb2a5b52f2026beb39ecf05304b8392864b"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c09aa9d90f3500ea4c9b393ee96f96b0ccb27f2f350d09a47f533293c78ea776"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0ac102ce99934b162914b1e4a6b94ca7da0f4058b6d6fd65b0cef330c0f3346f"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:09c52320c42d7f5c7748b69e9f0389266fd4f82cf34c38485c14ee976cb8cb04"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:cdde8414154054763b42b74fe8ce89d7f3d17a7ac5dd77204f0e142cdc9239e9"}, + {file = "scipy-1.15.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c9d8fc81d6a3b6844235e6fd175ee1d4c060163905a2becce8e74cb0d7554ce"}, + {file = "scipy-1.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fb57b30f0017d4afa5fe5f5b150b8f807618819287c21cbe51130de7ccdaed2"}, + {file = "scipy-1.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491d57fe89927fa1aafbe260f4cfa5ffa20ab9f1435025045a5315006a91b8f5"}, + {file = "scipy-1.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:900f3fa3db87257510f011c292a5779eb627043dd89731b9c461cd16ef76ab3d"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:100193bb72fbff37dbd0bf14322314fc7cbe08b7ff3137f11a34d06dc0ee6b85"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:2114a08daec64980e4b4cbdf5bee90935af66d750146b1d2feb0d3ac30613692"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6b3e71893c6687fc5e29208d518900c24ea372a862854c9888368c0b267387ab"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:837299eec3d19b7e042923448d17d95a86e43941104d33f00da7e31a0f715d3c"}, + {file = "scipy-1.15.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82add84e8a9fb12af5c2c1a3a3f1cb51849d27a580cb9e6bd66226195142be6e"}, + {file = "scipy-1.15.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070d10654f0cb6abd295bc96c12656f948e623ec5f9a4eab0ddb1466c000716e"}, + {file = "scipy-1.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55cc79ce4085c702ac31e49b1e69b27ef41111f22beafb9b49fea67142b696c4"}, + {file = "scipy-1.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:c352c1b6d7cac452534517e022f8f7b8d139cd9f27e6fbd9f3cbd0bfd39f5bef"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0458839c9f873062db69a03de9a9765ae2e694352c76a16be44f93ea45c28d2b"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:af0b61c1de46d0565b4b39c6417373304c1d4f5220004058bdad3061c9fa8a95"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:71ba9a76c2390eca6e359be81a3e879614af3a71dfdabb96d1d7ab33da6f2364"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14eaa373c89eaf553be73c3affb11ec6c37493b7eaaf31cf9ac5dffae700c2e0"}, + {file = "scipy-1.15.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f735bc41bd1c792c96bc426dece66c8723283695f02df61dcc4d0a707a42fc54"}, + {file = "scipy-1.15.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2722a021a7929d21168830790202a75dbb20b468a8133c74a2c0230c72626b6c"}, + {file = "scipy-1.15.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc7136626261ac1ed988dca56cfc4ab5180f75e0ee52e58f1e6aa74b5f3eacd5"}, + {file = "scipy-1.15.1.tar.gz", hash = "sha256:033a75ddad1463970c96a88063a1df87ccfddd526437136b6ee81ff0312ebdf6"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.5" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.16.5)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "selenium" +version = "4.25.0" +description = "Official Python bindings for Selenium WebDriver" +optional = false +python-versions = ">=3.8" +files = [ + {file = "selenium-4.25.0-py3-none-any.whl", hash = "sha256:3798d2d12b4a570bc5790163ba57fef10b2afee958bf1d80f2a3cf07c4141f33"}, + {file = "selenium-4.25.0.tar.gz", hash = "sha256:95d08d3b82fb353f3c474895154516604c7f0e6a9a565ae6498ef36c9bac6921"}, +] + +[package.dependencies] +certifi = ">=2021.10.8" +trio = ">=0.17,<1.0" +trio-websocket = ">=0.9,<1.0" +typing_extensions = ">=4.9,<5.0" +urllib3 = {version = ">=1.26,<3", extras = ["socks"]} +websocket-client = ">=1.8,<2.0" + +[[package]] +name = "sgmllib3k" +version = "1.0.0" +description = "Py3k port of sgmllib." +optional = false +python-versions = "*" +files = [ + {file = "sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"}, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + +[[package]] +name = "soupsieve" +version = "2.6" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.37" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6f5d254a22394847245f411a2956976401e84da4288aa70cbcd5190744062c1"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41296bbcaa55ef5fdd32389a35c710133b097f7b2609d8218c0eabded43a1d84"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bedee60385c1c0411378cbd4dc486362f5ee88deceea50002772912d798bb00f"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6c67415258f9f3c69867ec02fea1bf6508153709ecbd731a982442a590f2b7e4"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-win32.whl", hash = "sha256:650dcb70739957a492ad8acff65d099a9586b9b8920e3507ca61ec3ce650bb72"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-win_amd64.whl", hash = "sha256:93d1543cd8359040c02b6614421c8e10cd7a788c40047dbc507ed46c29ae5636"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:78361be6dc9073ed17ab380985d1e45e48a642313ab68ab6afa2457354ff692c"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b661b49d0cb0ab311a189b31e25576b7ac3e20783beb1e1817d72d9d02508bf5"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d57bafbab289e147d064ffbd5cca2d7b1394b63417c0636cea1f2e93d16eb9e8"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa2c0913f02341d25fb858e4fb2031e6b0813494cca1ba07d417674128ce11b"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9df21b8d9e5c136ea6cde1c50d2b1c29a2b5ff2b1d610165c23ff250e0704087"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db18ff6b8c0f1917f8b20f8eca35c28bbccb9f83afa94743e03d40203ed83de9"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-win32.whl", hash = "sha256:46954173612617a99a64aee103bcd3f078901b9a8dcfc6ae80cbf34ba23df989"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-win_amd64.whl", hash = "sha256:7b7e772dc4bc507fdec4ee20182f15bd60d2a84f1e087a8accf5b5b7a0dcf2ba"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2952748ecd67ed3b56773c185e85fc084f6bdcdec10e5032a7c25a6bc7d682ef"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3151822aa1db0eb5afd65ccfafebe0ef5cda3a7701a279c8d0bf17781a793bb4"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaa8039b6d20137a4e02603aba37d12cd2dde7887500b8855356682fc33933f4"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdba1f73b64530c47b27118b7053b8447e6d6f3c8104e3ac59f3d40c33aa9fd"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1b2690456528a87234a75d1a1644cdb330a6926f455403c8e4f6cad6921f9098"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf5ae8a9dcf657fd72144a7fd01f243236ea39e7344e579a121c4205aedf07bb"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-win32.whl", hash = "sha256:ea308cec940905ba008291d93619d92edaf83232ec85fbd514dcb329f3192761"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-win_amd64.whl", hash = "sha256:635d8a21577341dfe4f7fa59ec394b346da12420b86624a69e466d446de16aff"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c4096727193762e72ce9437e2a86a110cf081241919ce3fab8e89c02f6b6658"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4fb5ac86d8fe8151966814f6720996430462e633d225497566b3996966b9bdb"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e56a139bfe136a22c438478a86f8204c1eb5eed36f4e15c4224e4b9db01cb3e4"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f95fc8e3f34b5f6b3effb49d10ac97c569ec8e32f985612d9b25dd12d0d2e94"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c505edd429abdfe3643fa3b2e83efb3445a34a9dc49d5f692dd087be966020e0"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:12b0f1ec623cccf058cf21cb544f0e74656618165b083d78145cafde156ea7b6"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-win32.whl", hash = "sha256:293f9ade06b2e68dd03cfb14d49202fac47b7bb94bffcff174568c951fbc7af2"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-win_amd64.whl", hash = "sha256:d70f53a0646cc418ca4853da57cf3ddddbccb8c98406791f24426f2dd77fd0e2"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44f569d0b1eb82301b92b72085583277316e7367e038d97c3a1a899d9a05e342"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2eae3423e538c10d93ae3e87788c6a84658c3ed6db62e6a61bb9495b0ad16bb"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfff7be361048244c3aa0f60b5e63221c5e0f0e509f4e47b8910e22b57d10ae7"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:5bc3339db84c5fb9130ac0e2f20347ee77b5dd2596ba327ce0d399752f4fce39"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:84b9f23b0fa98a6a4b99d73989350a94e4a4ec476b9a7dfe9b79ba5939f5e80b"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-win32.whl", hash = "sha256:51bc9cfef83e0ac84f86bf2b10eaccb27c5a3e66a1212bef676f5bee6ef33ebb"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-win_amd64.whl", hash = "sha256:8e47f1af09444f87c67b4f1bb6231e12ba6d4d9f03050d7fc88df6d075231a49"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6b788f14c5bb91db7f468dcf76f8b64423660a05e57fe277d3f4fad7b9dcb7ce"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521ef85c04c33009166777c77e76c8a676e2d8528dc83a57836b63ca9c69dcd1"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75311559f5c9881a9808eadbeb20ed8d8ba3f7225bef3afed2000c2a9f4d49b9"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cce918ada64c956b62ca2c2af59b125767097ec1dca89650a6221e887521bfd7"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9d087663b7e1feabea8c578d6887d59bb00388158e8bff3a76be11aa3f748ca2"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cf95a60b36997dad99692314c4713f141b61c5b0b4cc5c3426faad570b31ca01"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-win32.whl", hash = "sha256:d75ead7dd4d255068ea0f21492ee67937bd7c90964c8f3c2bea83c7b7f81b95f"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-win_amd64.whl", hash = "sha256:74bbd1d0a9bacf34266a7907d43260c8d65d31d691bb2356f41b17c2dca5b1d0"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:648ec5acf95ad59255452ef759054f2176849662af4521db6cb245263ae4aa33"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:35bd2df269de082065d4b23ae08502a47255832cc3f17619a5cea92ce478b02b"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f581d365af9373a738c49e0c51e8b18e08d8a6b1b15cc556773bcd8a192fa8b"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82df02816c14f8dc9f4d74aea4cb84a92f4b0620235daa76dde002409a3fbb5a"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94b564e38b344d3e67d2e224f0aec6ba09a77e4582ced41e7bfd0f757d926ec9"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:955a2a765aa1bd81aafa69ffda179d4fe3e2a3ad462a736ae5b6f387f78bfeb8"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-win32.whl", hash = "sha256:03f0528c53ca0b67094c4764523c1451ea15959bbf0a8a8a3096900014db0278"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-win_amd64.whl", hash = "sha256:4b12885dc85a2ab2b7d00995bac6d967bffa8594123b02ed21e8eb2205a7584b"}, + {file = "SQLAlchemy-2.0.37-py3-none-any.whl", hash = "sha256:a8998bf9f8658bd3839cbc44ddbe982955641863da0c1efe5b00c1ab4f5c16b1"}, + {file = "sqlalchemy-2.0.37.tar.gz", hash = "sha256:12b28d99a9c14eaf4055810df1001557176716de0167b91026e648e65229bffb"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "starlette" +version = "0.38.6" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.8" +files = [ + {file = "starlette-0.38.6-py3-none-any.whl", hash = "sha256:4517a1409e2e73ee4951214ba012052b9e16f60e90d73cfb06192c19203bbb05"}, + {file = "starlette-0.38.6.tar.gz", hash = "sha256:863a1588f5574e70a821dadefb41e4881ea451a47a3cd1b4df359d4ffefe5ead"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tenacity" +version = "8.5.0" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, + {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + +[[package]] +name = "threadpoolctl" +version = "3.5.0" +description = "threadpoolctl" +optional = false +python-versions = ">=3.8" +files = [ + {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, + {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, +] + +[[package]] +name = "tiktoken" +version = "0.8.0" +description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" +optional = false +python-versions = ">=3.9" +files = [ + {file = "tiktoken-0.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b07e33283463089c81ef1467180e3e00ab00d46c2c4bbcef0acab5f771d6695e"}, + {file = "tiktoken-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9269348cb650726f44dd3bbb3f9110ac19a8dcc8f54949ad3ef652ca22a38e21"}, + {file = "tiktoken-0.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e13f37bc4ef2d012731e93e0fef21dc3b7aea5bb9009618de9a4026844e560"}, + {file = "tiktoken-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f13d13c981511331eac0d01a59b5df7c0d4060a8be1e378672822213da51e0a2"}, + {file = "tiktoken-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6b2ddbc79a22621ce8b1166afa9f9a888a664a579350dc7c09346a3b5de837d9"}, + {file = "tiktoken-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d8c2d0e5ba6453a290b86cd65fc51fedf247e1ba170191715b049dac1f628005"}, + {file = "tiktoken-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d622d8011e6d6f239297efa42a2657043aaed06c4f68833550cac9e9bc723ef1"}, + {file = "tiktoken-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2efaf6199717b4485031b4d6edb94075e4d79177a172f38dd934d911b588d54a"}, + {file = "tiktoken-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5637e425ce1fc49cf716d88df3092048359a4b3bbb7da762840426e937ada06d"}, + {file = "tiktoken-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fb0e352d1dbe15aba082883058b3cce9e48d33101bdaac1eccf66424feb5b47"}, + {file = "tiktoken-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56edfefe896c8f10aba372ab5706b9e3558e78db39dd497c940b47bf228bc419"}, + {file = "tiktoken-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:326624128590def898775b722ccc327e90b073714227175ea8febbc920ac0a99"}, + {file = "tiktoken-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:881839cfeae051b3628d9823b2e56b5cc93a9e2efb435f4cf15f17dc45f21586"}, + {file = "tiktoken-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe9399bdc3f29d428f16a2f86c3c8ec20be3eac5f53693ce4980371c3245729b"}, + {file = "tiktoken-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a58deb7075d5b69237a3ff4bb51a726670419db6ea62bdcd8bd80c78497d7ab"}, + {file = "tiktoken-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2908c0d043a7d03ebd80347266b0e58440bdef5564f84f4d29fb235b5df3b04"}, + {file = "tiktoken-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:294440d21a2a51e12d4238e68a5972095534fe9878be57d905c476017bff99fc"}, + {file = "tiktoken-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:d8f3192733ac4d77977432947d563d7e1b310b96497acd3c196c9bddb36ed9db"}, + {file = "tiktoken-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:02be1666096aff7da6cbd7cdaa8e7917bfed3467cd64b38b1f112e96d3b06a24"}, + {file = "tiktoken-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94ff53c5c74b535b2cbf431d907fc13c678bbd009ee633a2aca269a04389f9a"}, + {file = "tiktoken-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b231f5e8982c245ee3065cd84a4712d64692348bc609d84467c57b4b72dcbc5"}, + {file = "tiktoken-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4177faa809bd55f699e88c96d9bb4635d22e3f59d635ba6fd9ffedf7150b9953"}, + {file = "tiktoken-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5376b6f8dc4753cd81ead935c5f518fa0fbe7e133d9e25f648d8c4dabdd4bad7"}, + {file = "tiktoken-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:18228d624807d66c87acd8f25fc135665617cab220671eb65b50f5d70fa51f69"}, + {file = "tiktoken-0.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e17807445f0cf1f25771c9d86496bd8b5c376f7419912519699f3cc4dc5c12e"}, + {file = "tiktoken-0.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:886f80bd339578bbdba6ed6d0567a0d5c6cfe198d9e587ba6c447654c65b8edc"}, + {file = "tiktoken-0.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6adc8323016d7758d6de7313527f755b0fc6c72985b7d9291be5d96d73ecd1e1"}, + {file = "tiktoken-0.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b591fb2b30d6a72121a80be24ec7a0e9eb51c5500ddc7e4c2496516dd5e3816b"}, + {file = "tiktoken-0.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:845287b9798e476b4d762c3ebda5102be87ca26e5d2c9854002825d60cdb815d"}, + {file = "tiktoken-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:1473cfe584252dc3fa62adceb5b1c763c1874e04511b197da4e6de51d6ce5a02"}, + {file = "tiktoken-0.8.0.tar.gz", hash = "sha256:9ccbb2740f24542534369c5635cfd9b2b3c2490754a78ac8831d99f89f94eeb2"}, +] + +[package.dependencies] +regex = ">=2022.1.18" +requests = ">=2.26.0" + +[package.extras] +blobfile = ["blobfile (>=2)"] + +[[package]] +name = "together" +version = "1.4.1" +description = "Python client for Together's Cloud Platform!" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "together-1.4.1-py3-none-any.whl", hash = "sha256:10106ec81f73557c4d0b4e6b02614124c393c12b01466889615b0fa7758d6c1d"}, + {file = "together-1.4.1.tar.gz", hash = "sha256:0fd69ae5afa1054e945036b3aa1b01e22a7a6a709f22c994119558062e85ff37"}, +] + +[package.dependencies] +aiohttp = ">=3.9.3,<4.0.0" +click = ">=8.1.7,<9.0.0" +eval-type-backport = ">=0.1.3,<0.3.0" +filelock = ">=3.13.1,<4.0.0" +numpy = {version = ">=1.26.0", markers = "python_version >= \"3.12\""} +pillow = ">=11.1.0,<12.0.0" +pyarrow = ">=10.0.1" +pydantic = ">=2.6.3,<3.0.0" +requests = ">=2.31.0,<3.0.0" +rich = ">=13.8.1,<14.0.0" +tabulate = ">=0.9.0,<0.10.0" +tqdm = ">=4.66.2,<5.0.0" +typer = ">=0.9,<0.16" + +[[package]] +name = "tomlkit" +version = "0.13.2" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, +] + +[[package]] +name = "toolz" +version = "0.12.1" +description = "List processing tools and functional utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, + {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "trio" +version = "0.28.0" +description = "A friendly Python library for async concurrency and I/O" +optional = false +python-versions = ">=3.9" +files = [ + {file = "trio-0.28.0-py3-none-any.whl", hash = "sha256:56d58977acc1635735a96581ec70513cc781b8b6decd299c487d3be2a721cd94"}, + {file = "trio-0.28.0.tar.gz", hash = "sha256:4e547896fe9e8a5658e54e4c7c5fa1db748cbbbaa7c965e7d40505b928c73c05"}, +] + +[package.dependencies] +attrs = ">=23.2.0" +cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} +idna = "*" +outcome = "*" +sniffio = ">=1.3.0" +sortedcontainers = "*" + +[[package]] +name = "trio-websocket" +version = "0.11.1" +description = "WebSocket library for Trio" +optional = false +python-versions = ">=3.7" +files = [ + {file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"}, + {file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"}, +] + +[package.dependencies] +trio = ">=0.11" +wsproto = ">=0.14" + +[[package]] +name = "tweepy" +version = "4.14.0" +description = "Twitter library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tweepy-4.14.0-py3-none-any.whl", hash = "sha256:db6d3844ccc0c6d27f339f12ba8acc89912a961da513c1ae50fa2be502a56afb"}, + {file = "tweepy-4.14.0.tar.gz", hash = "sha256:1f9f1707d6972de6cff6c5fd90dfe6a449cd2e0d70bd40043ffab01e07a06c8c"}, +] + +[package.dependencies] +oauthlib = ">=3.2.0,<4" +requests = ">=2.27.0,<3" +requests-oauthlib = ">=1.2.0,<2" + +[package.extras] +async = ["aiohttp (>=3.7.3,<4)", "async-lru (>=1.0.3,<3)"] +dev = ["coverage (>=4.4.2)", "coveralls (>=2.1.0)", "tox (>=3.21.0)"] +docs = ["myst-parser (==0.15.2)", "readthedocs-sphinx-search (==0.1.1)", "sphinx (==4.2.0)", "sphinx-hoverxref (==0.7b1)", "sphinx-rtd-theme (==1.0.0)", "sphinx-tabs (==3.2.0)"] +socks = ["requests[socks] (>=2.27.0,<3)"] +test = ["vcrpy (>=1.10.3)"] + +[[package]] +name = "typer" +version = "0.15.1" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.7" +files = [ + {file = "typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"}, + {file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"}, +] + +[package.dependencies] +click = ">=8.0.0" +rich = ">=10.11.0" +shellingham = ">=1.3.0" +typing-extensions = ">=3.7.4.3" + +[[package]] +name = "types-requests" +version = "2.32.0.20240914" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-requests-2.32.0.20240914.tar.gz", hash = "sha256:2850e178db3919d9bf809e434eef65ba49d0e7e33ac92d588f4a5e295fffd405"}, + {file = "types_requests-2.32.0.20240914-py3-none-any.whl", hash = "sha256:59c2f673eb55f32a99b2894faf6020e1a9f4a402ad0f192bfee0b64469054310"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.dependencies] +pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.31.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.31.0-py3-none-any.whl", hash = "sha256:cac7be4dd4d891c363cd942160a7b02e69150dcbc7a36be04d5f4af4b17c8ced"}, + {file = "uvicorn-0.31.0.tar.gz", hash = "sha256:13bc21373d103859f68fe739608e2eb054a816dea79189bc3ca08ea89a275906"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "web3" +version = "7.6.0" +description = "web3: A Python library for interacting with Ethereum" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "web3-7.6.0-py3-none-any.whl", hash = "sha256:670dac222b2ec5ce72f4572d8e5d91afe79fcac03af9dabfc69da4fe9f6621df"}, + {file = "web3-7.6.0.tar.gz", hash = "sha256:25df8acdcb78eb872c3299408b79e8b4fd091602de5e3d29cbd8459e8f75ff23"}, +] + +[package.dependencies] +aiohttp = ">=3.7.4.post0" +eth-abi = ">=5.0.1" +eth-account = ">=0.13.1" +eth-hash = {version = ">=0.5.1", extras = ["pycryptodome"]} +eth-typing = ">=5.0.0" +eth-utils = ">=5.0.0" +hexbytes = ">=1.2.0" +pydantic = ">=2.4.0" +pyunormalize = ">=15.0.0" +pywin32 = {version = ">=223", markers = "platform_system == \"Windows\""} +requests = ">=2.23.0" +types-requests = ">=2.0.0" +typing-extensions = ">=4.0.1" +websockets = ">=10.0.0,<14.0.0" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-tester[py-evm] (>=0.11.0b1,<0.13.0b1)", "flaky (>=3.7.0)", "hypothesis (>=3.31.2)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "py-geth (>=5.0.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.18.1,<0.23)", "pytest-mock (>=1.10)", "pytest-xdist (>=2.4.0)", "setuptools (>=38.6.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "tqdm (>4.32)", "twine (>=1.13)", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-tester[py-evm] (>=0.11.0b1,<0.13.0b1)", "flaky (>=3.7.0)", "hypothesis (>=3.31.2)", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "py-geth (>=5.0.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.18.1,<0.23)", "pytest-mock (>=1.10)", "pytest-xdist (>=2.4.0)", "tox (>=4.0.0)"] +tester = ["eth-tester[py-evm] (>=0.11.0b1,<0.13.0b1)", "py-geth (>=5.0.0)"] + +[[package]] +name = "webdriver-manager" +version = "4.0.2" +description = "Library provides the way to automatically manage drivers for different browsers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "webdriver_manager-4.0.2-py2.py3-none-any.whl", hash = "sha256:75908d92ecc45ff2b9953614459c633db8f9aa1ff30181cefe8696e312908129"}, + {file = "webdriver_manager-4.0.2.tar.gz", hash = "sha256:efedf428f92fd6d5c924a0d054e6d1322dd77aab790e834ee767af392b35590f"}, +] + +[package.dependencies] +packaging = "*" +python-dotenv = "*" +requests = "*" + +[[package]] +name = "websocket-client" +version = "1.8.0" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, + {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, +] + +[package.extras] +docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "websockets" +version = "13.0.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f"}, + {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c"}, + {file = "websockets-13.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f1d3d1f2eb79fe7b0fb02e599b2bf76a7619c79300fc55f0b5e2d382881d4f7f"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c7d62ee071fa94a2fc52c2b472fed4af258d43f9030479d9c4a2de885fd543"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6724b554b70d6195ba19650fef5759ef11346f946c07dbbe390e039bcaa7cc3d"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a952fa2ae57a42ba7951e6b2605e08a24801a4931b5644dfc68939e041bc7f"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17118647c0ea14796364299e942c330d72acc4b248e07e639d34b75067b3cdd8"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64a11aae1de4c178fa653b07d90f2fb1a2ed31919a5ea2361a38760192e1858b"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0617fd0b1d14309c7eab6ba5deae8a7179959861846cbc5cb528a7531c249448"}, + {file = "websockets-13.0.1-cp310-cp310-win32.whl", hash = "sha256:11f9976ecbc530248cf162e359a92f37b7b282de88d1d194f2167b5e7ad80ce3"}, + {file = "websockets-13.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c3c493d0e5141ec055a7d6809a28ac2b88d5b878bb22df8c621ebe79a61123d0"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:699ba9dd6a926f82a277063603fc8d586b89f4cb128efc353b749b641fcddda7"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf2fae6d85e5dc384bf846f8243ddaa9197f3a1a70044f59399af001fd1f51d4"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:52aed6ef21a0f1a2a5e310fb5c42d7555e9c5855476bbd7173c3aa3d8a0302f2"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb2b9a318542153674c6e377eb8cb9ca0fc011c04475110d3477862f15d29f0"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5df891c86fe68b2c38da55b7aea7095beca105933c697d719f3f45f4220a5e0e"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac2d146ff30d9dd2fcf917e5d147db037a5c573f0446c564f16f1f94cf87462"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8ac5b46fd798bbbf2ac6620e0437c36a202b08e1f827832c4bf050da081b501"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46af561eba6f9b0848b2c9d2427086cabadf14e0abdd9fde9d72d447df268418"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b5a06d7f60bc2fc378a333978470dfc4e1415ee52f5f0fce4f7853eb10c1e9df"}, + {file = "websockets-13.0.1-cp311-cp311-win32.whl", hash = "sha256:556e70e4f69be1082e6ef26dcb70efcd08d1850f5d6c5f4f2bcb4e397e68f01f"}, + {file = "websockets-13.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:67494e95d6565bf395476e9d040037ff69c8b3fa356a886b21d8422ad86ae075"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f9c9e258e3d5efe199ec23903f5da0eeaad58cf6fccb3547b74fd4750e5ac47a"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6b41a1b3b561f1cba8321fb32987552a024a8f67f0d05f06fcf29f0090a1b956"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f73e676a46b0fe9426612ce8caeca54c9073191a77c3e9d5c94697aef99296af"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f613289f4a94142f914aafad6c6c87903de78eae1e140fa769a7385fb232fdf"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f52504023b1480d458adf496dc1c9e9811df4ba4752f0bc1f89ae92f4f07d0c"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:139add0f98206cb74109faf3611b7783ceafc928529c62b389917a037d4cfdf4"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47236c13be337ef36546004ce8c5580f4b1150d9538b27bf8a5ad8edf23ccfab"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c44ca9ade59b2e376612df34e837013e2b273e6c92d7ed6636d0556b6f4db93d"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9bbc525f4be3e51b89b2a700f5746c2a6907d2e2ef4513a8daafc98198b92237"}, + {file = "websockets-13.0.1-cp312-cp312-win32.whl", hash = "sha256:3624fd8664f2577cf8de996db3250662e259bfbc870dd8ebdcf5d7c6ac0b5185"}, + {file = "websockets-13.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0513c727fb8adffa6d9bf4a4463b2bade0186cbd8c3604ae5540fae18a90cb99"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1ee4cc030a4bdab482a37462dbf3ffb7e09334d01dd37d1063be1136a0d825fa"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbb0b697cc0655719522406c059eae233abaa3243821cfdfab1215d02ac10231"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:acbebec8cb3d4df6e2488fbf34702cbc37fc39ac7abf9449392cefb3305562e9"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63848cdb6fcc0bf09d4a155464c46c64ffdb5807ede4fb251da2c2692559ce75"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:872afa52a9f4c414d6955c365b6588bc4401272c629ff8321a55f44e3f62b553"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e70fec7c54aad4d71eae8e8cab50525e899791fc389ec6f77b95312e4e9920"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e82db3756ccb66266504f5a3de05ac6b32f287faacff72462612120074103329"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e85f46ce287f5c52438bb3703d86162263afccf034a5ef13dbe4318e98d86e7"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3fea72e4e6edb983908f0db373ae0732b275628901d909c382aae3b592589f2"}, + {file = "websockets-13.0.1-cp313-cp313-win32.whl", hash = "sha256:254ecf35572fca01a9f789a1d0f543898e222f7b69ecd7d5381d8d8047627bdb"}, + {file = "websockets-13.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca48914cdd9f2ccd94deab5bcb5ac98025a5ddce98881e5cce762854a5de330b"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b74593e9acf18ea5469c3edaa6b27fa7ecf97b30e9dabd5a94c4c940637ab96e"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:132511bfd42e77d152c919147078460c88a795af16b50e42a0bd14f0ad71ddd2"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:165bedf13556f985a2aa064309baa01462aa79bf6112fbd068ae38993a0e1f1b"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e801ca2f448850685417d723ec70298feff3ce4ff687c6f20922c7474b4746ae"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30d3a1f041360f029765d8704eae606781e673e8918e6b2c792e0775de51352f"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67648f5e50231b5a7f6d83b32f9c525e319f0ddc841be0de64f24928cd75a603"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4f0426d51c8f0926a4879390f53c7f5a855e42d68df95fff6032c82c888b5f36"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ef48e4137e8799998a343706531e656fdec6797b80efd029117edacb74b0a10a"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:249aab278810bee585cd0d4de2f08cfd67eed4fc75bde623be163798ed4db2eb"}, + {file = "websockets-13.0.1-cp38-cp38-win32.whl", hash = "sha256:06c0a667e466fcb56a0886d924b5f29a7f0886199102f0a0e1c60a02a3751cb4"}, + {file = "websockets-13.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1f3cf6d6ec1142412d4535adabc6bd72a63f5f148c43fe559f06298bc21953c9"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fa082ea38d5de51dd409434edc27c0dcbd5fed2b09b9be982deb6f0508d25bc"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a365bcb7be554e6e1f9f3ed64016e67e2fa03d7b027a33e436aecf194febb63"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10a0dc7242215d794fb1918f69c6bb235f1f627aaf19e77f05336d147fce7c37"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59197afd478545b1f73367620407b0083303569c5f2d043afe5363676f2697c9"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d20516990d8ad557b5abeb48127b8b779b0b7e6771a265fa3e91767596d7d97"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1a2e272d067030048e1fe41aa1ec8cfbbaabce733b3d634304fa2b19e5c897f"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad327ac80ba7ee61da85383ca8822ff808ab5ada0e4a030d66703cc025b021c4"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:518f90e6dd089d34eaade01101fd8a990921c3ba18ebbe9b0165b46ebff947f0"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68264802399aed6fe9652e89761031acc734fc4c653137a5911c2bfa995d6d6d"}, + {file = "websockets-13.0.1-cp39-cp39-win32.whl", hash = "sha256:a5dc0c42ded1557cc7c3f0240b24129aefbad88af4f09346164349391dea8e58"}, + {file = "websockets-13.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b448a0690ef43db5ef31b3a0d9aea79043882b4632cfc3eaab20105edecf6097"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:faef9ec6354fe4f9a2c0bbb52fb1ff852effc897e2a4501e25eb3a47cb0a4f89"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:03d3f9ba172e0a53e37fa4e636b86cc60c3ab2cfee4935e66ed1d7acaa4625ad"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d450f5a7a35662a9b91a64aefa852f0c0308ee256122f5218a42f1d13577d71e"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f55b36d17ac50aa8a171b771e15fbe1561217510c8768af3d546f56c7576cdc"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14b9c006cac63772b31abbcd3e3abb6228233eec966bf062e89e7fa7ae0b7333"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b79915a1179a91f6c5f04ece1e592e2e8a6bd245a0e45d12fd56b2b59e559a32"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f40de079779acbcdbb6ed4c65af9f018f8b77c5ec4e17a4b737c05c2db554491"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e4ba642fc87fa532bac07e5ed7e19d56940b6af6a8c61d4429be48718a380f"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a02b0161c43cc9e0232711eff846569fad6ec836a7acab16b3cf97b2344c060"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aa74a45d4cdc028561a7d6ab3272c8b3018e23723100b12e58be9dfa5a24491"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00fd961943b6c10ee6f0b1130753e50ac5dcd906130dcd77b0003c3ab797d026"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d93572720d781331fb10d3da9ca1067817d84ad1e7c31466e9f5e59965618096"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:71e6e5a3a3728886caee9ab8752e8113670936a193284be9d6ad2176a137f376"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c4a6343e3b0714e80da0b0893543bf9a5b5fa71b846ae640e56e9abc6fbc4c83"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a678532018e435396e37422a95e3ab87f75028ac79570ad11f5bf23cd2a7d8c"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6716c087e4aa0b9260c4e579bb82e068f84faddb9bfba9906cb87726fa2e870"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e33505534f3f673270dd67f81e73550b11de5b538c56fe04435d63c02c3f26b5"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acab3539a027a85d568c2573291e864333ec9d912675107d6efceb7e2be5d980"}, + {file = "websockets-13.0.1-py3-none-any.whl", hash = "sha256:b80f0c51681c517604152eb6a572f5a9378f877763231fddb883ba2f968e8817"}, + {file = "websockets-13.0.1.tar.gz", hash = "sha256:4d6ece65099411cfd9a48d13701d7438d9c34f479046b34c50ff60bb8834e43e"}, +] + +[[package]] +name = "werkzeug" +version = "2.2.2" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, + {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog"] + +[[package]] +name = "wrapt" +version = "1.17.2" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.8" +files = [ + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, + {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, + {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, + {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, + {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, + {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, + {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, + {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, + {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, + {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, + {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, + {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, + {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, + {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, + {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, + {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, + {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, +] + +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + +[[package]] +name = "yarl" +version = "1.11.1" +description = "Yet another URL library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46"}, + {file = "yarl-1.11.1-cp310-cp310-win32.whl", hash = "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91"}, + {file = "yarl-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e"}, + {file = "yarl-1.11.1-cp311-cp311-win32.whl", hash = "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6"}, + {file = "yarl-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45"}, + {file = "yarl-1.11.1-cp312-cp312-win32.whl", hash = "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447"}, + {file = "yarl-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da"}, + {file = "yarl-1.11.1-cp313-cp313-win32.whl", hash = "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979"}, + {file = "yarl-1.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420"}, + {file = "yarl-1.11.1-cp38-cp38-win32.whl", hash = "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a"}, + {file = "yarl-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df"}, + {file = "yarl-1.11.1-cp39-cp39-win32.whl", hash = "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74"}, + {file = "yarl-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0"}, + {file = "yarl-1.11.1-py3-none-any.whl", hash = "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38"}, + {file = "yarl-1.11.1.tar.gz", hash = "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "c71fb1f7f285f603186a8de6cb67284d90dac18d9610f195244f0d82042d80d0" diff --git a/submodules/agents/poetry.toml b/submodules/agents/poetry.toml new file mode 100644 index 00000000..ab1033bd --- /dev/null +++ b/submodules/agents/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/submodules/agents/pyproject.toml b/submodules/agents/pyproject.toml new file mode 100755 index 00000000..8314fd07 --- /dev/null +++ b/submodules/agents/pyproject.toml @@ -0,0 +1,84 @@ +[tool.poetry] +name = "moragents" +version = "0.3.0" +description = "Server for moragents" +authors = ["Neo, Morpheus, and Trinity "] +readme = "README.md" +include = ["src"] + +[tool.poetry.dependencies] +python = "^3.12" +llama-cpp-python = "0.2.90" +werkzeug = "2.2.2" +web3 = "7.6.0" +scikit-learn = "1.5.1" +fastapi = "0.115.0" +pymupdf = "1.22.5" +faiss-cpu = "1.8.0.post1" +feedparser = "*" +langchain-text-splitters = "0.3.0" +langchain-community = "0.3.1" +langchain-ollama = "0.2.2" +tweepy = "4.14.0" +uvicorn = "0.31.0" +python-dateutil = "2.9.0.post0" +python-multipart = "0.0.12" +beautifulsoup4 = "4.12.3" +selenium = "4.25.0" +cdp-sdk = "0.15.0" +aiofiles = "24.1.0" +pytz = "2024.2" +pyshorteners = "1.0.1" +webdriver-manager = "4.0.2" +psycopg2-binary = "2.9.10" +alembic = "1.14.1" +langchain-together = "0.3.0" +langchain-cerebras = "0.5.0" +boto3 = "1.37.0" +together = "1.4.1" + +[tool.poetry.group.dev.dependencies] +pycodestyle = "^2.10.0" +autopep8 = "^2.0.2" +pytest = "^8.3.2" +pytest-mock = "^3.11.1" +pytest-asyncio = "^0.24.0" +mypy = "^1.4.1" +pylint = "^2.17.5" +black = "^24.0.0" + +[tool.poetry.scripts] +login = "login:run" + +[tool.black] +line-length = 120 + +[tool.mypy] +python_version = "3.12" +packages = ["src"] +namespace_packages = true +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +warn_unreachable = true +explicit_package_bases = true + +[[tool.mypy.overrides]] +module = "sqlalchemy.*" +ignore_missing_imports = true + +# This tells mypy to treat the src directory as a package root +[[tool.mypy.overrides]] +module = "src.*" +implicit_reexport = true + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/submodules/moragents_dockers/agents/pytest.ini b/submodules/agents/pytest.ini old mode 100644 new mode 100755 similarity index 77% rename from submodules/moragents_dockers/agents/pytest.ini rename to submodules/agents/pytest.ini index a7f7118b..589da6ae --- a/submodules/moragents_dockers/agents/pytest.ini +++ b/submodules/agents/pytest.ini @@ -1,3 +1,3 @@ [pytest] -pythonpath = . +pythonpath = src addopts = --import-mode=importlib -p no:warnings diff --git a/build_assets/macOS/__init__.py b/submodules/agents/src/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from build_assets/macOS/__init__.py rename to submodules/agents/src/__init__.py diff --git a/submodules/agents/src/app.py b/submodules/agents/src/app.py new file mode 100755 index 00000000..7d300280 --- /dev/null +++ b/submodules/agents/src/app.py @@ -0,0 +1,81 @@ +import os +import uvicorn +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from models.config.config import Config +from config import load_agent_routes, setup_logging + +# Configure routes +from routes import ( + agent_manager_routes, + wallet_manager_routes, + delegation_routes, + # workflow_manager_routes, +) + +# Configure logging +logger = setup_logging() +logger.info("Logging configured successfully") + +CONF = Config.get_instance() + +# Initialize FastAPI app +app = FastAPI() +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Setup upload directory +UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads") +os.makedirs(UPLOAD_FOLDER, exist_ok=True) +logger.info(f"Upload folder created at {UPLOAD_FOLDER}") + +# Include core routers +ROUTERS = [ + delegation_routes.router, + agent_manager_routes.router, + wallet_manager_routes.router, + # workflow_manager_routes.router, +] + +# Dynamically load and add agent routers +# Load and include all routers +agent_routers = load_agent_routes() +routers = agent_routers + ROUTERS + +for router in routers: + app.include_router(router) + + +# Temporary disable workflow manager +# TODO: Re-enable workflow manager +# +# @asynccontextmanager +# async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: +# """Lifespan context manager for FastAPI application""" +# # Startup +# logger.info("Starting workflow manager initialization") +# await workflow_manager_instance.initialize() +# logger.info("Workflow manager initialized successfully") +# yield +# # Shutdown +# # Add any cleanup code here if needed + + +# app.router.lifespan_context = lifespan + + +if __name__ == "__main__": + logger.info("Starting FastAPI application") + uvicorn.run( + "app:app", + host=CONF.get("host", "default"), + port=CONF.get_int("port", "default"), + workers=CONF.get_int("workers", "default"), + reload=CONF.get_bool("reload", "default"), + ) diff --git a/submodules/agents/src/config.py b/submodules/agents/src/config.py new file mode 100755 index 00000000..c37adbe1 --- /dev/null +++ b/submodules/agents/src/config.py @@ -0,0 +1,202 @@ +import os +import importlib.util + +from typing import List, Dict, Any, Optional + +from fastapi import APIRouter +from langchain_together import ChatTogether +from langchain_cerebras import ChatCerebras +from langchain_ollama import ChatOllama, OllamaEmbeddings + +from services.vectorstore.together_embeddings import TogetherEmbeddings +from services.vectorstore.vector_store_service import VectorStoreService +from services.secrets import get_secret +from logs import setup_logging + +logger = setup_logging() + + +def load_agent_routes() -> List[APIRouter]: + """ + Dynamically load all route modules from agent subdirectories. + Returns a list of FastAPI router objects. + """ + routers: List[APIRouter] = [] + agents_dir = os.path.join(os.path.dirname(__file__), "services/agents") + logger.info(f"Loading agents from {agents_dir}") + + for agent_dir in os.listdir(agents_dir): + agent_path = os.path.join(agents_dir, agent_dir) + routes_file = os.path.join(agent_path, "routes.py") + + # Skip non-agent directories + if not os.path.isdir(agent_path) or agent_dir.startswith("__"): + continue + + # Skip if no routes file exists + if not os.path.exists(routes_file): + continue + + try: + module_name = f"services.agents.{agent_dir}.routes" + spec = importlib.util.spec_from_file_location(module_name, routes_file) + + if spec is None or spec.loader is None: + logger.error(f"Failed to load module spec for {routes_file}") + continue + + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + if hasattr(module, "router"): + routers.append(module.router) + logger.info(f"Successfully loaded routes from {agent_dir}") + else: + logger.warning(f"No router found in {agent_dir}/routes.py") + + except Exception as e: + logger.error(f"Error loading routes from {agent_dir}: {str(e)}") + + return routers + + +def load_agent_config(agent_name: str) -> Optional[Dict[str, Any]]: + """ + Load configuration for a specific agent by name. + + Args: + agent_name (str): Name of the agent to load config for + + Returns: + Optional[Dict[str, Any]]: Agent configuration if found and loaded successfully, None otherwise + """ + agents_dir = os.path.join(os.path.dirname(__file__), "services/agents") + agent_path = os.path.join(agents_dir, agent_name) + config_file = os.path.join(agent_path, "config.py") + + # Verify agent directory exists and is valid + if not os.path.isdir(agent_path) or agent_name.startswith("__"): + logger.error(f"Invalid agent directory: {agent_name}") + return None + + # Check config file exists + if not os.path.exists(config_file): + logger.warning(f"No config file found for agent: {agent_name}") + return None + + try: + # Import the config module + module_name = f"services.agents.{agent_name}.config" + spec = importlib.util.spec_from_file_location(module_name, config_file) + + if spec is None or spec.loader is None: + logger.error(f"Failed to load module spec for {config_file}") + return None + + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + # Check for Config class and agent_config + if ( + hasattr(module, "Config") + and hasattr(module.Config, "agent_config") + and module.Config.agent_config.is_enabled + ): + config_dict: Dict[str, Any] = module.Config.agent_config.model_dump() + config_dict["name"] = agent_name + logger.info(f"Successfully loaded config for {agent_name}") + return config_dict + else: + logger.warning(f"No Config class or agent_config found in {agent_name}/config.py") + return None + + except Exception as e: + logger.error(f"Error loading config for {agent_name}: {str(e)}") + return None + + +def load_agent_configs() -> List[Dict[str, Any]]: + """ + Dynamically load configurations from all agent subdirectories. + Returns a consolidated configuration dictionary. + Skips special directories like __init__.py, __pycache__, and README.md. + """ + agents_dir = os.path.join(os.path.dirname(__file__), "services/agents") + logger.info(f"Loading agents from {agents_dir}") + configs: List[Dict[str, Any]] = [] + + for agent_dir in os.listdir(agents_dir): + # Skip special directories and files + if agent_dir.startswith("__") or agent_dir.startswith(".") or "." in agent_dir: + continue + + config = load_agent_config(agent_dir) + if config: + configs.append(config) + + return configs + + +# Configuration object +class AppConfig: + + # Model configuration + OLLAMA_MODEL = "llama3.2:3b" + OLLAMA_EMBEDDING_MODEL = "nomic-embed-text" + OLLAMA_URL = "http://host.docker.internal:11434" + MAX_UPLOAD_LENGTH = 16 * 1024 * 1024 + + # LLM Configurations + LLM_AGENT_MODEL = "meta-llama/Llama-3.3-70B-Instruct-Turbo" # Together AI + LLM_DELEGATOR_MODEL = "llama-3.3-70b" # Cerebras + + +# Get environment +env = os.environ.get("ENV", "dev") + +if env == "dev": + LLM_AGENT = ChatOllama( + model=AppConfig.OLLAMA_MODEL, + base_url=AppConfig.OLLAMA_URL, + temperature=0.7, + ) + + LLM_DELEGATOR = ChatOllama( + model=AppConfig.OLLAMA_MODEL, + base_url=AppConfig.OLLAMA_URL, + ) + + embeddings = OllamaEmbeddings( + model=AppConfig.OLLAMA_EMBEDDING_MODEL, + base_url=AppConfig.OLLAMA_URL, + ) +else: + LLM_AGENT = ChatTogether( + api_key=get_secret("TogetherApiKey"), + model=AppConfig.LLM_AGENT_MODEL, + temperature=0.7, + ) + + LLM_DELEGATOR = ChatCerebras( + api_key=get_secret("CerebrasApiKey"), + model=AppConfig.LLM_DELEGATOR_MODEL, + ) + + embeddings = TogetherEmbeddings( + model_name="togethercomputer/m2-bert-80M-8k-retrieval", + api_key=get_secret("TogetherApiKey"), + ) + +# Vector store path for persistence +VECTOR_STORE_PATH = os.path.join(os.getcwd(), "data", "vector_store") + +# Initialize vector store service and load existing store if it exists +RAG_VECTOR_STORE = VectorStoreService(embeddings) +if os.path.exists(VECTOR_STORE_PATH): + logger.info(f"Loading existing vector store from {VECTOR_STORE_PATH}") + try: + RAG_VECTOR_STORE = VectorStoreService.load(VECTOR_STORE_PATH, embeddings) + except Exception as e: + logger.error(f"Failed to load vector store: {str(e)}") + # Continue with empty vector store if load fails + RAG_VECTOR_STORE = VectorStoreService(embeddings) diff --git a/build_assets/windows/__init__.py b/submodules/agents/src/controllers/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from build_assets/windows/__init__.py rename to submodules/agents/src/controllers/__init__.py diff --git a/submodules/agents/src/controllers/delegation_controller.py b/submodules/agents/src/controllers/delegation_controller.py new file mode 100755 index 00000000..c38b1272 --- /dev/null +++ b/submodules/agents/src/controllers/delegation_controller.py @@ -0,0 +1,110 @@ +from typing import Optional +from fastapi import HTTPException +from fastapi.responses import JSONResponse +from models.service.chat_models import ChatRequest, AgentResponse +from stores import agent_manager_instance +from services.delegator.delegator import Delegator +from config import setup_logging, LLM_DELEGATOR +from models.service.service_models import GenerateConversationTitleRequest +from langchain.schema import SystemMessage + +logger = setup_logging() + + +class DelegationController: + def __init__(self, delegator: Optional[Delegator] = None): + self.delegator = delegator + + async def handle_chat(self, chat_request: ChatRequest) -> JSONResponse: + """Handle chat requests and delegate to appropriate agent""" + logger.info(f"Received chat request for conversation {chat_request.conversation_id}") + + assert self.delegator is not None + logger.info(f"Delegator: {self.delegator}") + + try: + # Parse command if present + agent_name, message = agent_manager_instance.parse_command(chat_request.prompt.content) + + if agent_name: + agent_manager_instance.set_active_agent(agent_name) + chat_request.prompt.content = message + else: + agent_manager_instance.clear_active_agent() + + # If command was parsed, use that agent directly + if agent_name: + logger.info(f"Using command agent flow: {agent_name}") + agent = agent_manager_instance.get_agent(agent_name) + if not agent: + logger.error(f"Agent {agent_name} not found") + raise HTTPException(status_code=404, detail=f"Agent {agent_name} not found") + + agent_response = await agent.chat(chat_request) + current_agent = agent_name + + # Otherwise use delegator to find appropriate agent + else: + logger.info("Using delegator flow") + # self.delegator.reset_attempted_agents() + current_agent, agent_response = await self.delegator.delegate_chat(chat_request) + + # We only critically fail if we don't get an AgentResponse + if not isinstance(agent_response, AgentResponse): + logger.error(f"Agent {current_agent} returned invalid response type {type(agent_response)}") + raise HTTPException(status_code=500, detail="Agent returned invalid response type") + + response = agent_response.to_chat_message(current_agent).model_dump() + logger.info(f"Sending response: {response}") + return JSONResponse(content=response) + + except HTTPException: + raise + except TimeoutError: + logger.error("Chat request timed out") + raise HTTPException(status_code=504, detail="Request timed out") + except ValueError as ve: + logger.error(f"Input formatting error: {str(ve)}") + raise HTTPException(status_code=400, detail=str(ve)) + except Exception as e: + logger.error(f"Error in chat route: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + async def generate_conversation_title(self, request: GenerateConversationTitleRequest) -> str: + """Generate a title for a conversation based on chat history""" + system_prompt = """You are a helpful assistant that generates concise, descriptive titles for conversations. + Generate a short title (3-6 words) that captures the main topic or theme of the conversation. + The title should be clear and informative but not too long. DO NOT SURROUND THE TITLE WITH QUOTES, spaces, + or any other characters. Just return the title as a string.""" + + messages = [ + SystemMessage(content=system_prompt), + *request.messages_for_llm, + ] + + logger.info(f"Generating title with messages: {messages}") + + try: + for attempt in range(3): + try: + result = LLM_DELEGATOR.invoke(messages) + if not result: + continue + + title = result.content.strip() + if not title: + continue + + logger.info(f"Generated title: {title}") + return title + + except Exception as e: + logger.warning(f"Title generation attempt {attempt+1} failed: {str(e)}") + if attempt == 2: + raise + + raise Exception("All title generation attempts failed") + + except Exception as e: + logger.error(f"Error generating title: {str(e)}") + raise HTTPException(status_code=500, detail=f"Failed to generate title: {str(e)}") diff --git a/submodules/agents/src/controllers/user_controller.py b/submodules/agents/src/controllers/user_controller.py new file mode 100644 index 00000000..fa51470f --- /dev/null +++ b/submodules/agents/src/controllers/user_controller.py @@ -0,0 +1,143 @@ +import logging +from typing import Optional, List, Dict, Any, Self + +from sqlalchemy.orm import Session + +from models.session import DBSessionFactory +from models.daos.user_dao import UserDAO +from models.service.user_service_models import UserModel, UserSettingModel + +logger = logging.getLogger(__name__) + + +class UserController: + """Controller for managing users and user settings.""" + + def __init__(self, session: Optional[Session] = None, auto_close_session: Optional[bool] = None): + self._auto_close_session = (session is None) if (auto_close_session is None) else auto_close_session + self._session: Optional[Session] = session + + def __enter__(self) -> Self: + """Context manager entry point.""" + if self._session is None: + self._session = DBSessionFactory.get_instance().new_session() + return self + + def __exit__(self, exc_type: Optional[type], exc_value: Optional[Exception], traceback: Optional[Any]) -> None: + """Context manager exit point.""" + if self._auto_close_session and self._session: + self._session.close() + + # ************ + # User Methods + # ************ + + def get_user(self, user_id: int) -> Optional[UserModel]: + """Get a user by ID.""" + if not self._session: + return None + user_dao = UserDAO(self._session) + user = user_dao.get_by_id(user_id) + if user: + return user.to_service_model() + return None + + def get_user_by_wallet(self, wallet_address: str) -> Optional[UserModel]: + """Get a user by wallet address.""" + if not self._session: + return None + user_dao = UserDAO(self._session) + user = user_dao.get_by_wallet_address(wallet_address) + if user: + return user.to_service_model() + return None + + def list_users(self) -> List[UserModel]: + """List all users.""" + if not self._session: + return [] + user_dao = UserDAO(self._session) + users = user_dao.list_all() + return [user.to_service_model() for user in users] + + def create_user(self, wallet_address: str) -> UserModel: + """Create a new user.""" + if not self._session: + raise RuntimeError("No database session available") + user_dao = UserDAO(self._session) + user = user_dao.create(wallet_address) + return user.to_service_model() + + def update_user(self, user_id: int, wallet_address: str) -> Optional[UserModel]: + """Update an existing user.""" + if not self._session: + return None + user_dao = UserDAO(self._session) + user = user_dao.update(user_id, wallet_address) + if user: + return user.to_service_model() + return None + + def delete_user(self, user_id: int) -> bool: + """Delete a user.""" + if not self._session: + return False + user_dao = UserDAO(self._session) + return user_dao.delete(user_id) + + # ********************* + # User Settings Methods + # ********************* + + def get_setting(self, user_id: int, settings_key: str) -> Optional[UserSettingModel]: + """Get a setting by key for a specific user.""" + if not self._session: + return None + user_dao = UserDAO(self._session) + setting = user_dao.get_setting(user_id, settings_key) + if setting: + return setting.to_service_model() + return None + + def list_user_settings(self, user_id: int) -> List[UserSettingModel]: + """List all settings for a user.""" + if not self._session: + return [] + user_dao = UserDAO(self._session) + settings = user_dao.get_all_settings(user_id) + return [setting.to_service_model() for setting in settings] + + def create_setting(self, user_id: int, settings_key: str, settings_value: Dict[str, Any]) -> UserSettingModel: + """Create a new setting.""" + if not self._session: + raise RuntimeError("No database session available") + user_dao = UserDAO(self._session) + setting = user_dao.create_setting(user_id, settings_key, settings_value) + return setting.to_service_model() + + def update_setting( + self, user_id: int, settings_key: str, settings_value: Dict[str, Any] + ) -> Optional[UserSettingModel]: + """Update an existing setting.""" + if not self._session: + return None + user_dao = UserDAO(self._session) + setting = user_dao.update_setting(user_id, settings_key, settings_value) + if setting: + return setting.to_service_model() + return None + + def upsert_setting(self, user_id: int, settings_key: str, settings_value: Dict[str, Any]) -> UserSettingModel: + """Create or update a setting.""" + if not self._session: + raise RuntimeError("No database session available") + user_dao = UserDAO(self._session) + setting = user_dao.upsert_setting(user_id, settings_key, settings_value) + return setting.to_service_model() + + def delete_setting(self, user_id: int, settings_key: str) -> bool: + """Delete a setting.""" + if not self._session: + return False + user_dao = UserDAO(self._session) + return user_dao.delete_setting(user_id, settings_key) diff --git a/submodules/agents/src/logs.py b/submodules/agents/src/logs.py new file mode 100644 index 00000000..4c99f140 --- /dev/null +++ b/submodules/agents/src/logs.py @@ -0,0 +1,27 @@ +import logging +import sys + + +def setup_logging() -> logging.Logger: + """ + Setup logging for the application. + """ + # Create formatter + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + + # Setup console handler + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(logging.INFO) + console_handler.setFormatter(formatter) + + # Get the root logger + root_logger = logging.getLogger() + root_logger.setLevel(logging.INFO) + + # Remove any existing handlers + root_logger.handlers = [] + + # Add our handler + root_logger.addHandler(console_handler) + + return root_logger diff --git a/submodules/agents/src/models/__init__.py b/submodules/agents/src/models/__init__.py new file mode 100755 index 00000000..0fd6bbe4 --- /dev/null +++ b/submodules/agents/src/models/__init__.py @@ -0,0 +1,10 @@ +"""Models package.""" + +from sqlalchemy.orm import declarative_base + +Base = declarative_base() +metadata = Base.metadata + +from models.core.user_models import User, UserSetting + +__all__ = ["Base", "User", "UserSetting"] diff --git a/submodules/agents/src/models/config/config-prod.ini b/submodules/agents/src/models/config/config-prod.ini new file mode 100755 index 00000000..87d05bd5 --- /dev/null +++ b/submodules/agents/src/models/config/config-prod.ini @@ -0,0 +1,8 @@ +[default] +logging_config = logging-prod.ini +host = https://api.mysuperagent.io +port = 8888 +workers = 1 +reload = false +cors_allowed_origins = https://mysuperagent.io +base_url = https://localhost:8888 diff --git a/submodules/agents/src/models/config/config.ini b/submodules/agents/src/models/config/config.ini new file mode 100755 index 00000000..c65ee333 --- /dev/null +++ b/submodules/agents/src/models/config/config.ini @@ -0,0 +1,17 @@ +[default] +logging_config = logging.ini +host = 0.0.0.0 +port = 8888 +workers = 1 +reload = true +cors_allowed_origins = http://localhost:3333 +base_url = https://localhost:8888 + +[db] +db_url = postgresql+psycopg2://neo:trinity@localhost:5678/morpheus_db + +[integrations] +; TogetherApiKey = "mock-key" +; CerebrasApiKey = "mock-key" +; CodexApiKey = "mock-key" +; ElfaApiKey = "mock-key" diff --git a/submodules/agents/src/models/config/config.py b/submodules/agents/src/models/config/config.py new file mode 100644 index 00000000..7890c941 --- /dev/null +++ b/submodules/agents/src/models/config/config.py @@ -0,0 +1,112 @@ +import logging +import logging.config +import os +from pathlib import Path + +from configparser import ConfigParser + +# Get the absolute path to the config directory +BASE_DIR = Path(os.path.dirname(os.path.abspath(__file__))) + + +class Config: + """ + wrapper class for python ConfigParser class. will check environment variables + for config values before checking config file properties + """ + + _instance = None + + def __new__(cls): + """Singleton like pattern""" + if not cls._instance: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self): + """Initialize Config object""" + self._env_vars = dict(os.environ.copy()) + self._load() + + @classmethod + def get_instance(cls): + """Singleton like pattern""" + if not cls._instance: + cls._instance = Config() + return cls._instance + + def _load(self): + """Load config from config file and environment variables""" + # load base config + conf_parser = ConfigParser() + base_config = BASE_DIR / "config.ini" + print(f"loading base config: {base_config}") + with open(base_config, "r", encoding="utf-8") as handle: + conf_parser.read_file(handle) + + # load env specific config + env = os.environ.get("ENV", "dev") + env_conf = BASE_DIR / f"config-{env.lower()}.ini" + if env_conf.is_file(): + print(f"loading config: {env_conf}") + with open(env_conf, "r", encoding="utf-8") as handle: + conf_parser.read_file(handle) + + # load local config if set + local_conf_file = os.environ.get("LOCAL_CONF", BASE_DIR / "local.conf") + if Path(local_conf_file).is_file(): + print(f"loading additional config: {local_conf_file}") + with open(local_conf_file, "r", encoding="utf-8") as handle: + conf_parser.read_file(handle) + + self._conf = conf_parser + self._app_config_init() + + def _app_config_init(self): + """Initialize app config""" + logging.config.fileConfig(BASE_DIR / self.get("logging_config")) + + # disable noisy loggers + logging.getLogger("google.cloud.pubsub_v1").setLevel(logging.WARNING) + + def has(self, key: str, section: str = "default") -> bool: + """Check if key exists in environment variables or config section""" + # Check env vars first + if key in self._env_vars: + return True + + # Then check config section + try: + return key in self._conf[section] + except KeyError: + return False + + def get(self, key, section="default", type_conv=lambda x: x): + """Get config value for key, section. If not found in config file, check environment variables""" + value = self._env_vars.get(key) + + if not value: + value = self._conf[section][key] + + return type_conv(value) + + def get_bytes(self, key, section="default") -> bytes: + """Get config value for key, section and cast to bytes""" + return self.get(key, section, lambda s: s.encode("utf-8")) + + def get_bool(self, key, section="default") -> bool: + """Get config value for key, section and cast to boolean""" + return self.get(key, section, lambda s: s.lower() in ["true", "1"]) + + def get_int(self, key, section="default") -> int: + """Get config value for key, section and cast to integer""" + return self.get(key, section, int) + + def get_float(self, key, section="default") -> float: + """Get config value for key, section and cast to float""" + return self.get(key, section, float) + + def get_list(self, key, section="default", sep=",") -> list[str]: + """Get config value for key, section and cast to list""" + str_list = self.get(key, section).strip() + return [] if str_list == "" else str_list.split(sep) diff --git a/submodules/agents/src/models/config/logging-prod.ini b/submodules/agents/src/models/config/logging-prod.ini new file mode 100644 index 00000000..546ee513 --- /dev/null +++ b/submodules/agents/src/models/config/logging-prod.ini @@ -0,0 +1,20 @@ +[loggers] +keys=root + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=DEBUG +handlers=consoleHandler + +[handler_consoleHandler] +class=StreamHandler +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(filename)s - %(levelname)s - %(message)s` \ No newline at end of file diff --git a/submodules/agents/src/models/config/logging.ini b/submodules/agents/src/models/config/logging.ini new file mode 100644 index 00000000..afbedf38 --- /dev/null +++ b/submodules/agents/src/models/config/logging.ini @@ -0,0 +1,20 @@ +[loggers] +keys=root + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=DEBUG +handlers=consoleHandler + +[handler_consoleHandler] +class=StreamHandler +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s - %(filename)s - %(levelname)s - %(message)s \ No newline at end of file diff --git a/submodules/__init__.py b/submodules/agents/src/models/core/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/__init__.py rename to submodules/agents/src/models/core/__init__.py diff --git a/submodules/agents/src/models/core/user_models.py b/submodules/agents/src/models/core/user_models.py new file mode 100644 index 00000000..a58deee3 --- /dev/null +++ b/submodules/agents/src/models/core/user_models.py @@ -0,0 +1,78 @@ +from datetime import datetime +from typing import List + +from sqlalchemy import JSON, DateTime, String, Integer, ForeignKey, func, UniqueConstraint +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from models import Base +from models.service.user_service_models import UserModel, UserSettingModel + + +class User(Base): + """SQLAlchemy model for users""" + + __tablename__ = "users" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + wallet_address: Mapped[str] = mapped_column(String(255), nullable=False, unique=True) + created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) + updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) + + # Relationships + settings: Mapped[List["UserSetting"]] = relationship( + "UserSetting", back_populates="user", cascade="all, delete-orphan" + ) + + def to_service_model(self) -> UserModel: + """Convert to service model""" + return UserModel( + id=self.id, wallet_address=self.wallet_address, created_at=self.created_at, updated_at=self.updated_at + ) + + @classmethod + def from_service_model(cls, model: UserModel) -> "User": + """Create from service model""" + return cls( + id=model.id, wallet_address=model.wallet_address, created_at=model.created_at, updated_at=model.updated_at + ) + + +class UserSetting(Base): + """SQLAlchemy model for user settings""" + + __tablename__ = "user_settings" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False) + settings_key: Mapped[str] = mapped_column(String(255), nullable=False) + settings_value: Mapped[JSON] = mapped_column(JSON, nullable=False) + created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) + updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) + + # Relationships + user: Mapped["User"] = relationship("User", back_populates="settings") + + __table_args__ = (UniqueConstraint("user_id", "settings_key", name="uix_user_settings"),) + + def to_service_model(self) -> UserSettingModel: + """Convert to service model""" + return UserSettingModel( + id=self.id, + user_id=self.user_id, + settings_key=self.settings_key, + settings_value=self.settings_value, + created_at=self.created_at, + updated_at=self.updated_at, + ) + + @classmethod + def from_service_model(cls, model: UserSettingModel) -> "UserSetting": + """Create from service model""" + return cls( + id=model.id, + user_id=model.user_id, + settings_key=model.settings_key, + settings_value=model.settings_value, + created_at=model.created_at, + updated_at=model.updated_at, + ) diff --git a/submodules/moragents_dockers/__init__.py b/submodules/agents/src/models/daos/__init__.py similarity index 100% rename from submodules/moragents_dockers/__init__.py rename to submodules/agents/src/models/daos/__init__.py diff --git a/submodules/agents/src/models/daos/user_dao.py b/submodules/agents/src/models/daos/user_dao.py new file mode 100644 index 00000000..7c131e79 --- /dev/null +++ b/submodules/agents/src/models/daos/user_dao.py @@ -0,0 +1,88 @@ +from typing import List, Optional, Dict, Any +from datetime import datetime +from sqlalchemy import select +from sqlalchemy.orm import Session + +from models.core.user_models import User, UserSetting + + +class UserDAO: + def __init__(self, session: Session): + self.session = session + + def create(self, wallet_address: str) -> User: + user = User(wallet_address=wallet_address) + self.session.add(user) + self.session.commit() + return user + + def get_by_id(self, user_id: int) -> Optional[User]: + return self.session.get(User, user_id) + + def get_by_wallet_address(self, wallet_address: str) -> Optional[User]: + stmt = select(User).where(User.wallet_address == wallet_address) + result = self.session.execute(stmt).scalar_one_or_none() + return result + + def update(self, user_id: int, wallet_address: str) -> Optional[User]: + user = self.get_by_id(user_id) + if user: + user.wallet_address = wallet_address + user.updated_at = datetime.utcnow() + self.session.commit() + return user + + def delete(self, user_id: int) -> bool: + user = self.get_by_id(user_id) + if user: + self.session.delete(user) + self.session.commit() + return True + return False + + def list_all(self) -> List[User]: + stmt = select(User) + return list(self.session.execute(stmt).scalars()) + + # User Settings Methods + def get_setting(self, user_id: int, settings_key: str) -> Optional[UserSetting]: + stmt = select(UserSetting).where(UserSetting.user_id == user_id, UserSetting.settings_key == settings_key) + return self.session.execute(stmt).scalar_one_or_none() + + def get_all_settings(self, user_id: int) -> List[UserSetting]: + stmt = select(UserSetting).where(UserSetting.user_id == user_id) + return list(self.session.execute(stmt).scalars()) + + def create_setting(self, user_id: int, settings_key: str, settings_value: Dict[str, Any]) -> UserSetting: + setting = UserSetting(user_id=user_id, settings_key=settings_key, settings_value=settings_value) + self.session.add(setting) + self.session.commit() + return setting + + def update_setting(self, user_id: int, settings_key: str, settings_value: Dict[str, Any]) -> Optional[UserSetting]: + setting = self.get_setting(user_id, settings_key) + if setting: + setting.settings_value = settings_value + setting.updated_at = datetime.utcnow() + self.session.commit() + return setting + return None + + def upsert_setting(self, user_id: int, settings_key: str, settings_value: Dict[str, Any]) -> UserSetting: + setting = self.get_setting(user_id, settings_key) + if setting: + setting.settings_value = settings_value + setting.updated_at = datetime.utcnow() + else: + setting = UserSetting(user_id=user_id, settings_key=settings_key, settings_value=settings_value) + self.session.add(setting) + self.session.commit() + return setting + + def delete_setting(self, user_id: int, settings_key: str) -> bool: + setting = self.get_setting(user_id, settings_key) + if setting: + self.session.delete(setting) + self.session.commit() + return True + return False diff --git a/submodules/agents/src/models/models.py b/submodules/agents/src/models/models.py new file mode 100644 index 00000000..8c13254f --- /dev/null +++ b/submodules/agents/src/models/models.py @@ -0,0 +1,4 @@ +# pylint: skip-file + +from models import Base +from .core import * diff --git a/submodules/moragents_dockers/agents/__init__.py b/submodules/agents/src/models/service/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/__init__.py rename to submodules/agents/src/models/service/__init__.py diff --git a/submodules/agents/src/models/service/agent_config.py b/submodules/agents/src/models/service/agent_config.py new file mode 100755 index 00000000..b19f6ac5 --- /dev/null +++ b/submodules/agents/src/models/service/agent_config.py @@ -0,0 +1,25 @@ +from pydantic import BaseModel + + +class AgentConfig(BaseModel): + """ + Configuration model for agents in the system. + + Attributes: + path (str): Import path to the agent module + class_name (str): Name of the agent class to instantiate + description (str): Description of what the agent does + delegator_description (str): Description of what the agent does for use in the delegator + human_readable_name (str): User-friendly display name for the agent + command (str): Command used to invoke this agent (e.g. "dexscreener") + upload_required (bool): Whether this agent requires file uploads to function + """ + + path: str + class_name: str + description: str + delegator_description: str + human_readable_name: str + command: str + upload_required: bool = False + is_enabled: bool = True diff --git a/submodules/moragents_dockers/agents/src/agents/agent_core/agent.py b/submodules/agents/src/models/service/agent_core.py old mode 100644 new mode 100755 similarity index 91% rename from submodules/moragents_dockers/agents/src/agents/agent_core/agent.py rename to submodules/agents/src/models/service/agent_core.py index 14b7e73e..c035a2eb --- a/submodules/moragents_dockers/agents/src/agents/agent_core/agent.py +++ b/submodules/agents/src/models/service/agent_core.py @@ -1,18 +1,18 @@ import logging - from abc import ABC, abstractmethod -from enum import Enum -from typing import Dict, Any, Optional +from typing import Dict, Any, Optional, Callable, TypeVar, Awaitable from functools import wraps -from src.models.core import ChatRequest, AgentResponse +from models.service.chat_models import ChatRequest, AgentResponse + +T = TypeVar("T") -def handle_exceptions(func): +def handle_exceptions(func: Callable[..., Awaitable[AgentResponse]]) -> Callable[..., Awaitable[AgentResponse]]: """Decorator to handle exceptions uniformly across agent methods""" @wraps(func) - async def wrapper(self, *args, **kwargs): + async def wrapper(self: Any, *args: Any, **kwargs: Any) -> AgentResponse: try: return await func(self, *args, **kwargs) except ValueError as e: @@ -30,13 +30,12 @@ async def wrapper(self, *args, **kwargs): class AgentCore(ABC): """Enhanced core agent functionality that all specialized agents inherit from.""" - def __init__(self, config: Dict[str, Any], llm: Any, embeddings: Any): + def __init__(self, config: Dict[str, Any], llm: Any): self.config = config self.llm = llm - self.embeddings = embeddings self._setup_logging() - def _setup_logging(self): + def _setup_logging(self) -> None: """Set up logging for the agent""" self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}") diff --git a/submodules/moragents_dockers/agents/src/models/core.py b/submodules/agents/src/models/service/chat_models.py old mode 100644 new mode 100755 similarity index 76% rename from submodules/moragents_dockers/agents/src/models/core.py rename to submodules/agents/src/models/service/chat_models.py index 30995b76..73745855 --- a/submodules/moragents_dockers/agents/src/models/core.py +++ b/submodules/agents/src/models/service/chat_models.py @@ -3,6 +3,7 @@ from typing import List, Optional, Dict, Any from fastapi import Query from pydantic import BaseModel, Field +from langchain.schema import BaseMessage, HumanMessage, AIMessage class ResponseType(Enum): @@ -44,6 +45,24 @@ class ChatRequest(BaseModel): chain_id: str wallet_address: str conversation_id: str = Query(default="default") + chat_history: List[ChatMessage] = Field(default_factory=list) + + @property + def messages_for_llm(self) -> List[BaseMessage]: + """Get formatted message history for LLM, including current prompt""" + messages: List[BaseMessage] = [] + + # Add chat history messages up to the past 5 messages + for i, msg in enumerate(reversed(self.chat_history[:5])): + if msg.role == "user": + messages.append(HumanMessage(content=f"Chat History index {i}: {msg.content}")) + elif msg.role == "assistant": + messages.append(AIMessage(content=f"Chat History index {i}: {msg.content}")) + + # Add current prompt + messages.append(HumanMessage(content=f"Current Prompt: {self.prompt.content}")) + + return messages class Conversation(BaseModel): @@ -78,9 +97,13 @@ def to_chat_message(self, agent_name: str) -> ChatMessage: ) @classmethod - def success(cls, content: str, metadata: Optional[Dict[str, Any]] = None) -> "AgentResponse": + def success( + cls, content: str, metadata: Optional[Dict[str, Any]] = None, action_type: Optional[str] = None + ) -> "AgentResponse": """Create a successful response""" - return cls(response_type=ResponseType.SUCCESS, content=content, metadata=metadata or {}) + return cls( + response_type=ResponseType.SUCCESS, content=content, metadata=metadata or {}, action_type=action_type + ) @classmethod def error(cls, error_message: str) -> "AgentResponse": diff --git a/submodules/agents/src/models/service/service_models.py b/submodules/agents/src/models/service/service_models.py new file mode 100644 index 00000000..a0690dba --- /dev/null +++ b/submodules/agents/src/models/service/service_models.py @@ -0,0 +1,25 @@ +from typing import List +from pydantic import BaseModel, Field +from langchain.schema import BaseMessage, HumanMessage, AIMessage +from models.service.chat_models import ChatMessage + + +class GenerateConversationTitleRequest(BaseModel): + """Request model for generating a conversation title""" + + conversation_id: str = Field(default="default") + chat_history: List[ChatMessage] = Field(default_factory=list) + + @property + def messages_for_llm(self) -> List[BaseMessage]: + """Get formatted message history for LLM""" + messages: List[BaseMessage] = [] + + # Add chat history messages up to the past 10 messages + for msg in self.chat_history[:10]: + if msg.role == "user": + messages.append(HumanMessage(content=msg.content)) + elif msg.role == "assistant": + messages.append(AIMessage(content=msg.content)) + + return messages diff --git a/submodules/agents/src/models/service/user_service_models.py b/submodules/agents/src/models/service/user_service_models.py new file mode 100644 index 00000000..d864f11b --- /dev/null +++ b/submodules/agents/src/models/service/user_service_models.py @@ -0,0 +1,19 @@ +from typing import Optional, Dict, Any +from pydantic import BaseModel +from datetime import datetime + + +class UserModel(BaseModel): + id: Optional[int] = None + wallet_address: str + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + +class UserSettingModel(BaseModel): + id: Optional[int] = None + user_id: int + settings_key: str + settings_value: Dict[str, Any] + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None diff --git a/submodules/agents/src/models/session.py b/submodules/agents/src/models/session.py new file mode 100644 index 00000000..5c7aac3f --- /dev/null +++ b/submodules/agents/src/models/session.py @@ -0,0 +1,69 @@ +"""db session factory""" + +import re +import logging + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, Session +from sqlalchemy.pool import StaticPool +from sqlalchemy.exc import SQLAlchemyError + +from agents.src.models.config.config import Config + +logger = logging.getLogger(__name__) + + +class DBSessionFactory: + """singleton db session factory""" + + _instance = None + + def __new__(cls): + """singleton instance""" + if not cls._instance: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self) -> None: + """initialize db session factory""" + conf = Config.get_instance() + db_url = conf.get("db_url", "db") + logging.debug(f"connecting to db: db_url={sanitize_db_url(db_url)}") + + if db_url.startswith("sqlite:///:memory:"): + engine = create_engine(db_url, connect_args={"check_same_thread": False}, poolclass=StaticPool) + else: + engine = create_engine(db_url) + + self._session_factory = sessionmaker(bind=engine, autocommit=False, autoflush=False) + self._engine = engine + + def new_session(self) -> Session: + """create new session""" + return self._session_factory() + + def check_health(self): + """check db connection health""" + try: + with self.new_session() as session: + session.execute("SELECT 1") + return True, None + except SQLAlchemyError as err: + return False, str(err) + + @property + def engine(self): + """get sqlalchemy engine""" + return self._engine + + @classmethod + def get_instance(cls): + """get singleton instance""" + if not cls._instance: + cls._instance = DBSessionFactory() + return cls._instance + + +def sanitize_db_url(url): + """sanitize db url for logging""" + return re.sub(r"//.*@", "//****@", url) diff --git a/submodules/moragents_dockers/agents/src/__init__.py b/submodules/agents/src/py.typed similarity index 100% rename from submodules/moragents_dockers/agents/src/__init__.py rename to submodules/agents/src/py.typed diff --git a/submodules/moragents_dockers/agents/src/agents/__init__.py b/submodules/agents/src/routes/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/__init__.py rename to submodules/agents/src/routes/__init__.py diff --git a/submodules/moragents_dockers/agents/src/routes/agent_manager_routes.py b/submodules/agents/src/routes/agent_manager_routes.py old mode 100644 new mode 100755 similarity index 96% rename from submodules/moragents_dockers/agents/src/routes/agent_manager_routes.py rename to submodules/agents/src/routes/agent_manager_routes.py index 6b40af49..08556db5 --- a/submodules/moragents_dockers/agents/src/routes/agent_manager_routes.py +++ b/submodules/agents/src/routes/agent_manager_routes.py @@ -1,7 +1,7 @@ import logging from fastapi import APIRouter, Request from fastapi.responses import JSONResponse -from src.stores import agent_manager_instance +from stores import agent_manager_instance logger = logging.getLogger(__name__) diff --git a/submodules/agents/src/routes/delegation_routes.py b/submodules/agents/src/routes/delegation_routes.py new file mode 100755 index 00000000..4f608c44 --- /dev/null +++ b/submodules/agents/src/routes/delegation_routes.py @@ -0,0 +1,62 @@ +from fastapi import APIRouter, HTTPException +from fastapi.responses import JSONResponse +from models.service.chat_models import ChatRequest +from services.delegator.delegator import Delegator +from controllers.delegation_controller import DelegationController +from config import LLM_DELEGATOR, setup_logging +from models.service.service_models import GenerateConversationTitleRequest + +logger = setup_logging() + +router = APIRouter(prefix="/api/v1", tags=["chat"]) + + +@router.post("/chat") +async def chat(chat_request: ChatRequest) -> JSONResponse: + """Handle chat requests and delegate to appropriate agent""" + logger.info(f"Received chat request for conversation {chat_request.conversation_id}") + + # Initialize new delegator and controller for each request + delegator = Delegator(LLM_DELEGATOR) + controller = DelegationController(delegator) + + try: + response = await controller.handle_chat(chat_request) + return response + + except HTTPException: + raise + except TimeoutError: + logger.error("Chat request timed out") + raise HTTPException(status_code=504, detail="Request timed out") + except ValueError as ve: + logger.error(f"Input formatting error: {str(ve)}") + raise HTTPException(status_code=400, detail=str(ve)) + except Exception as e: + logger.error(f"Error in chat route: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/generate-title") +async def generate_conversation_title(request: GenerateConversationTitleRequest) -> JSONResponse: + """Generate a title for a conversation based on chat history""" + logger.info(f"Received title generation request for conversation {request.conversation_id}") + + # Initialize new delegator and controller for each request + controller = DelegationController() + + try: + title = await controller.generate_conversation_title(request) + return JSONResponse(content={"title": title}) + + except HTTPException: + raise + except TimeoutError: + logger.error("Title generation request timed out") + raise HTTPException(status_code=504, detail="Request timed out") + except ValueError as ve: + logger.error(f"Input formatting error: {str(ve)}") + raise HTTPException(status_code=400, detail=str(ve)) + except Exception as e: + logger.error(f"Error in generate title route: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) diff --git a/submodules/agents/src/routes/user_routes.py b/submodules/agents/src/routes/user_routes.py new file mode 100644 index 00000000..403d70ac --- /dev/null +++ b/submodules/agents/src/routes/user_routes.py @@ -0,0 +1,203 @@ +from fastapi import APIRouter, HTTPException +from fastapi.responses import JSONResponse +from typing import List, Dict, Any +from agents.src.models.service.user_service_models import UserModel, UserSettingModel +from controllers.user_controller import UserController +from config import setup_logging + +logger = setup_logging() + +router = APIRouter(prefix="/api/v1", tags=["users"]) + + +@router.get("/users/{user_id}") +async def get_user(user_id: int) -> JSONResponse: + """Get a user by ID""" + logger.info(f"Received request to get user {user_id}") + + try: + with UserController() as controller: + user = controller.get_user(user_id) + if not user: + raise HTTPException(status_code=404, detail=f"User {user_id} not found") + return JSONResponse(content=user.model_dump()) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error getting user: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/users/wallet/{wallet_address}") +async def get_user_by_wallet(wallet_address: str) -> JSONResponse: + """Get a user by wallet address""" + logger.info(f"Received request to get user by wallet {wallet_address}") + + try: + with UserController() as controller: + user = controller.get_user_by_wallet(wallet_address) + if not user: + raise HTTPException(status_code=404, detail=f"User with wallet {wallet_address} not found") + return JSONResponse(content=user.model_dump()) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error getting user by wallet: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/users") +async def list_users() -> JSONResponse: + """List all users""" + logger.info("Received request to list all users") + + try: + with UserController() as controller: + users = controller.list_users() + return JSONResponse(content=[user.model_dump() for user in users]) + + except Exception as e: + logger.error(f"Error listing users: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/users") +async def create_user(wallet_address: str) -> JSONResponse: + """Create a new user""" + logger.info(f"Received request to create user with wallet {wallet_address}") + + try: + with UserController() as controller: + user = controller.create_user(wallet_address) + return JSONResponse(content=user.model_dump(), status_code=201) + + except Exception as e: + logger.error(f"Error creating user: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.put("/users/{user_id}") +async def update_user(user_id: int, wallet_address: str) -> JSONResponse: + """Update an existing user""" + logger.info(f"Received request to update user {user_id}") + + try: + with UserController() as controller: + user = controller.update_user(user_id, wallet_address) + if not user: + raise HTTPException(status_code=404, detail=f"User {user_id} not found") + return JSONResponse(content=user.model_dump()) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error updating user: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.delete("/users/{user_id}") +async def delete_user(user_id: int) -> JSONResponse: + """Delete a user""" + logger.info(f"Received request to delete user {user_id}") + + try: + with UserController() as controller: + success = controller.delete_user(user_id) + if not success: + raise HTTPException(status_code=404, detail=f"User {user_id} not found") + return JSONResponse(content={"status": "success"}) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error deleting user: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/users/{user_id}/settings/{settings_key}") +async def get_user_setting(user_id: int, settings_key: str) -> JSONResponse: + """Get a user setting by key""" + logger.info(f"Received request to get setting {settings_key} for user {user_id}") + + try: + with UserController() as controller: + setting = controller.get_setting(user_id, settings_key) + if not setting: + raise HTTPException(status_code=404, detail=f"Setting {settings_key} not found for user {user_id}") + return JSONResponse(content=setting.model_dump()) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error getting user setting: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/users/{user_id}/settings") +async def list_user_settings(user_id: int) -> JSONResponse: + """List all settings for a user""" + logger.info(f"Received request to list settings for user {user_id}") + + try: + with UserController() as controller: + settings = controller.list_user_settings(user_id) + return JSONResponse(content=[setting.model_dump() for setting in settings]) + + except Exception as e: + logger.error(f"Error listing user settings: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/users/{user_id}/settings/{settings_key}") +async def create_user_setting(user_id: int, settings_key: str, settings_value: Dict[str, Any]) -> JSONResponse: + """Create a new user setting""" + logger.info(f"Received request to create setting {settings_key} for user {user_id}") + + try: + with UserController() as controller: + setting = controller.create_setting(user_id, settings_key, settings_value) + return JSONResponse(content=setting.model_dump(), status_code=201) + + except Exception as e: + logger.error(f"Error creating user setting: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.put("/users/{user_id}/settings/{settings_key}") +async def update_user_setting(user_id: int, settings_key: str, settings_value: Dict[str, Any]) -> JSONResponse: + """Update an existing user setting""" + logger.info(f"Received request to update setting {settings_key} for user {user_id}") + + try: + with UserController() as controller: + setting = controller.update_setting(user_id, settings_key, settings_value) + if not setting: + raise HTTPException(status_code=404, detail=f"Setting {settings_key} not found for user {user_id}") + return JSONResponse(content=setting.model_dump()) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error updating user setting: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.delete("/users/{user_id}/settings/{settings_key}") +async def delete_user_setting(user_id: int, settings_key: str) -> JSONResponse: + """Delete a user setting""" + logger.info(f"Received request to delete setting {settings_key} for user {user_id}") + + try: + with UserController() as controller: + success = controller.delete_setting(user_id, settings_key) + if not success: + raise HTTPException(status_code=404, detail=f"Setting {settings_key} not found for user {user_id}") + return JSONResponse(content={"status": "success"}) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error deleting user setting: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) diff --git a/submodules/moragents_dockers/agents/src/routes/wallet_manager_routes.py b/submodules/agents/src/routes/wallet_manager_routes.py old mode 100644 new mode 100755 similarity index 89% rename from submodules/moragents_dockers/agents/src/routes/wallet_manager_routes.py rename to submodules/agents/src/routes/wallet_manager_routes.py index 113d5e66..f2ee2dce --- a/submodules/moragents_dockers/agents/src/routes/wallet_manager_routes.py +++ b/submodules/agents/src/routes/wallet_manager_routes.py @@ -1,7 +1,7 @@ import logging from fastapi import APIRouter, Request from fastapi.responses import JSONResponse -from src.stores import wallet_manager_instance +from stores import wallet_manager_instance logger = logging.getLogger(__name__) @@ -19,9 +19,7 @@ async def create_wallet(request: Request) -> JSONResponse: try: wallet = wallet_manager_instance.create_wallet(wallet_id, network_id, set_active) address = wallet.default_address.address_id - return JSONResponse( - content={"status": "success", "wallet_id": wallet_id, "address": address} - ) + return JSONResponse(content={"status": "success", "wallet_id": wallet_id, "address": address}) except Exception as e: logger.error(f"Failed to create wallet: {str(e)}") return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @@ -45,9 +43,7 @@ async def restore_wallet(request: Request) -> JSONResponse: wallet = wallet_manager_instance.restore_wallet(wallet_id, wallet_data, set_active) if wallet: address = wallet.default_address.address_id - return JSONResponse( - content={"status": "success", "wallet_id": wallet_id, "address": address} - ) + return JSONResponse(content={"status": "success", "wallet_id": wallet_id, "address": address}) return JSONResponse( status_code=500, content={"status": "error", "message": f"Failed to restore wallet {wallet_id}"}, @@ -112,9 +108,7 @@ async def load_wallet(request: Request) -> JSONResponse: wallet = wallet_manager_instance.load_wallet(wallet_id, filepath, set_active) if wallet: address = wallet.default_address.address_id - return JSONResponse( - content={"status": "success", "wallet_id": wallet_id, "address": address} - ) + return JSONResponse(content={"status": "success", "wallet_id": wallet_id, "address": address}) return JSONResponse( status_code=500, content={"status": "error", "message": f"Failed to load wallet {wallet_id}"}, @@ -147,16 +141,12 @@ async def set_active_wallet(request: Request) -> JSONResponse: wallet_id = data.get("wallet_id") if not wallet_id: - return JSONResponse( - status_code=400, content={"status": "error", "message": "Missing wallet_id"} - ) + return JSONResponse(status_code=400, content={"status": "error", "message": "Missing wallet_id"}) success = wallet_manager_instance.set_active_wallet(wallet_id) if success: address = wallet_manager_instance.get_wallet_address(wallet_id) - return JSONResponse( - content={"status": "success", "wallet_id": wallet_id, "address": address} - ) + return JSONResponse(content={"status": "success", "wallet_id": wallet_id, "address": address}) return JSONResponse( status_code=500, content={"status": "error", "message": f"Failed to set wallet {wallet_id} as active"}, diff --git a/submodules/moragents_dockers/agents/src/routes/workflow_manager_routes.py b/submodules/agents/src/routes/workflow_manager_routes.py old mode 100644 new mode 100755 similarity index 98% rename from submodules/moragents_dockers/agents/src/routes/workflow_manager_routes.py rename to submodules/agents/src/routes/workflow_manager_routes.py index 8c2c3cac..9c89970d --- a/submodules/moragents_dockers/agents/src/routes/workflow_manager_routes.py +++ b/submodules/agents/src/routes/workflow_manager_routes.py @@ -2,7 +2,7 @@ from datetime import timedelta from fastapi import APIRouter, Request from fastapi.responses import JSONResponse -from src.stores import workflow_manager_instance +from stores import workflow_manager_instance logger = logging.getLogger(__name__) diff --git a/submodules/agents/src/services/__init__.py b/submodules/agents/src/services/__init__.py new file mode 100755 index 00000000..1f555cfa --- /dev/null +++ b/submodules/agents/src/services/__init__.py @@ -0,0 +1,111 @@ +import logging +import logging.config +import os + +from configparser import ConfigParser +from typing import Any, Callable, TypeVar, Optional + +BASE_DIR = "./config" + + +T = TypeVar("T") + + +class Config: + """ + wrapper class for python ConfigParser class. will check environment variables + for config values before checking config file properties + """ + + _instance: Optional["Config"] = None + _env_vars: dict[str, str] + _conf: ConfigParser + + def __new__(cls) -> "Config": + """Singleton like pattern""" + if not cls._instance: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self) -> None: + """Initialize Config object""" + self._env_vars = dict(os.environ.copy()) + self._load() + + @classmethod + def get_instance(cls) -> "Config": + """Singleton like pattern""" + if not cls._instance: + cls._instance = Config() + return cls._instance + + def _load(self) -> None: + """Load config from config file and environment variables""" + # load base config + conf_parser = ConfigParser() + base_config = f"{BASE_DIR}/config.ini" + print(f"loading base config: {base_config}") + with open(base_config, "r", encoding="utf-8") as handle: + conf_parser.read_file(handle) + + # load env specific config + env = os.environ.get("ENV") + if not env: + env = "dev" + env_conf = f"{BASE_DIR}/config-{env.lower()}.ini" + if os.path.isfile(env_conf): + print(f"loading config: {env_conf}") + with open(env_conf, "r", encoding="utf-8") as handle: + conf_parser.read_file(handle) + + # load local config if set + local_conf_file = os.environ.get("LOCAL_CONF") + if not local_conf_file: + local_conf_file = f"{BASE_DIR}/local.conf" + if os.path.isfile(local_conf_file): + print(f"loading additional config: {local_conf_file}") + with open(local_conf_file, "r", encoding="utf-8") as handle: + conf_parser.read_file(handle) + + self._conf = conf_parser + self._app_config_init() + + def _app_config_init(self) -> None: + """Initialize app config""" + logging.config.fileConfig(f'{BASE_DIR}/{self.get("logging_config")}') + + # disable noisy loggers + logging.getLogger("google.cloud.pubsub_v1").setLevel(logging.WARNING) + + def get(self, key: str, section: str = "default", type_conv: Callable[[str], T] = lambda x: x) -> T: + """Get config value for key, section. If not found in config file, check environment variables""" + value = self._env_vars.get(key) + + if not value: + value = self._conf[section][key] + + return type_conv(value) + + def get_bytes(self, key: str, section: str = "default") -> bytes: + """Get config value for key, section and cast to bytes""" + return self.get(key, section, lambda s: s.encode("utf-8")) + + def get_bool(self, key: str, section: str = "default") -> bool: + """Get config value for key, section and cast to boolean""" + return self.get(key, section, lambda s: s.lower() in ["true", "1"]) + + def get_int(self, key: str, section: str = "default") -> int: + """Get config value for key, section and cast to integer""" + return self.get(key, section, int) + + def get_float(self, key: str, section: str = "default") -> float: + """Get config value for key, section and cast to float""" + return self.get(key, section, float) + + def get_list(self, key: str, section: str = "default", sep: str = ",") -> list[str]: + """Get config value for key, section and cast to list""" + value = self.get(key, section) + if not isinstance(value, str): + raise TypeError(f"Expected string value for {key}, got {type(value)}") + str_list = value.strip() + return [] if str_list == "" else [s.strip() for s in str_list.split(sep)] diff --git a/submodules/moragents_dockers/agents/src/agents/README.md b/submodules/agents/src/services/agents/README.md old mode 100644 new mode 100755 similarity index 96% rename from submodules/moragents_dockers/agents/src/agents/README.md rename to submodules/agents/src/services/agents/README.md index 7b701fc1..8054a7e9 --- a/submodules/moragents_dockers/agents/src/agents/README.md +++ b/submodules/agents/src/services/agents/README.md @@ -32,7 +32,7 @@ After creating your agent, you'll need to make the following modifications to ge Add your agent's configuration to the main config file: ```python -from src.agents.your_agent_name.config import Config as YourAgentConfig +from services.agents.your_agent_name.config import Config as YourAgentConfig class Config: # ... existing config ... @@ -208,8 +208,8 @@ Create tests for your agent in the `tests/agents/your_agent_name/` directory: ## Example Usage ```python -from src.agents.your_agent_name.agent import YourAgentNameAgent -from src.models.core import ChatRequest +from services.agents.your_agent_name.agent import YourAgentNameAgent +from models.service.chat_models import ChatRequest # Initialize agent agent = YourAgentNameAgent(config, llm, embeddings) diff --git a/submodules/moragents_dockers/agents/src/agents/agent_core/__init__.py b/submodules/agents/src/services/agents/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/agent_core/__init__.py rename to submodules/agents/src/services/agents/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/base_agent/README.md b/submodules/agents/src/services/agents/base_agent/README.md old mode 100644 new mode 100755 similarity index 75% rename from submodules/moragents_dockers/agents/src/agents/base_agent/README.md rename to submodules/agents/src/services/agents/base_agent/README.md index 35e95f8f..62d0001d --- a/submodules/moragents_dockers/agents/src/agents/base_agent/README.md +++ b/submodules/agents/src/services/agents/base_agent/README.md @@ -1,37 +1,46 @@ # Introduction to the Coinbase Enabled Agent Experience + Welcome to the new world of the Coinbase enabled agent experience. This guide will walk you through the process of setting up your Coinbase API configuration and using the available features. ## Setting Up Coinbase API Configuration + To get started, you'll need to create your Coinbase developer platform API key and API secret by visiting your Coinbase developer portal. Once you have your API key and API secret, follow these steps: -1. Spin up the MORagents application + +1. Spin up the MySuperAgent application 2. Click **Settings** at the top 3. Click **Coinbase API** 4. Paste your API key and API secret. Use the full API key name and Private key values you saved when creating your Coinbase API Key. This is also in the "name" value inside your downloaded cdp_api_key.json file starting with "organizations" and the full privateKey value. Don't just copy the Key ID. 5. Click **Save Coinbase Credentials** to lock these values in ## Creating a Coinbase Wallet + Note that the wallet you use to perform dollar cost averaging and gasless sends is separate from your browser wallet integration. To create your Coinbase wallet: -1. Click **CDP Wallets** at the top of the UI for MORagents + +1. Click **CDP Wallets** at the top of the UI for MySuperAgent 2. Click **Create New Wallet** -This will create a new local wallet file. Please download this file and store it somewhere safe, as subsequent openings of the MORagents app may not restore it automatically. + This will create a new local wallet file. Please download this file and store it somewhere safe, as subsequent openings of the MySuperAgent app may not restore it automatically. ## Restoring Your Coinbase Wallet + To restore your wallet, follow these steps: + 1. Click **CDP Wallets** 2. Click **Create New Wallet** 3. Click **Restore Existing** 4. Choose the JSON file you downloaded earlier -It's imperative that you have the JSON file somewhere secure, as future openings of this app may not necessarily restore it, and not doing so may result in a loss of funds. + It's imperative that you have the JSON file somewhere secure, as future openings of this app may not necessarily restore it, and not doing so may result in a loss of funds. ### To fund your wallet: + 1. Click **CDP Wallets** at the top 2. Click the copy icon to retrieve the address of your Coinbase local wallet 3. Use MetaMask or any other wallet set to the base L2 to send USDC to this address -Once your wallet is funded, you can ask the agent, "what is my USDC balance on base?" to see the updated balance. To send USDC from this agent to another address, type "send USDC on base" and follow the prompts. - + Once your wallet is funded, you can ask the agent, "what is my USDC balance on base?" to see the updated balance. To send USDC from this agent to another address, type "send USDC on base" and follow the prompts. ### Gasless USDC Sends + To send USDC, you'll need to ensure that you have some USDC on the base L2 for the wallet you created. You can check your balance by asking the agent, "what is my USDC balance on base?" ## Community Development + All agents, including the Gasless Send and DCA agents are provided under the MIT license for the community to build and explore. They are references for the community to build and eventually ship to the decentralized inference of Morpheus in early 2025, where these agents can live and function for long periods of time without needing local compute. diff --git a/submodules/moragents_dockers/agents/src/agents/base_agent/__init__.py b/submodules/agents/src/services/agents/base_agent/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/base_agent/__init__.py rename to submodules/agents/src/services/agents/base_agent/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/base_agent/agent.py b/submodules/agents/src/services/agents/base_agent/agent.py old mode 100644 new mode 100755 similarity index 82% rename from submodules/moragents_dockers/agents/src/agents/base_agent/agent.py rename to submodules/agents/src/services/agents/base_agent/agent.py index 8954c2e1..be2b885d --- a/submodules/moragents_dockers/agents/src/agents/base_agent/agent.py +++ b/submodules/agents/src/services/agents/base_agent/agent.py @@ -1,12 +1,13 @@ import logging -from typing import Any, Dict, Optional +from typing import Any, Dict -from src.agents.base_agent import tools -from src.models.core import ChatRequest, AgentResponse -from src.agents.agent_core.agent import AgentCore -from langchain.schema import HumanMessage, SystemMessage -from src.agents.base_agent.config import Config -from src.stores import wallet_manager_instance +from services.agents.base_agent import tools +from models.service.chat_models import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore +from langchain.schema import SystemMessage +from services.agents.base_agent.config import Config +from services.agents.base_agent.tool_types import BaseAgentToolType +from stores import wallet_manager_instance logger = logging.getLogger(__name__) @@ -14,8 +15,8 @@ class BaseAgent(AgentCore): """Agent for handling Base blockchain transactions.""" - def __init__(self, config: Dict[str, Any], llm: Any, embeddings: Any): - super().__init__(config, llm, embeddings) + def __init__(self, config: Dict[str, Any], llm: Any): + super().__init__(config, llm) self.tools_provided = Config.tools self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) @@ -44,7 +45,7 @@ async def _process_request(self, request: ChatRequest) -> AgentResponse: "When you need to perform an action, use the appropriate function with the correct arguments." ) ), - HumanMessage(content=request.prompt.content), + *request.messages_for_llm, ] result = self.tool_bound_llm.invoke(messages) @@ -57,11 +58,11 @@ async def _process_request(self, request: ChatRequest) -> AgentResponse: async def _execute_tool(self, func_name: str, args: Dict[str, Any]) -> AgentResponse: """Execute the appropriate Base transaction tool based on function name.""" try: - if func_name == "swap_assets": + if func_name == BaseAgentToolType.SWAP_ASSETS.value: return AgentResponse.action_required(content="Ready to perform swap", action_type="swap") - elif func_name == "transfer_asset": + elif func_name == BaseAgentToolType.TRANSFER_ASSET.value: return AgentResponse.action_required(content="Ready to perform transfer", action_type="transfer") - elif func_name == "get_balance": + elif func_name == BaseAgentToolType.GET_BALANCE.value: wallet = wallet_manager_instance.get_active_wallet() if not wallet: return AgentResponse.success( diff --git a/submodules/moragents_dockers/agents/src/agents/base_agent/config.py b/submodules/agents/src/services/agents/base_agent/config.py old mode 100644 new mode 100755 similarity index 69% rename from submodules/moragents_dockers/agents/src/agents/base_agent/config.py rename to submodules/agents/src/services/agents/base_agent/config.py index 57e5bfcc..e94c1226 --- a/submodules/moragents_dockers/agents/src/agents/base_agent/config.py +++ b/submodules/agents/src/services/agents/base_agent/config.py @@ -1,7 +1,34 @@ +from models.service.agent_config import AgentConfig +from services.agents.base_agent.tool_types import BaseAgentToolType + + class Config: + """Configuration for Base Network Agent.""" + + # ************* + # AGENT CONFIG + # ************* + + agent_config = AgentConfig( + path="services.agents.base_agent.agent", + class_name="BaseAgent", + description="Interact with the Base network and developer-managed wallets", + delegator_description=( + "Specializes in transactions and interactions specifically on the Base network via developer-managed wallets." + "Use ONLY when users explicitly reference Base, base network, or Coinbase." + ), + human_readable_name="Base Transaction Manager", + command="base", + upload_required=False, + ) + + # ************* + # TOOLS CONFIG + # ************* + tools = [ { - "name": "swap_assets", + "name": BaseAgentToolType.SWAP_ASSETS.value, "description": "Swap one asset for another (Base Mainnet only)", "parameters": { "type": "object", @@ -14,7 +41,7 @@ class Config: }, }, { - "name": "transfer_asset", + "name": BaseAgentToolType.TRANSFER_ASSET.value, "description": "Transfer an asset to another address", "parameters": { "type": "object", @@ -26,19 +53,17 @@ class Config: }, }, { - "name": "get_balance", + "name": BaseAgentToolType.GET_BALANCE.value, "description": "Get balance of a specific asset", "parameters": { "type": "object", - "properties": { - "asset_id": {"type": "string", "description": "Asset ID to check balance for"} - }, + "properties": {"asset_id": {"type": "string", "description": "Asset ID to check balance for"}}, "required": ["asset_id"], }, }, # TODO: Add more base tools / functionality # { - # "name": "create_token", + # "name": BaseAgentToolType.CREATE_TOKEN.value, # "description": "Create a new ERC-20 token", # "parameters": { # "type": "object", @@ -54,12 +79,12 @@ class Config: # }, # }, # { - # "name": "request_eth_from_faucet", + # "name": BaseAgentToolType.REQUEST_ETH_FROM_FAUCET.value, # "description": "Request ETH from testnet faucet", # "parameters": {"type": "object", "properties": {}}, # }, # { - # "name": "mint_nft", + # "name": BaseAgentToolType.MINT_NFT.value, # "description": "Mint an NFT to an address", # "parameters": { # "type": "object", @@ -71,7 +96,7 @@ class Config: # }, # }, # { - # "name": "register_basename", + # "name": BaseAgentToolType.REGISTER_BASENAME.value, # "description": "Register a basename for the agent's wallet", # "parameters": { # "type": "object", diff --git a/submodules/moragents_dockers/agents/src/agents/base_agent/routes.py b/submodules/agents/src/services/agents/base_agent/routes.py old mode 100644 new mode 100755 similarity index 92% rename from submodules/moragents_dockers/agents/src/agents/base_agent/routes.py rename to submodules/agents/src/services/agents/base_agent/routes.py index b085caa5..314f4ad3 --- a/submodules/moragents_dockers/agents/src/agents/base_agent/routes.py +++ b/submodules/agents/src/services/agents/base_agent/routes.py @@ -2,8 +2,8 @@ from decimal import Decimal from fastapi import APIRouter from fastapi.responses import JSONResponse -from src.stores import wallet_manager_instance -from src.agents.base_agent.tools import swap_assets, transfer_asset +from stores import wallet_manager_instance +from services.agents.base_agent.tools import swap_assets, transfer_asset router = APIRouter(prefix="/base", tags=["base"]) logger = logging.getLogger(__name__) @@ -67,9 +67,7 @@ async def transfer(data: dict): ) logger.info(f"Using wallet address: {wallet.default_address.address_id}") - logger.info( - f"Attempting transfer: {data['amount']} {data['asset']} -> {data['destinationAddress']}" - ) + logger.info(f"Attempting transfer: {data['amount']} {data['asset']} -> {data['destinationAddress']}") # Execute transfer result = transfer_asset( diff --git a/submodules/agents/src/services/agents/base_agent/tests/benchmark/test_base_agent.py b/submodules/agents/src/services/agents/base_agent/tests/benchmark/test_base_agent.py new file mode 100755 index 00000000..e66a80e6 --- /dev/null +++ b/submodules/agents/src/services/agents/base_agent/tests/benchmark/test_base_agent.py @@ -0,0 +1,95 @@ +import logging +import pytest +from unittest.mock import Mock, patch +from typing import Dict, Any + +from services.agents.base_agent.agent import BaseAgent +from models.service.chat_models import ChatRequest, AgentResponse +from stores import wallet_manager_instance + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def base_agent(llm): + config: Dict[str, Any] = {"name": "base", "description": "Agent for Base blockchain transactions"} + return BaseAgent(config=config, llm=llm) + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_get_balance_success(base_agent, mock_wallet, make_chat_request): + wallet_manager_instance.configure_cdp_client = Mock(return_value=True) + wallet_manager_instance.get_active_wallet = Mock(return_value=mock_wallet) + + request = make_chat_request(content="What's my ETH balance?", agent_name="base") + + with patch("services.agents.base_agent.tools.get_balance") as mock_get_balance: + mock_get_balance.return_value = {"address": "0x123", "balance": "1.5", "asset": "ETH"} + + response = await base_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert "1.5 ETH" in response.content + assert response.response_type.value == "success" + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_no_cdp_client(base_agent, make_chat_request): + wallet_manager_instance.configure_cdp_client = Mock(return_value=False) + + request = make_chat_request(content="What's my ETH balance?", agent_name="base") + + response = await base_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert "CDP client is not initialized" in response.content + assert response.response_type.value == "success" + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_no_active_wallet(base_agent, make_chat_request): + wallet_manager_instance.configure_cdp_client = Mock(return_value=True) + wallet_manager_instance.get_active_wallet = Mock(return_value=None) + + request = make_chat_request(content="What's my ETH balance?", agent_name="base") + + response = await base_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert "select or create a wallet" in response.content + assert response.response_type.value == "success" + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_swap_assets(base_agent, mock_wallet, make_chat_request): + wallet_manager_instance.configure_cdp_client = Mock(return_value=True) + wallet_manager_instance.get_active_wallet = Mock(return_value=mock_wallet) + + request = make_chat_request(content="Swap 1 ETH for USDC", agent_name="base") + + response = await base_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.action_type == "swap" + assert "Ready to perform swap" in response.content + assert response.response_type.value == "action_required" + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_transfer_asset(base_agent, mock_wallet, make_chat_request): + wallet_manager_instance.configure_cdp_client = Mock(return_value=True) + wallet_manager_instance.get_active_wallet = Mock(return_value=mock_wallet) + + request = make_chat_request(content="Send 1 ETH to 0x456", agent_name="base") + + response = await base_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.action_type == "transfer" + assert "Ready to perform transfer" in response.content + assert response.response_type.value == "action_required" diff --git a/submodules/agents/src/services/agents/base_agent/tool_types.py b/submodules/agents/src/services/agents/base_agent/tool_types.py new file mode 100644 index 00000000..bd6b36b3 --- /dev/null +++ b/submodules/agents/src/services/agents/base_agent/tool_types.py @@ -0,0 +1,13 @@ +from enum import Enum + + +class BaseAgentToolType(Enum): + """Enum for different Base blockchain transaction tool types""" + + SWAP_ASSETS = "swap_assets" + TRANSFER_ASSET = "transfer_asset" + GET_BALANCE = "get_balance" + CREATE_TOKEN = "create_token" + REQUEST_ETH_FROM_FAUCET = "request_eth_from_faucet" + MINT_NFT = "mint_nft" + REGISTER_BASENAME = "register_basename" diff --git a/submodules/moragents_dockers/agents/src/agents/base_agent/tools.py b/submodules/agents/src/services/agents/base_agent/tools.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/base_agent/tools.py rename to submodules/agents/src/services/agents/base_agent/tools.py diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/__init__.py b/submodules/agents/src/services/agents/codex/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/crypto_data/__init__.py rename to submodules/agents/src/services/agents/codex/__init__.py diff --git a/submodules/agents/src/services/agents/codex/agent.py b/submodules/agents/src/services/agents/codex/agent.py new file mode 100755 index 00000000..c0e7e77a --- /dev/null +++ b/submodules/agents/src/services/agents/codex/agent.py @@ -0,0 +1,94 @@ +import logging +from typing import Dict, Any +from models.service.agent_core import AgentCore +from models.service.chat_models import ChatRequest, AgentResponse +from .config import Config +from .tools import list_top_tokens, get_top_holders_percent, search_nfts +from .models import TopTokensResponse, TopHoldersResponse, NftSearchResponse +from .utils.tool_types import CodexToolType +from .utils.networks import NETWORK_TO_ID_MAPPING + +logger = logging.getLogger(__name__) + + +class CodexAgent(AgentCore): + """Agent for interacting with Codex.io API.""" + + def __init__(self, config: Dict[str, Any], llm: Any): + super().__init__(config, llm) + self.tools_provided = Config.tools + self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) + + async def _process_request(self, request: ChatRequest) -> AgentResponse: + """Process the validated chat request for Codex API interactions.""" + try: + messages = [Config.system_message, *request.messages_for_llm] + + result = self.tool_bound_llm.invoke(messages) + return await self._handle_llm_response(result) + + except Exception as e: + logger.error(f"Error processing request: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) + + async def _execute_tool(self, func_name: str, args: Dict[str, Any]) -> AgentResponse: + """Execute the appropriate Codex API tool based on function name.""" + try: + if func_name == CodexToolType.LIST_TOP_TOKENS.value: + top_tokens_response: TopTokensResponse = await list_top_tokens( + limit=args.get("limit"), + networks=args.get("networks"), + resolution=args.get("resolution"), + ) + return AgentResponse.success( + content=top_tokens_response.formatted_response, + metadata=top_tokens_response.model_dump(), + action_type=CodexToolType.LIST_TOP_TOKENS.value, + ) + + elif func_name == CodexToolType.GET_TOP_HOLDERS_PERCENT.value: + if args.get("tokenName") is None: + return AgentResponse.needs_info( + content="Please specify both the token name and network you'd like to get top holders for" + ) + + if args.get("network") is None: + return AgentResponse.needs_info( + content=f"Please specify the network (Ethereum, Solana, etc.) you'd like to search for {args.get('tokenName')}" + ) + + if args.get("network") not in NETWORK_TO_ID_MAPPING: + return AgentResponse.needs_info( + content=f"Please specify a valid network (Ethereum, Solana, etc.) you'd like to search for {args.get('tokenName')}" + ) + + holders_response: TopHoldersResponse = await get_top_holders_percent( + token_name=args["tokenName"], + network=args["network"], + ) + return AgentResponse.success( + content=holders_response.formatted_response, + metadata=holders_response.model_dump(), + action_type=CodexToolType.GET_TOP_HOLDERS_PERCENT.value, + ) + + elif func_name == CodexToolType.SEARCH_NFTS.value: + nft_search_response: NftSearchResponse = await search_nfts( + search=args["search"], + limit=args.get("limit"), + network_filter=args.get("networkFilter"), + filter_wash_trading=args.get("filterWashTrading"), + window=args.get("window"), + ) + return AgentResponse.success( + content=nft_search_response.formatted_response, + metadata=nft_search_response.model_dump(), + action_type=CodexToolType.SEARCH_NFTS.value, + ) + + else: + return AgentResponse.error(error_message=f"Unknown tool: {func_name}") + + except Exception as e: + logger.error(f"Error executing tool {func_name}: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) diff --git a/submodules/agents/src/services/agents/codex/config.py b/submodules/agents/src/services/agents/codex/config.py new file mode 100755 index 00000000..eb8933ac --- /dev/null +++ b/submodules/agents/src/services/agents/codex/config.py @@ -0,0 +1,134 @@ +from langchain.schema import SystemMessage + +from models.service.agent_config import AgentConfig +from .utils.tool_types import CodexToolType + + +class Config: + """Configuration for Codex.io API.""" + + # ************* + # AGENT CONFIG + # ------------ + # This must be defined in every agent config file + # It is required to load the agent + # ************* + + agent_config = AgentConfig( + path="services.agents.codex.agent", + class_name="CodexAgent", + description="Fetches and analyzes advanced token and NFT data from Codex.io such as trending tokens, top holders, and more", + delegator_description=( + "Use this agent for: 1) Getting lists of trending tokens across networks with customizable time frames, " + "2) Analyzing token holder concentration by getting top holder percentages for specific tokens on specific networks, " + "3) Searching NFT collections with detailed metrics and wash trading detection. " + "This agent MUST be used for any queries related to trending tokens, token holder analysis, or NFT collection searches." + ), + human_readable_name="Codex Market Analyst", + command="codex", + upload_required=False, + ) + + # ************** + # SYSTEM MESSAGE + # ************** + + system_message = SystemMessage( + content=( + "You are an agent that can fetch and analyze token and NFT data " + "from Codex.io. You can get trending tokens, analyze token holder " + "concentration, and search for NFT collections." + ) + ) + + # ************* + # TOOLS CONFIG + # ************* + + tools = [ + { + "name": CodexToolType.LIST_TOP_TOKENS.value, + "description": "Get a list of trending tokens across specified networks", + "parameters": { + "type": "object", + "properties": { + "limit": { + "type": "integer", + "description": "Maximum number of tokens to return (max 50)", + "required": False, + }, + "networks": { + "type": "array", + "items": {"type": "string"}, + "description": "List of network names to filter by (Ethereum, Solana, etc.)", + "required": False, + }, + "resolution": { + "type": "string", + "description": "Time frame for trending results (1, 5, 15, 30, 60, 240, 720, or 1D)", + "required": False, + }, + }, + }, + }, + { + "name": CodexToolType.GET_TOP_HOLDERS_PERCENT.value, + "description": "Get the top holders for a token. If no network is provided, then LEAVE IT AS NONE", + "parameters": { + "type": "object", + "properties": { + "tokenName": { + "type": "string", + "description": "Token name to get top holders for", + "required": False, + }, + "network": { + "type": "string", + "description": "Network to search for token on. Must be deliberately specified.", + "required": False, + }, + }, + }, + }, + { + "name": CodexToolType.SEARCH_NFTS.value, + "description": "Search for NFT collections by name or address", + "parameters": { + "type": "object", + "properties": { + "search": { + "type": "string", + "description": "Query string to search for", + "required": True, + }, + "limit": { + "type": "integer", + "description": "Maximum number of results to return", + "required": False, + }, + "networkFilter": { + "type": "array", + "items": {"type": "integer"}, + "description": "List of network IDs to filter by", + "required": False, + }, + "filterWashTrading": { + "type": "boolean", + "description": "Whether to filter collections linked to wash trading", + "required": False, + }, + "window": { + "type": "string", + "description": "Time frame for stats (1h, 4h, 12h, or 1d)", + "required": False, + }, + }, + }, + }, + ] + + # ************* + # API CONFIG + # ************* + + GRAPHQL_URL = "https://graph.codex.io/graphql" diff --git a/submodules/agents/src/services/agents/codex/models.py b/submodules/agents/src/services/agents/codex/models.py new file mode 100755 index 00000000..fcc1c437 --- /dev/null +++ b/submodules/agents/src/services/agents/codex/models.py @@ -0,0 +1,211 @@ +from pydantic import BaseModel +from typing import Optional, List + + +class TokenMetadata(BaseModel): + """Model for token metadata.""" + + address: str + createdAt: int + decimals: int + id: str + imageBannerUrl: Optional[str] + imageLargeUrl: Optional[str] + imageSmallUrl: Optional[str] + imageThumbUrl: Optional[str] + isScam: Optional[bool] + lastTransaction: int + liquidity: str + marketCap: Optional[str] + name: str + networkId: int + price: float + priceChange: float + priceChange1: Optional[float] + priceChange4: Optional[float] + priceChange12: Optional[float] + priceChange24: Optional[float] + quoteToken: Optional[str] + resolution: str + symbol: str + topPairId: str + txnCount1: Optional[int] + txnCount4: Optional[int] + txnCount12: Optional[int] + txnCount24: Optional[int] + uniqueBuys1: Optional[int] + uniqueBuys4: Optional[int] + uniqueBuys12: Optional[int] + uniqueBuys24: Optional[int] + uniqueSells1: Optional[int] + uniqueSells4: Optional[int] + uniqueSells12: Optional[int] + uniqueSells24: Optional[int] + volume: str + + @property + def formatted_response(self) -> str: + """Format token metadata for response.""" + return ( + f"Token {self.symbol} ({self.name}):\n" + f"Price: ${self.price:,.6f}\n" + f"Market Cap: {self.marketCap or 'N/A'}\n" + f"Liquidity: {self.liquidity}\n" + f"Volume: {self.volume}\n" + f"24h Change: {self.priceChange24 or 0:+.2f}%\n" + f"24h Transactions: {self.txnCount24 or 0}\n" + f"Contract: {self.address}" + ) + + +class TopTokensResponse(BaseModel): + """Model for top tokens response.""" + + success: bool + data: List[TokenMetadata] + + @property + def formatted_response(self) -> str: + """Format top tokens response for display.""" + if not self.success: + return "Failed to get top tokens." + if not self.data: + return "No top tokens found." + + formatted = "# Top 5 Tokens\n\n" + for token in self.data[:10]: + formatted += f"{token.formatted_response}\n\n---\n\n" + return formatted + + +class TokenPair(BaseModel): + """Model for token pair information.""" + + token0: str + token1: str + + +class Exchange(BaseModel): + """Model for exchange information.""" + + name: str + + +class TokenDetails(BaseModel): + """Model for detailed token information.""" + + address: str + decimals: int + id: str + name: str + networkId: int + symbol: str + + +class TokenFilterResult(BaseModel): + """Model for token information from filter tokens endpoint.""" + + buyCount1: Optional[int] = None + high1: Optional[str] = None + txnCount1: Optional[int] = None + uniqueTransactions1: Optional[int] = None + volume1: Optional[str] = None + liquidity: Optional[str] = None + marketCap: Optional[str] = None + priceUSD: Optional[str] = None + pair: Optional[TokenPair] = None + exchanges: Optional[List[Exchange]] = None + token: Optional[TokenDetails] = None + + +class TopHoldersResponse(BaseModel): + """Model for top holders response.""" + + success: bool + data: float # Percentage owned by top 10 holders + token_info: Optional[TokenFilterResult] = None + + @property + def formatted_response(self) -> str: + """Format top holders response for display.""" + if not self.success: + return "Failed to get top holders data." + + risk = "High Risk" if self.data > 80 else "Medium Risk" if self.data > 50 else "Low Risk" + + response = f"Top 10 holders own {self.data:.2f}% of supply. {risk}\n\n" + + if self.token_info and self.token_info.token: + response += f"Token Info:\n" + response += f"Name: {self.token_info.token.name} ({self.token_info.token.symbol})\n" + + if self.token_info.priceUSD: + response += f"Price: ${float(self.token_info.priceUSD):,.6f}\n" + if self.token_info.marketCap: + response += f"Market Cap: ${float(self.token_info.marketCap):,.2f}\n" + if self.token_info.liquidity: + response += f"Liquidity: ${float(self.token_info.liquidity):,.2f}\n" + if self.token_info.volume1: + response += f"24h Volume: ${float(self.token_info.volume1):,.2f}\n" + if self.token_info.txnCount1: + response += f"24h Transactions: {self.token_info.txnCount1:,}\n" + if self.token_info.uniqueTransactions1: + response += f"Unique Transactions: {self.token_info.uniqueTransactions1:,}\n" + if self.token_info.exchanges: + exchange_names = [ex.name for ex in self.token_info.exchanges] + response += f"Available on: {', '.join(exchange_names)}\n" + if self.token_info.pair: + response += f"Trading Pair: {self.token_info.pair.token0}/{self.token_info.pair.token1}\n" + + return response + + +class NftSearchItem(BaseModel): + """Model for NFT search item.""" + + address: str + average: str + ceiling: str + floor: str + id: str + imageUrl: Optional[str] + name: Optional[str] + networkId: int + symbol: Optional[str] + tradeCount: str + tradeCountChange: float + volume: str + volumeChange: float + window: str + + @property + def formatted_response(self) -> str: + """Format NFT search item for display.""" + return ( + f"# {self.name or 'Unnamed'} ({self.symbol or 'No Symbol'})\n" + f"Floor Price: {self.floor}\n" + f"Volume: {self.volume} ({self.volumeChange:+.2f}%)\n" + f"Trade Count: {self.tradeCount}\n" + f"Contract: {self.address}" + ) + + +class NftSearchResponse(BaseModel): + """Model for NFT search response.""" + + success: bool + hasMore: int + items: List[NftSearchItem] + + @property + def formatted_response(self) -> str: + """Format NFT search response for display.""" + if not self.success: + return "Failed to search NFT collections." + if not self.items: + return "No NFT collections found." + + formatted = "# Top 5 NFT Collections\n\n" + for item in self.items[:10]: + formatted += f"{item.formatted_response}\n\n---\n\n" + return formatted diff --git a/submodules/agents/src/services/agents/codex/tests/benchmarks/test_codex_agent.py b/submodules/agents/src/services/agents/codex/tests/benchmarks/test_codex_agent.py new file mode 100755 index 00000000..775d5b1e --- /dev/null +++ b/submodules/agents/src/services/agents/codex/tests/benchmarks/test_codex_agent.py @@ -0,0 +1,116 @@ +import logging +import pytest +from unittest.mock import patch +from typing import Dict, Any + +from services.agents.codex.agent import CodexAgent +from models.service.chat_models import ChatRequest, AgentResponse +from services.agents.codex.models import TopTokensResponse, TopHoldersResponse, NftSearchResponse +from services.agents.codex.utils.tool_types import CodexToolType + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def codex_agent(llm): + config: Dict[str, Any] = {"name": "codex", "description": "Agent for Codex.io API"} + return CodexAgent(config=config, llm=llm) + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_list_top_tokens_success(codex_agent, make_chat_request): + request = make_chat_request(content="List top tokens", agent_name="codex") + + with patch("services.agents.codex.tools.list_top_tokens") as mock_tokens: + mock_response = TopTokensResponse(formatted_response="Top tokens list") + mock_tokens.return_value = mock_response + + response = await codex_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert response.content == "Top tokens list" + assert response.action_type == CodexToolType.LIST_TOP_TOKENS.value + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_get_top_holders_success(codex_agent, make_chat_request): + request = make_chat_request(content="Get top holders for Bitcoin on Ethereum", agent_name="codex") + + with patch("services.agents.codex.tools.get_top_holders_percent") as mock_holders: + mock_response = TopHoldersResponse(formatted_response="Top holders data") + mock_holders.return_value = mock_response + + response = await codex_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert response.content == "Top holders data" + assert response.action_type == CodexToolType.GET_TOP_HOLDERS_PERCENT.value + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_search_nfts_success(codex_agent, make_chat_request): + request = make_chat_request(content="Search for BAYC NFTs", agent_name="codex") + + with patch("services.agents.codex.tools.search_nfts") as mock_search: + mock_response = NftSearchResponse(formatted_response="NFT search results") + mock_search.return_value = mock_response + + response = await codex_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert response.content == "NFT search results" + assert response.action_type == CodexToolType.SEARCH_NFTS.value + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_top_holders_missing_token(codex_agent, make_chat_request): + request = make_chat_request(content="Get top holders on Ethereum", agent_name="codex") + + response = await codex_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "needs_info" + assert "Please specify both the token name and network" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_top_holders_missing_network(codex_agent, make_chat_request): + request = make_chat_request(content="Get top holders for Bitcoin", agent_name="codex") + + response = await codex_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "needs_info" + assert "Please specify the network" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_top_holders_invalid_network(codex_agent, make_chat_request): + request = make_chat_request(content="Get top holders for Bitcoin on InvalidNetwork", agent_name="codex") + + response = await codex_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "needs_info" + assert "Please specify a valid network" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_unknown_tool(codex_agent, make_chat_request): + request = make_chat_request(content="Do something invalid", agent_name="codex") + + response = await codex_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Unknown tool" in response.content diff --git a/submodules/agents/src/services/agents/codex/tests/config.py b/submodules/agents/src/services/agents/codex/tests/config.py new file mode 100644 index 00000000..58495f10 --- /dev/null +++ b/submodules/agents/src/services/agents/codex/tests/config.py @@ -0,0 +1,155 @@ +from models.service.agent_config import AgentConfig + + +class Config: + """Configuration for Elfa Social API.""" + + # ************* + # AGENT CONFIG + # ------------ + # This must be defined in every agent config file + # It is required to load the agent + # ************* + + agent_config = AgentConfig( + path="services.agents.elfa.agent", + class_name="ElfaAgent", + description="Fetches and analyzes social media data related to cryptocurrency from Elfa.", + human_readable_name="Elfa Social Analyst", + command="elfa", + upload_required=False, + ) + + # ************* + # TOOLS CONFIG + # ************* + + tools = [ + { + "name": "get_mentions", + "description": "Get tweets by smart accounts with at least 10 other smart interactions", + "parameters": { + "type": "object", + "properties": { + "limit": { + "type": "number", + "description": "Number of mentions to return (default: 100)", + "required": False, + }, + "offset": { + "type": "number", + "description": "Offset for pagination (default: 0)", + "required": False, + }, + }, + }, + }, + { + "name": "get_top_mentions", + "description": "Get top mentions for a specific ticker, ranked by view count", + "parameters": { + "type": "object", + "properties": { + "ticker": { + "type": "string", + "description": "The ticker symbol to get mentions for", + "required": True, + }, + "timeWindow": { + "type": "string", + "description": "Time window for mentions (e.g., '1h', '24h', '7d')", + "required": False, + }, + "includeAccountDetails": { + "type": "boolean", + "description": "Include account details in response", + "required": False, + }, + }, + }, + }, + { + "name": "search_mentions", + "description": "Search for mentions by keywords within a time range", + "parameters": { + "type": "object", + "properties": { + "keywords": { + "type": "array", + "items": {"type": "string"}, + "description": "Up to 5 keywords to search for", + "required": True, + }, + "from": { + "type": "number", + "description": "Start timestamp (unix)", + "required": True, + }, + "to": { + "type": "number", + "description": "End timestamp (unix)", + "required": True, + }, + "limit": { + "type": "number", + "description": "Number of results to return (default: 20, max: 30)", + "required": False, + }, + "cursor": { + "type": "string", + "description": "Cursor for pagination", + "required": False, + }, + }, + }, + }, + { + "name": "get_trending_tokens", + "description": "Get trending tokens based on social media mentions", + "parameters": { + "type": "object", + "properties": { + "timeWindow": { + "type": "string", + "description": "Time window for trending analysis (default: '24h')", + "required": False, + }, + "minMentions": { + "type": "number", + "description": "Minimum number of mentions required (default: 5)", + "required": False, + }, + }, + }, + }, + { + "name": "get_account_smart_stats", + "description": "Get smart stats and social metrics for a given username", + "parameters": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "Username to get stats for", + "required": True, + }, + }, + }, + }, + ] + + # ************* + # API CONFIG + # ************* + + BASE_URL = "https://api.elfa.ai" + API_VERSION = "v1" + RATE_LIMIT = 60 # requests per minute + + ENDPOINTS = { + "mentions": f"/{API_VERSION}/mentions", + "top_mentions": f"/{API_VERSION}/top-mentions", + "mentions_search": f"/{API_VERSION}/mentions/search", + "trending_tokens": f"/{API_VERSION}/trending-tokens", + "account_smart_stats": f"/{API_VERSION}/account/smart-stats", + } diff --git a/submodules/agents/src/services/agents/codex/tools.py b/submodules/agents/src/services/agents/codex/tools.py new file mode 100755 index 00000000..5472c118 --- /dev/null +++ b/submodules/agents/src/services/agents/codex/tools.py @@ -0,0 +1,272 @@ +import logging +import os +from typing import Optional, List, Dict, Any + +import aiohttp + +from .config import Config +from .utils.networks import NETWORK_TO_ID_MAPPING +from .models import TopTokensResponse, TopHoldersResponse, NftSearchResponse, TokenFilterResult +from services.secrets import get_secret + +logger = logging.getLogger(__name__) + + +async def _make_graphql_request(query: str, variables: Optional[Dict[str, Any]] = None) -> Any: + """Make a GraphQL request to Codex API.""" + # Get API key from environment + api_key = get_secret("CodexApiKey") + if not api_key: + raise Exception("CODEX_API_KEY environment variable is not set") + + headers = {"Authorization": api_key, "Content-Type": "application/json"} + + data = {"query": query, "variables": variables or {}} + + try: + async with aiohttp.ClientSession() as session: + async with session.post(Config.GRAPHQL_URL, json=data, headers=headers) as response: + result = await response.json() + + if response.status != 200: + error_msg = result.get("message", str(result)) + raise Exception(f"API request failed with status {response.status}: {error_msg}") + + if "errors" in result: + error = result["errors"][0] + error_msg = error.get("message", "Unknown GraphQL error") + raise Exception(f"GraphQL error: {error_msg}") + + return result["data"] + except Exception as e: + logger.error(f"API request failed: {str(e)}", exc_info=True) + raise Exception(f"Failed to fetch data: {str(e)}") + + +async def list_top_tokens( + limit: Optional[int] = None, + networks: Optional[List[str]] = None, + resolution: Optional[str] = None, +) -> TopTokensResponse: + """Get list of trending tokens across specified networks.""" + try: + + # Map network names to IDs if networks are provided + network_filter = [] + if networks: + network_filter = [ + NETWORK_TO_ID_MAPPING[network] for network in networks if network in NETWORK_TO_ID_MAPPING + ] + + variables = { + "limit": min(limit or 20, 50), # Default 20, max 50 + "network_filter": network_filter, + "resolution": resolution or "1D", # Default to 1 day + } + + query = """ + query ListTopTokens($limit: Int, $networkFilter: [Int!], $resolution: String) { + listTopTokens(limit: $limit, networkFilter: $networkFilter, resolution: $resolution) { + address + createdAt + decimals + id + imageBannerUrl + imageLargeUrl + imageSmallUrl + imageThumbUrl + isScam + lastTransaction + liquidity + marketCap + name + networkId + price + priceChange + priceChange1 + priceChange4 + priceChange12 + priceChange24 + quoteToken + resolution + symbol + topPairId + txnCount1 + txnCount4 + txnCount12 + txnCount24 + uniqueBuys1 + uniqueBuys4 + uniqueBuys12 + uniqueBuys24 + uniqueSells1 + uniqueSells4 + uniqueSells12 + uniqueSells24 + volume + } + } + """ + + response = await _make_graphql_request(query, variables) + return TopTokensResponse(success=True, data=response["listTopTokens"]) + except Exception as e: + logger.error(f"Failed to get top tokens: {str(e)}", exc_info=True) + raise Exception(f"Failed to get top tokens: {str(e)}") + + +async def _filter_tokens(token_name: str, network: str) -> TokenFilterResult: + """Helper function to find a token ID by name and network.""" + try: + network_id = NETWORK_TO_ID_MAPPING.get(network) + if not network_id: + raise Exception(f"Invalid network: {network}") + + variables = { + "phrase": token_name, + "filters": {"network": [network_id], "liquidity": {"gt": 100000}, "txnCount24": {"gt": 200}}, + "limit": 1, + } + + query = """ + query FilterTokens($phrase: String, $filters: TokenFilters, $limit: Int) { + filterTokens(phrase: $phrase, filters: $filters, limit: $limit) { + results { + buyCount1 + high1 + txnCount1 + uniqueTransactions1 + volume1 + liquidity + marketCap + priceUSD + pair { + token0 + token1 + } + exchanges { + name + } + token { + address + decimals + id + name + networkId + symbol + } + } + } + } + """ + logger.info(f"Making GraphQL request to filter tokens with query: {query} and variables: {variables}") + response = await _make_graphql_request(query, variables) + logger.info(f"Received filter tokens response: {response}") + + results = response["filterTokens"]["results"] + logger.info(f"Found {len(results)} matching tokens") + + if not results: + logger.warning(f"No token found matching {token_name} on {network}") + raise Exception(f"No token found matching {token_name} on {network}") + + token_info = TokenFilterResult(**results[0]) + logger.info(f"Selected token info: {token_info}") + return token_info + + except Exception as e: + logger.error(f"Failed to filter tokens: {str(e)}", exc_info=True) + raise Exception(f"Failed to filter tokens: {str(e)}") + + +async def get_top_holders_percent(token_name: str, network: str) -> TopHoldersResponse: + """Get percentage owned by top 10 holders for a token.""" + try: + logger.info(f"Getting top holders percentage for token {token_name} on {network}") + # Strip special characters from token name + token_name = "".join(c for c in token_name if c.isalnum() or c.isspace()) + token_name = token_name.strip() + + if not token_name: + raise Exception("Token name cannot be empty after stripping special characters") + + logger.info(f"Sanitized token name: {token_name}") + # First get the token info by filtering tokens + logger.info(f"Filtering tokens to get token info") + token_info = await _filter_tokens(token_name, network) + logger.info(f"Found token info: {token_info}") + + if not token_info.token: + raise Exception("Token info missing token details") + + # Then get top holders percentage using token ID + variables = {"tokenId": token_info.token.id} + logger.info(f"Getting top holders with variables: {variables}") + + query = """ + query GetTop10HoldersPercent($tokenId: String!) { + top10HoldersPercent(tokenId: $tokenId) + } + """ + + logger.info("Making GraphQL request for top holders percentage") + response = await _make_graphql_request(query, variables) + logger.info(f"Received top holders response: {response}") + + percentage = response["top10HoldersPercent"] + logger.info(f"Top 10 holders own {percentage}% of token {token_name}") + + return TopHoldersResponse(success=True, data=percentage, token_info=token_info) + except Exception as e: + logger.error(f"Failed to get top holders percentage: {str(e)}", exc_info=True) + raise Exception(f"Failed to get top holders percentage: {str(e)}") + + +async def search_nfts( + search: str, + limit: Optional[int] = None, + network_filter: Optional[List[int]] = None, + filter_wash_trading: Optional[bool] = None, + window: Optional[str] = None, +) -> NftSearchResponse: + """Search for NFT collections by name or address.""" + try: + variables = { + "search": search, + "limit": limit or 20, # Default to 20 results + "networkFilter": network_filter or [], + "filterWashTrading": filter_wash_trading or False, + "window": window or "1d", # Default to 1 day + } + + query = """ + query SearchNFTs($search: String!, $limit: Int, $networkFilter: [Int!], $filterWashTrading: Boolean, $window: String) { + searchNfts(search: $search, limit: $limit, networkFilter: $networkFilter, filterWashTrading: $filterWashTrading, window: $window) { + hasMore + items { + address + average + ceiling + floor + id + imageUrl + name + networkId + symbol + tradeCount + tradeCountChange + volume + volumeChange + window + } + } + } + """ + + response = await _make_graphql_request(query, variables) + return NftSearchResponse( + success=True, hasMore=response["searchNfts"]["hasMore"], items=response["searchNfts"]["items"] + ) + except Exception as e: + logger.error(f"Failed to search NFTs: {str(e)}", exc_info=True) + raise Exception(f"Failed to search NFTs: {str(e)}") diff --git a/submodules/moragents_dockers/agents/src/agents/dca_agent/__init__.py b/submodules/agents/src/services/agents/codex/utils/__init__.py similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/dca_agent/__init__.py rename to submodules/agents/src/services/agents/codex/utils/__init__.py diff --git a/submodules/agents/src/services/agents/codex/utils/networks.py b/submodules/agents/src/services/agents/codex/utils/networks.py new file mode 100644 index 00000000..5bbbe128 --- /dev/null +++ b/submodules/agents/src/services/agents/codex/utils/networks.py @@ -0,0 +1,110 @@ +NETWORK_TO_ID_MAPPING = { + "Metis": 1088, + "HyperEVM": 999, + "Swellchain": 1923, + "Mantle": 5000, + "Klaytn": 8217, + "opBNB": 204, + "Odyssey Chain": 153153, + "Wanchain": 888, + "Shibarium": 109, + "Telos": 40, + "Zircuit": 48900, + "Polygon Mumbai": 80001, + "Celo": 42220, + "Evmos": 9001, + "Base Sepolia": 84532, + "Dogechain": 2000, + "Saigon": 2021, + "Vector": 420042, + "Vana": 1480, + "Sophon": 50104, + "OEC": 66, + "Aurora": 1313161554, + "CheeseChain": 383353, + "Velas": 106, + "Manta": 169, + "Ethereum": 1, + "Ethereum Sepolia": 11155111, + "Arbitrum Nova": 42170, + "Chiliz": 88888, + "Oasis Emerald": 42262, + "Sei": 531, + "Linea": 59144, + "Plume": 98865, + "Energi": 39797, + "Echos": 4321, + "Milkomeda": 2001, + "Blast Sepolia": 168587773, + "Berachain Artio": 80085, + "Berachain bArtio": 80084, + "Abstract Testnet": 11124, + "Treasure": 61166, + "Unichain": 130, + "Scroll": 534352, + "zkSync": 324, + "Meter": 82, + "Polygon": 137, + "Conwai": 668668, + "Base": 8453, + "KardiaChain": 24, + "Energy Web": 246, + "Smartbch": 10000, + "Monad Testnet": 10143, + "Echelon": 3000, + "Sonic": 146, + "Fantom": 250, + "Blast": 81457, + "Story": 1514, + "Polygon zkEVM": 1101, + "MELD": 333000333, + "Heco": 128, + "Sei Arctic": 713715, + "Goerli": 5, + "Ronin": 2020, + "Mode": 34443, + "Polis": 333999, + "Shiden": 336, + "IoTeX": 4689, + "xDai": 100, + "Moonbeam": 1284, + "Abstract": 2741, + "re.al": 111188, + "Sui": 101, + "Tron": 728126428, + "Moonriver": 1285, + "Sanko Sepolia": 1992, + "Degen Chain": 666666666, + "Ham": 5112, + "Solana": 1399811149, + "World Chain": 480, + "ZYX": 55, + "Xai": 660279, + "Ink": 57073, + "Binance Smart Chain": 56, + "Hoo Smart Chain": 70, + "Berachain": 80094, + "Avalanche": 43114, + "Boba": 288, + "Elastos": 20, + "Harmony": 1666600000, + "Avalanche DFK": 53935, + "Core": 1116, + "Callisto": 820, + "Story Iliad": 1513, + "Sanko": 1996, + "Pulsechain": 369, + "KuCoin Community Chain": 321, + "Arbitrum": 42161, + "Cronos": 25, + "ApeChain": 33139, + "Astar": 592, + "Superposition": 55244, + "Optimism": 10, + "Syscoin": 57, + "Berachain Old": 80089, + "Conflux": 1030, + "Canto": 7700, + "Flow EVM": 747, + "Fuse": 122, +} diff --git a/submodules/agents/src/services/agents/codex/utils/tool_types.py b/submodules/agents/src/services/agents/codex/utils/tool_types.py new file mode 100644 index 00000000..77447fda --- /dev/null +++ b/submodules/agents/src/services/agents/codex/utils/tool_types.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class CodexToolType(Enum): + """Enum for different Codex API tool types""" + + LIST_TOP_TOKENS = "list_top_tokens" + GET_TOP_HOLDERS_PERCENT = "get_top_holders_percent" + SEARCH_NFTS = "search_nfts" diff --git a/submodules/moragents_dockers/agents/src/agents/create_new_agent.sh b/submodules/agents/src/services/agents/create_new_agent.sh similarity index 96% rename from submodules/moragents_dockers/agents/src/agents/create_new_agent.sh rename to submodules/agents/src/services/agents/create_new_agent.sh index f680728b..9a4aafab 100755 --- a/submodules/moragents_dockers/agents/src/agents/create_new_agent.sh +++ b/submodules/agents/src/services/agents/create_new_agent.sh @@ -29,8 +29,8 @@ create_agent_files() { import logging from typing import Any, Dict -from src.agents.agent_core.agent import AgentCore -from src.models.core import ChatRequest, AgentResponse +from services.agents.agent_core.agent import AgentCore +from models.service.chat_models import ChatRequest, AgentResponse from langchain.schema import HumanMessage, SystemMessage logger = logging.getLogger(__name__) diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/README.md b/submodules/agents/src/services/agents/crypto_data/README.md old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/crypto_data/README.md rename to submodules/agents/src/services/agents/crypto_data/README.md diff --git a/submodules/moragents_dockers/agents/src/agents/default/__init__.py b/submodules/agents/src/services/agents/crypto_data/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/default/__init__.py rename to submodules/agents/src/services/agents/crypto_data/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py b/submodules/agents/src/services/agents/crypto_data/agent.py old mode 100644 new mode 100755 similarity index 75% rename from submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py rename to submodules/agents/src/services/agents/crypto_data/agent.py index 3d556d68..b63d4c60 --- a/submodules/moragents_dockers/agents/src/agents/crypto_data/agent.py +++ b/submodules/agents/src/services/agents/crypto_data/agent.py @@ -1,8 +1,11 @@ import logging -from src.agents.crypto_data import tools -from src.models.core import ChatRequest, AgentResponse -from src.agents.agent_core.agent import AgentCore -from langchain.schema import HumanMessage, SystemMessage +from typing import Dict, Any + +from services.agents.crypto_data import tools +from models.service.chat_models import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore +from services.agents.crypto_data.config import Config +from services.agents.crypto_data.tool_types import CryptoDataToolType logger = logging.getLogger(__name__) @@ -10,25 +13,15 @@ class CryptoDataAgent(AgentCore): """Agent for handling cryptocurrency-related queries and data retrieval.""" - def __init__(self, config, llm, embeddings): - super().__init__(config, llm, embeddings) - self.tools_provided = tools.get_tools() + def __init__(self, config: Dict[str, Any], llm: Any) -> None: + super().__init__(config, llm) + self.tools_provided = Config.tools self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) async def _process_request(self, request: ChatRequest) -> AgentResponse: """Process the validated chat request for crypto-related queries.""" try: - messages = [ - SystemMessage( - content=( - "Don't make assumptions about function arguments - " - "they should always be supplied by the user. " - "Ask for clarification if a request is ambiguous." - ) - ), - HumanMessage(content=request.prompt.content), - ] - + messages = [Config.system_message, *request.messages_for_llm] result = self.tool_bound_llm.invoke(messages) return await self._handle_llm_response(result) @@ -41,32 +34,32 @@ async def _execute_tool(self, func_name: str, args: dict) -> AgentResponse: try: metadata = {} - if func_name == "get_price": + if func_name == CryptoDataToolType.GET_PRICE.value: if "coin_name" not in args: return AgentResponse.needs_info(content="Please provide the name of the coin to get its price") content = tools.get_coin_price_tool(args["coin_name"]) trading_symbol = tools.get_tradingview_symbol(tools.get_coingecko_id(args["coin_name"])) if trading_symbol: metadata["coinId"] = trading_symbol - elif func_name == "get_floor_price": + elif func_name == CryptoDataToolType.GET_FLOOR_PRICE.value: if "nft_name" not in args: return AgentResponse.needs_info( content="Please provide the name of the NFT collection to get its floor price" ) content = tools.get_nft_floor_price_tool(args["nft_name"]) - elif func_name == "get_fdv": + elif func_name == CryptoDataToolType.GET_FULLY_DILUTED_VALUATION.value: if "coin_name" not in args: return AgentResponse.needs_info( content="Please provide the name of the coin to get its fully diluted valuation" ) content = tools.get_fully_diluted_valuation_tool(args["coin_name"]) - elif func_name == "get_tvl": + elif func_name == CryptoDataToolType.GET_TOTAL_VALUE_LOCKED.value: if "protocol_name" not in args: return AgentResponse.needs_info( content="Please provide the name of the protocol to get its total value locked" ) content = tools.get_protocol_total_value_locked_tool(args["protocol_name"]) - elif func_name == "get_market_cap": + elif func_name == CryptoDataToolType.GET_MARKET_CAP.value: if "coin_name" not in args: return AgentResponse.needs_info(content="Please provide the name of the coin to get its market cap") content = tools.get_coin_market_cap_tool(args["coin_name"]) diff --git a/submodules/agents/src/services/agents/crypto_data/config.py b/submodules/agents/src/services/agents/crypto_data/config.py new file mode 100755 index 00000000..53a12925 --- /dev/null +++ b/submodules/agents/src/services/agents/crypto_data/config.py @@ -0,0 +1,158 @@ +from langchain.schema import SystemMessage + +from models.service.agent_config import AgentConfig +from services.agents.crypto_data.tool_types import CryptoDataToolType + + +class Config: + """Configuration for Crypto Data Agent.""" + + # ************* + # AGENT CONFIG + # ************* + + agent_config = AgentConfig( + path="services.agents.crypto_data.agent", + class_name="CryptoDataAgent", + description="Fetches basic cryptocurrency data such as price, market cap, TVL, and FDV from various sources.", + delegator_description=( + "NOT a specialized agent. Fetches ONLY the very basic metrics for individual crypto assets including: current price, " + "market cap, fully diluted value (FDV), NFT floor prices, and TVL for DeFi protocols. " + "This agent CANNOT handle finding the most active tokens, rugcheck / safety, or any queries for multiple tokens. " + "Only use this agent when requesting a single specific metric (like price or TVL) for ONE " + "specific asset. For broader market analysis or comparisons across multiple assets, use " + "other specialized agents." + ), + human_readable_name="Basic Crypto Metrics", + command="cryptodata", + upload_required=False, + ) + + # ************* + # SYSTEM MESSAGE + # ************* + + system_message = SystemMessage( + content="You are a cryptocurrency data analyst that can fetch various metrics about cryptocurrencies, NFTs and DeFi protocols. " + "You can get price data for any cryptocurrency, floor prices for NFTs, Total Value Locked (TVL) for DeFi protocols, " + "and market metrics like market cap and fully diluted valuation (FDV) for cryptocurrencies. " + "When users ask questions, carefully analyze what metric they're looking for and use the appropriate tool. " + "For example:\n" + "- For general price queries, use the price tool\n" + "- For NFT valuations, use the floor price tool\n" + "- For DeFi protocol size/usage, use the TVL tool\n" + "- For token valuations, use market cap or FDV tools\n\n" + "Don't make assumptions about function arguments - they should always be supplied by the user. " + "Ask for clarification if a request is ambiguous or if you're unsure which metric would be most appropriate." + ) + + # ************* + # TOOLS CONFIG + # ************* + tools = [ + { + "type": "function", + "function": { + "name": CryptoDataToolType.GET_PRICE.value, + "description": "Get the price of a cryptocurrency", + "parameters": { + "type": "object", + "properties": { + "coin_name": { + "type": "string", + "description": "The name of the coin.", + } + }, + "required": ["coin_name"], + }, + }, + }, + { + "type": "function", + "function": { + "name": CryptoDataToolType.GET_FLOOR_PRICE.value, + "description": "Get the floor price of an NFT", + "parameters": { + "type": "object", + "properties": { + "nft_name": { + "type": "string", + "description": "Name of the NFT", + } + }, + "required": ["nft_name"], + }, + }, + }, + { + "type": "function", + "function": { + "name": CryptoDataToolType.GET_TOTAL_VALUE_LOCKED.value, + "description": "Get the TVL (Total Value Locked) of a protocol.", + "parameters": { + "type": "object", + "properties": { + "protocol_name": { + "type": "string", + "description": "Name of the protocol", + } + }, + "required": ["protocol_name"], + }, + }, + }, + { + "type": "function", + "function": { + "name": CryptoDataToolType.GET_FULLY_DILUTED_VALUATION.value, + "description": "Get the fdv or fully diluted valuation of a coin", + "parameters": { + "type": "object", + "properties": { + "coin_name": { + "type": "string", + "description": "Name of the coin", + } + }, + "required": ["coin_name"], + }, + }, + }, + { + "type": "function", + "function": { + "name": CryptoDataToolType.GET_MARKET_CAP.value, + "description": "Get the mc or market cap of a coin", + "parameters": { + "type": "object", + "properties": { + "coin_name": { + "type": "string", + "description": "Name of the coin", + } + }, + "required": ["coin_name"], + }, + }, + }, + ] + + # ************* + # API CONFIG + # ************* + + COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3" + DEFILLAMA_BASE_URL = "https://api.llama.fi" + + # Response messages + PRICE_SUCCESS_MESSAGE = "The price of {coin_name} is ${price:,}" + PRICE_FAILURE_MESSAGE = "Failed to retrieve price. Please enter a valid coin name." + FLOOR_PRICE_SUCCESS_MESSAGE = "The floor price of {nft_name} is ${floor_price:,}" + FLOOR_PRICE_FAILURE_MESSAGE = "Failed to retrieve floor price. Please enter a valid NFT name." + TVL_SUCCESS_MESSAGE = "The TVL of {protocol_name} is ${tvl:,}" + TVL_FAILURE_MESSAGE = "Failed to retrieve TVL. Please enter a valid protocol name." + FDV_SUCCESS_MESSAGE = "The fully diluted valuation of {coin_name} is ${fdv:,}" + FDV_FAILURE_MESSAGE = "Failed to retrieve FDV. Please enter a valid coin name." + MARKET_CAP_SUCCESS_MESSAGE = "The market cap of {coin_name} is ${market_cap:,}" + MARKET_CAP_FAILURE_MESSAGE = "Failed to retrieve market cap. Please enter a valid coin name." + API_ERROR_MESSAGE = "I can't seem to access the API at the moment." diff --git a/submodules/agents/src/services/agents/crypto_data/tests/benchmark/test_crypto_data_agent.py b/submodules/agents/src/services/agents/crypto_data/tests/benchmark/test_crypto_data_agent.py new file mode 100755 index 00000000..51391c9d --- /dev/null +++ b/submodules/agents/src/services/agents/crypto_data/tests/benchmark/test_crypto_data_agent.py @@ -0,0 +1,110 @@ +import logging +import pytest +from unittest.mock import patch +from typing import Dict, Any + +from services.agents.crypto_data.agent import CryptoDataAgent +from models.service.chat_models import ChatRequest, AgentResponse + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def crypto_agent(llm): + config: Dict[str, Any] = {"name": "crypto_data", "description": "Agent for crypto data queries"} + return CryptoDataAgent(config=config, llm=llm) + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_get_price_success(crypto_agent, make_chat_request): + request = make_chat_request(content="What's the price of Bitcoin?", agent_name="crypto_data") + + with patch("services.agents.crypto_data.tools.get_coin_price_tool") as mock_price: + mock_price.return_value = "Bitcoin price is $50,000" + with patch("services.agents.crypto_data.tools.get_coingecko_id") as mock_id: + mock_id.return_value = "bitcoin" + with patch("services.agents.crypto_data.tools.get_tradingview_symbol") as mock_symbol: + mock_symbol.return_value = "BTCUSD" + + response = await crypto_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert "Bitcoin price" in response.content + assert response.response_type.value == "success" + assert response.metadata.get("coinId") == "BTCUSD" + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_get_market_cap_success(crypto_agent, make_chat_request): + request = make_chat_request(content="What's the market cap of Ethereum?", agent_name="crypto_data") + + with patch("services.agents.crypto_data.tools.get_coin_market_cap_tool") as mock_mcap: + mock_mcap.return_value = "Ethereum market cap is $200B" + with patch("services.agents.crypto_data.tools.get_coingecko_id") as mock_id: + mock_id.return_value = "ethereum" + + response = await crypto_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert "market cap" in response.content + assert response.response_type.value == "success" + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_get_tvl_success(crypto_agent, make_chat_request): + request = make_chat_request(content="What's the TVL of Uniswap?", agent_name="crypto_data") + + with patch("services.agents.crypto_data.tools.get_protocol_total_value_locked_tool") as mock_tvl: + mock_tvl.return_value = "Uniswap TVL is $5B" + with patch("services.agents.crypto_data.tools.get_coingecko_id") as mock_id: + mock_id.return_value = "uniswap" + + response = await crypto_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert "TVL" in response.content + assert response.response_type.value == "success" + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_get_floor_price_success(crypto_agent, make_chat_request): + request = make_chat_request(content="What's the floor price of BAYC?", agent_name="crypto_data") + + with patch("services.agents.crypto_data.tools.get_nft_floor_price_tool") as mock_floor: + mock_floor.return_value = "BAYC floor price is 30 ETH" + with patch("services.agents.crypto_data.tools.get_coingecko_id") as mock_id: + mock_id.return_value = "bayc" + + response = await crypto_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert "floor price" in response.content + assert response.response_type.value == "success" + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_invalid_request(crypto_agent, make_chat_request): + request = make_chat_request(content="Do something invalid", agent_name="crypto_data") + + response = await crypto_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "needs_info" + assert "I don't know how to handle that type of request" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_missing_argument(crypto_agent, make_chat_request): + request = make_chat_request(content="Get price", agent_name="crypto_data") + + response = await crypto_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "needs_info" + assert "Please provide the name of the coin" in response.content diff --git a/submodules/agents/src/services/agents/crypto_data/tool_types.py b/submodules/agents/src/services/agents/crypto_data/tool_types.py new file mode 100644 index 00000000..1c588cea --- /dev/null +++ b/submodules/agents/src/services/agents/crypto_data/tool_types.py @@ -0,0 +1,11 @@ +from enum import Enum + + +class CryptoDataToolType(Enum): + """Enum for different Crypto Data API tool types""" + + GET_PRICE = "get_price" + GET_FLOOR_PRICE = "get_floor_price" + GET_FULLY_DILUTED_VALUATION = "get_fdv" + GET_TOTAL_VALUE_LOCKED = "get_tvl" + GET_MARKET_CAP = "get_market_cap" diff --git a/submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py b/submodules/agents/src/services/agents/crypto_data/tools.py old mode 100644 new mode 100755 similarity index 65% rename from submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py rename to submodules/agents/src/services/agents/crypto_data/tools.py index 4fc8306a..0df8521c --- a/submodules/moragents_dockers/agents/src/agents/crypto_data/tools.py +++ b/submodules/agents/src/services/agents/crypto_data/tools.py @@ -1,12 +1,13 @@ import logging +from typing import Any, Dict, List, Optional, Tuple, Union import requests from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity -from src.agents.crypto_data.config import Config +from services.agents.crypto_data.config import Config -def get_most_similar(text, data): +def get_most_similar(text: str, data: List[str]) -> List[str]: """Returns a list of most similar items based on cosine similarity.""" vectorizer = TfidfVectorizer() sentence_vectors = vectorizer.fit_transform(data) @@ -17,7 +18,7 @@ def get_most_similar(text, data): return top_matches -def get_coingecko_id(text, type="coin"): +def get_coingecko_id(text: str, type: str = "coin") -> Optional[str]: """Get the CoinGecko ID for a given coin or NFT.""" url = f"{Config.COINGECKO_BASE_URL}/search" params = {"query": text} @@ -36,8 +37,10 @@ def get_coingecko_id(text, type="coin"): raise -def get_tradingview_symbol(coingecko_id): +def get_tradingview_symbol(coingecko_id: Optional[str]) -> Optional[str]: """Convert a CoinGecko ID to a TradingView symbol.""" + if not coingecko_id: + return None url = f"{Config.COINGECKO_BASE_URL}/coins/{coingecko_id}" try: response = requests.get(url) @@ -50,7 +53,7 @@ def get_tradingview_symbol(coingecko_id): raise -def get_price(coin): +def get_price(coin: str) -> Optional[float]: """Get the price of a coin from CoinGecko API.""" coin_id = get_coingecko_id(coin, type="coin") if not coin_id: @@ -66,7 +69,7 @@ def get_price(coin): raise -def get_floor_price(nft): +def get_floor_price(nft: str) -> Optional[float]: """Get the floor price of an NFT from CoinGecko API.""" nft_id = get_coingecko_id(str(nft), type="nft") if not nft_id: @@ -81,7 +84,7 @@ def get_floor_price(nft): raise -def get_fdv(coin): +def get_fdv(coin: str) -> Optional[float]: """Get the fully diluted valuation of a coin from CoinGecko API.""" coin_id = get_coingecko_id(coin, type="coin") if not coin_id: @@ -97,7 +100,7 @@ def get_fdv(coin): raise -def get_market_cap(coin): +def get_market_cap(coin: str) -> Optional[float]: """Get the market cap of a coin from CoinGecko API.""" coin_id = get_coingecko_id(coin, type="coin") if not coin_id: @@ -113,7 +116,7 @@ def get_market_cap(coin): raise -def get_protocols_list(): +def get_protocols_list() -> Tuple[List[str], List[str], List[str]]: """Get the list of protocols from DefiLlama API.""" url = f"{Config.DEFILLAMA_BASE_URL}/protocols" try: @@ -130,7 +133,7 @@ def get_protocols_list(): raise -def get_tvl_value(protocol_id): +def get_tvl_value(protocol_id: str) -> Dict[str, Any]: """Gets the TVL value using the protocol ID from DefiLlama API.""" url = f"{Config.DEFILLAMA_BASE_URL}/tvl/{protocol_id}" try: @@ -142,7 +145,7 @@ def get_tvl_value(protocol_id): raise -def get_protocol_tvl(protocol_name): +def get_protocol_tvl(protocol_name: str) -> Optional[Dict[str, Any]]: """Get the TVL (Total Value Locked) of a protocol from DefiLlama API.""" id, name, gecko = get_protocols_list() tag = get_coingecko_id(protocol_name) @@ -155,18 +158,19 @@ def get_protocol_tvl(protocol_name): if not res: return None else: - result = [] + result: List[Dict[str, Any]] = [] for item in res: protocol_id = next((i for i, j in zip(id, name) if j == item), None) - tvl = get_tvl_value(protocol_id) - result.append({protocol_id: tvl}) + if protocol_id: + tvl = get_tvl_value(protocol_id) + result.append({protocol_id: tvl}) if not result: return None - max_key = max(result, key=lambda dct: dct.get(list(dct.keys())[0])) + max_key = max(result, key=lambda dct: float(dct[list(dct.keys())[0]]["tvl"])) return max_key -def get_coin_price_tool(coin_name): +def get_coin_price_tool(coin_name: str) -> str: """Get the price of a cryptocurrency.""" try: price = get_price(coin_name) @@ -177,7 +181,7 @@ def get_coin_price_tool(coin_name): return Config.API_ERROR_MESSAGE -def get_nft_floor_price_tool(nft_name): +def get_nft_floor_price_tool(nft_name: str) -> str: """Get the floor price of an NFT.""" try: floor_price = get_floor_price(nft_name) @@ -188,7 +192,7 @@ def get_nft_floor_price_tool(nft_name): return Config.API_ERROR_MESSAGE -def get_protocol_total_value_locked_tool(protocol_name): +def get_protocol_total_value_locked_tool(protocol_name: str) -> str: """Get the TVL (Total Value Locked) of a protocol.""" try: tvl = get_protocol_tvl(protocol_name) @@ -200,7 +204,7 @@ def get_protocol_total_value_locked_tool(protocol_name): return Config.API_ERROR_MESSAGE -def get_fully_diluted_valuation_tool(coin_name): +def get_fully_diluted_valuation_tool(coin_name: str) -> str: """Get the fully diluted valuation of a coin.""" try: fdv = get_fdv(coin_name) @@ -211,7 +215,7 @@ def get_fully_diluted_valuation_tool(coin_name): return Config.API_ERROR_MESSAGE -def get_coin_market_cap_tool(coin_name): +def get_coin_market_cap_tool(coin_name: str) -> str: """Get the market cap of a coin.""" try: market_cap = get_market_cap(coin_name) @@ -220,94 +224,3 @@ def get_coin_market_cap_tool(coin_name): return Config.MARKET_CAP_SUCCESS_MESSAGE.format(coin_name=coin_name, market_cap=market_cap) except requests.exceptions.RequestException: return Config.API_ERROR_MESSAGE - - -def get_tools(): - """Return a list of tools for the agent.""" - return [ - { - "type": "function", - "function": { - "name": "get_price", - "description": "Get the price of a cryptocurrency", - "parameters": { - "type": "object", - "properties": { - "coin_name": { - "type": "string", - "description": "The name of the coin.", - } - }, - "required": ["coin_name"], - }, - }, - }, - { - "type": "function", - "function": { - "name": "get_floor_price", - "description": "Get the floor price of an NFT", - "parameters": { - "type": "object", - "properties": { - "nft_name": { - "type": "string", - "description": "Name of the NFT", - } - }, - "required": ["nft_name"], - }, - }, - }, - { - "type": "function", - "function": { - "name": "get_tvl", - "description": "Get the TVL (Total Value Locked) of a protocol.", - "parameters": { - "type": "object", - "properties": { - "protocol_name": { - "type": "string", - "description": "Name of the protocol", - } - }, - "required": ["protocol_name"], - }, - }, - }, - { - "type": "function", - "function": { - "name": "get_fdv", - "description": "Get the fdv or fully diluted valuation of a coin", - "parameters": { - "type": "object", - "properties": { - "coin_name": { - "type": "string", - "description": "Name of the coin", - } - }, - "required": ["coin_name"], - }, - }, - }, - { - "type": "function", - "function": { - "name": "get_market_cap", - "description": "Get the mc or market cap of a coin", - "parameters": { - "type": "object", - "properties": { - "coin_name": { - "type": "string", - "description": "Name of the coin", - } - }, - "required": ["coin_name"], - }, - }, - }, - ] diff --git a/submodules/moragents_dockers/agents/src/agents/dca_agent/README.md b/submodules/agents/src/services/agents/dca_agent/README.md old mode 100644 new mode 100755 similarity index 71% rename from submodules/moragents_dockers/agents/src/agents/dca_agent/README.md rename to submodules/agents/src/services/agents/dca_agent/README.md index b3b2227f..fd128bd8 --- a/submodules/moragents_dockers/agents/src/agents/dca_agent/README.md +++ b/submodules/agents/src/services/agents/dca_agent/README.md @@ -1,43 +1,52 @@ # Introduction to the Coinbase Enabled Agent Experience + Welcome to the new world of the Coinbase enabled agent experience. This guide will walk you through the process of setting up your Coinbase API configuration and using the available features. ## Setting Up Coinbase API Configuration + To get started, you'll need to create your Coinbase developer platform API key and API secret by visiting your Coinbase developer portal. Once you have your API key and API secret, follow these steps: -1. Spin up the MORagents application + +1. Spin up the MySuperAgent application 2. Click **Settings** at the top 3. Click **Coinbase API** 4. Paste your API key and API secret 5. Click **Save Coinbase Credentials** to lock these values in ## Creating a Coinbase Wallet + Note that the wallet you use to perform dollar cost averaging and gasless sends is separate from your browser wallet integration. To create your Coinbase wallet: -1. Click **CDP Wallets** at the top of the UI for MORagents + +1. Click **CDP Wallets** at the top of the UI for MySuperAgent 2. Click **Create New Wallet** -This will create a new local wallet file. Please download this file and store it somewhere safe, as subsequent openings of the MORagents app may not restore it automatically. + This will create a new local wallet file. Please download this file and store it somewhere safe, as subsequent openings of the MySuperAgent app may not restore it automatically. ## Restoring Your Coinbase Wallet + To restore your wallet, follow these steps: + 1. Click **CDP Wallets** 2. Click **Create New Wallet** 3. Click **Restore Existing** 4. Choose the JSON file you downloaded earlier -It's imperative that you have the JSON file somewhere secure, as future openings of this app may not necessarily restore it, and not doing so may result in a loss of funds. + It's imperative that you have the JSON file somewhere secure, as future openings of this app may not necessarily restore it, and not doing so may result in a loss of funds. ### To fund your wallet: + 1. Click **CDP Wallets** at the top 2. Click the copy icon to retrieve the address of your Coinbase local wallet 3. Use MetaMask or any other wallet set to the base L2 to send USDC to this address -Once your wallet is funded, you can ask the agent, "what is my USDC balance on base?" to see the updated balance. To send USDC from this agent to another address, type "send USDC on base" and follow the prompts. + Once your wallet is funded, you can ask the agent, "what is my USDC balance on base?" to see the updated balance. To send USDC from this agent to another address, type "send USDC on base" and follow the prompts. ### Dollar Cost Averaging (DCA) Swaps + The DCA agent allows you to set up a recurring workflow to swap from one currency to another, such as from USDC to DAI. To use this feature: + 1. Fund your wallet with ETH on the base L2 (you can send ETH to this wallet the same way you sent USDC earlier) 2. Say "set up a DCA strategy" to the agent 3. Specify the frequency, amount, and maximum spend level in the widget that opens -Note that your app needs to be running for the executions to occur at the specified time. + Note that your app needs to be running for the executions to occur at the specified time. 4. You can verify your DCA strategies by clicking "Workflows" at the top -![workflowsview](../../../../../../images/dca-workflows-view.png) - ## Community Development -All agents, including the Gasless Send and DCA agents are provided under the MIT license for the community to build and explore. They are references for the community to build and eventually ship to the decentralized inference of Morpheus in early 2025, where these agents can live and function for long periods of time without needing local compute. \ No newline at end of file + +All agents, including the Gasless Send and DCA agents are provided under the MIT license for the community to build and explore. They are references for the community to build and eventually ship to the decentralized inference of Morpheus in early 2025, where these agents can live and function for long periods of time without needing local compute. diff --git a/submodules/moragents_dockers/agents/src/agents/dexscreener/__init__.py b/submodules/agents/src/services/agents/dca_agent/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/dexscreener/__init__.py rename to submodules/agents/src/services/agents/dca_agent/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/dca_agent/agent.py b/submodules/agents/src/services/agents/dca_agent/agent.py old mode 100644 new mode 100755 similarity index 91% rename from submodules/moragents_dockers/agents/src/agents/dca_agent/agent.py rename to submodules/agents/src/services/agents/dca_agent/agent.py index 1ddc14e0..57d1d27e --- a/submodules/moragents_dockers/agents/src/agents/dca_agent/agent.py +++ b/submodules/agents/src/services/agents/dca_agent/agent.py @@ -1,9 +1,9 @@ import logging from typing import Dict, Any -from src.models.core import ChatRequest, AgentResponse -from src.agents.agent_core.agent import AgentCore -from src.stores import wallet_manager_instance +from models.service.chat_models import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore +from stores import wallet_manager_instance from langchain.schema import HumanMessage, SystemMessage logger = logging.getLogger(__name__) @@ -12,9 +12,9 @@ class DCAAgent(AgentCore): """Agent for handling DCA (Dollar Cost Averaging) strategies.""" - def __init__(self, config: Dict[str, Any], llm: Any, embeddings: Any): + def __init__(self, config: Dict[str, Any], llm: Any): """Initialize the DCAAgent.""" - super().__init__(config, llm, embeddings) + super().__init__(config, llm) # TODO: Create specialized tools to pull out DCA params from user message # For now, we can ignore these tools diff --git a/submodules/agents/src/services/agents/dca_agent/config.py b/submodules/agents/src/services/agents/dca_agent/config.py new file mode 100755 index 00000000..53a42927 --- /dev/null +++ b/submodules/agents/src/services/agents/dca_agent/config.py @@ -0,0 +1,63 @@ +from models.service.agent_config import AgentConfig + + +class Config: + """Configuration for DCA (Dollar Cost Averaging) Agent.""" + + # ************* + # AGENT CONFIG + # ************* + + agent_config = AgentConfig( + path="services.agents.dca_agent.agent", + class_name="DCAAgent", + description="Handles automated dollar cost averaging transactions. Use when the user wants to set up recurring purchases of crypto assets.", + delegator_description="Manages dollar-cost averaging setup and execution, including schedule creation, " + "asset allocation adjustment, and transaction automation across multiple networks. Use when users mention " + "recurring purchases, DCA strategies, or automated investment approaches.", + human_readable_name="DCA Transaction Manager", + command="dca", + upload_required=False, + ) + + # ************* + # TOOLS CONFIG + # ************* + + tools = [ + { + "name": "setup_dca", + "description": "Set up a new DCA schedule for recurring asset purchases", + "parameters": { + "type": "object", + "properties": { + "amount": {"type": "string", "description": "Amount to invest per interval"}, + "asset_id": {"type": "string", "description": "Asset ID to purchase"}, + "interval": { + "type": "string", + "description": "Time interval between purchases (daily/weekly/monthly)", + }, + }, + "required": ["amount", "asset_id", "interval"], + }, + }, + { + "name": "get_dca_schedule", + "description": "Get current DCA schedule details", + "parameters": { + "type": "object", + "properties": {}, + }, + }, + { + "name": "cancel_dca", + "description": "Cancel an existing DCA schedule", + "parameters": { + "type": "object", + "properties": { + "schedule_id": {"type": "string", "description": "ID of the DCA schedule to cancel"}, + }, + "required": ["schedule_id"], + }, + }, + ] diff --git a/submodules/moragents_dockers/agents/src/agents/dca_agent/routes.py b/submodules/agents/src/services/agents/dca_agent/routes.py old mode 100644 new mode 100755 similarity index 87% rename from submodules/moragents_dockers/agents/src/agents/dca_agent/routes.py rename to submodules/agents/src/services/agents/dca_agent/routes.py index 30e254e8..c7adb971 --- a/submodules/moragents_dockers/agents/src/agents/dca_agent/routes.py +++ b/submodules/agents/src/services/agents/dca_agent/routes.py @@ -2,8 +2,8 @@ from decimal import Decimal from fastapi import APIRouter from fastapi.responses import JSONResponse -from src.stores import workflow_manager_instance, wallet_manager_instance -from src.agents.dca_agent.tools import DCAParams, create_dca_workflow +from stores import workflow_manager_instance, wallet_manager_instance +from services.agents.dca_agent.tools import DCAParams, create_dca_workflow router = APIRouter(prefix="/dca", tags=["dca"]) logger = logging.getLogger(__name__) @@ -21,17 +21,11 @@ async def create_strategy(data: dict): destination_token=data["destinationToken"].lower(), step_size=Decimal(str(data["stepSize"])), total_investment_amount=( - Decimal(str(data["totalInvestmentAmount"])) - if data.get("totalInvestmentAmount") - else None + Decimal(str(data["totalInvestmentAmount"])) if data.get("totalInvestmentAmount") else None ), frequency=data["frequency"], - max_purchase_amount=( - Decimal(str(data["maxPurchaseAmount"])) if data.get("maxPurchaseAmount") else None - ), - price_threshold=( - Decimal(str(data["priceThreshold"])) if data.get("priceThreshold") else None - ), + max_purchase_amount=(Decimal(str(data["maxPurchaseAmount"])) if data.get("maxPurchaseAmount") else None), + price_threshold=(Decimal(str(data["priceThreshold"])) if data.get("priceThreshold") else None), pause_on_volatility=data.get("pauseOnVolatility", False), wallet_id=wallet_manager_instance.active_wallet_id, ) diff --git a/submodules/agents/src/services/agents/dca_agent/tests/benchmarks/test_dca_agent.py b/submodules/agents/src/services/agents/dca_agent/tests/benchmarks/test_dca_agent.py new file mode 100755 index 00000000..9785e66f --- /dev/null +++ b/submodules/agents/src/services/agents/dca_agent/tests/benchmarks/test_dca_agent.py @@ -0,0 +1,76 @@ +import logging +import pytest +from unittest.mock import patch +from typing import Dict, Any + +from services.agents.dca_agent.agent import DCAAgent +from models.service.chat_models import ChatRequest, AgentResponse + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def dca_agent(llm): + config: Dict[str, Any] = {"name": "dca", "description": "Agent for DCA strategies"} + return DCAAgent(config=config, llm=llm) + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_missing_cdp_client(dca_agent, make_chat_request): + request = make_chat_request(content="Set up DCA strategy", agent_name="dca") + + with patch("stores.wallet_manager_instance.configure_cdp_client") as mock_cdp: + mock_cdp.return_value = False + + response = await dca_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "needs_info" + assert "CDP client is not initialized" in response.content + assert "API credentials" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_missing_wallet(dca_agent, make_chat_request): + request = make_chat_request(content="Set up DCA strategy", agent_name="dca") + + with patch("stores.wallet_manager_instance.configure_cdp_client") as mock_cdp: + mock_cdp.return_value = True + with patch("stores.wallet_manager_instance.get_active_wallet") as mock_wallet: + mock_wallet.return_value = None + + response = await dca_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "needs_info" + assert "select or create a wallet" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_successful_dca_setup(dca_agent, make_chat_request): + request = make_chat_request(content="Set up DCA strategy", agent_name="dca") + + with patch("stores.wallet_manager_instance.configure_cdp_client") as mock_cdp: + mock_cdp.return_value = True + with patch("stores.wallet_manager_instance.get_active_wallet") as mock_wallet: + mock_wallet.return_value = "mock_wallet" + + response = await dca_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "action_required" + assert response.content == "Ready to set up DCA" + assert response.action_type == "dca" + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_execute_unknown_tool(dca_agent): + response = await dca_agent._execute_tool("unknown_function", {}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "needs_info" + assert "I don't know how to unknown_function yet" in response.content diff --git a/submodules/moragents_dockers/agents/src/agents/dca_agent/tools.py b/submodules/agents/src/services/agents/dca_agent/tools.py old mode 100644 new mode 100755 similarity index 86% rename from submodules/moragents_dockers/agents/src/agents/dca_agent/tools.py rename to submodules/agents/src/services/agents/dca_agent/tools.py index 38ba4fa4..d81be651 --- a/submodules/moragents_dockers/agents/src/agents/dca_agent/tools.py +++ b/submodules/agents/src/services/agents/dca_agent/tools.py @@ -3,8 +3,8 @@ from datetime import timedelta from dataclasses import dataclass from decimal import Decimal -from src.stores import wallet_manager_instance -from src.agents.base_agent.tools import get_balance, swap_assets +from stores import wallet_manager_instance +from services.agents.base_agent.tools import get_balance, swap_assets logger = logging.getLogger(__name__) @@ -28,13 +28,9 @@ def to_dict(self) -> dict: "origin_token": self.origin_token, "destination_token": self.destination_token, "step_size": str(self.step_size), - "total_investment_amount": ( - str(self.total_investment_amount) if self.total_investment_amount else None - ), + "total_investment_amount": (str(self.total_investment_amount) if self.total_investment_amount else None), "frequency": self.frequency, - "max_purchase_amount": ( - str(self.max_purchase_amount) if self.max_purchase_amount else None - ), + "max_purchase_amount": (str(self.max_purchase_amount) if self.max_purchase_amount else None), "price_threshold": str(self.price_threshold) if self.price_threshold else None, "pause_on_volatility": self.pause_on_volatility, "wallet_id": self.wallet_id, @@ -47,17 +43,11 @@ def from_dict(cls, data: dict) -> "DCAParams": destination_token=data["destination_token"].lower(), step_size=Decimal(data["step_size"]), total_investment_amount=( - Decimal(data["total_investment_amount"]) - if data.get("total_investment_amount") - else None + Decimal(data["total_investment_amount"]) if data.get("total_investment_amount") else None ), frequency=data["frequency"], - max_purchase_amount=( - Decimal(data["max_purchase_amount"]) if data.get("max_purchase_amount") else None - ), - price_threshold=( - Decimal(data["price_threshold"]) if data.get("price_threshold") else None - ), + max_purchase_amount=(Decimal(data["max_purchase_amount"]) if data.get("max_purchase_amount") else None), + price_threshold=(Decimal(data["price_threshold"]) if data.get("price_threshold") else None), pause_on_volatility=data.get("pause_on_volatility", False), wallet_id=data.get("wallet_id"), ) diff --git a/submodules/moragents_dockers/agents/src/agents/imagen/__init__.py b/submodules/agents/src/services/agents/default/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/imagen/__init__.py rename to submodules/agents/src/services/agents/default/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/default/agent.py b/submodules/agents/src/services/agents/default/agent.py old mode 100644 new mode 100755 similarity index 85% rename from submodules/moragents_dockers/agents/src/agents/default/agent.py rename to submodules/agents/src/services/agents/default/agent.py index 9f19875f..da8d860c --- a/submodules/moragents_dockers/agents/src/agents/default/agent.py +++ b/submodules/agents/src/services/agents/default/agent.py @@ -1,10 +1,10 @@ import logging from typing import Dict, Any -from src.models.core import ChatRequest, AgentResponse -from src.agents.agent_core.agent import AgentCore -from src.stores import agent_manager_instance -from langchain.schema import HumanMessage, SystemMessage +from models.service.chat_models import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore +from stores import agent_manager_instance +from langchain.schema import SystemMessage logger = logging.getLogger(__name__) @@ -12,8 +12,8 @@ class DefaultAgent(AgentCore): """Agent for handling general conversation and providing information about Morpheus agents.""" - def __init__(self, config: Dict[str, Any], llm: Any, embeddings: Any): - super().__init__(config, llm, embeddings) + def __init__(self, config: Dict[str, Any], llm: Any): + super().__init__(config, llm) async def _process_request(self, request: ChatRequest) -> AgentResponse: """Process the validated chat request for general conversation.""" @@ -39,7 +39,7 @@ async def _process_request(self, request: ChatRequest) -> AgentResponse: messages = [ SystemMessage(content=system_prompt), - HumanMessage(content=request.prompt.content), + *request.messages_for_llm, ] result = self.llm.invoke(messages) diff --git a/submodules/agents/src/services/agents/default/config.py b/submodules/agents/src/services/agents/default/config.py new file mode 100755 index 00000000..f71bb529 --- /dev/null +++ b/submodules/agents/src/services/agents/default/config.py @@ -0,0 +1,53 @@ +from models.service.agent_config import AgentConfig + + +class Config: + """Configuration for Default Agent.""" + + # ************* + # AGENT CONFIG + # ------------ + # This must be defined in every agent config file + # It is required to load the agent + # ************* + + agent_config = AgentConfig( + path="services.agents.default.agent", + class_name="DefaultAgent", + description="Ask about active Morpheus agents, and also handles general questions", + delegator_description="Handles meta-queries about Morpheus itself, available agents, system capabilities, and " + "general cryptocurrency questions that don't require specialized agents. Use when no other agent is clearly " + "applicable or for simple informational requests.", + human_readable_name="Morpheus Default", + command="morpheus", + upload_required=False, + ) + + # ************* + # TOOLS CONFIG + # ************* + + tools = [ + { + "name": "get_available_agents", + "description": "Get list of all available agents and their descriptions", + "parameters": { + "type": "object", + "properties": {}, + }, + }, + { + "name": "get_agent_info", + "description": "Get detailed information about a specific agent", + "parameters": { + "type": "object", + "properties": { + "agent_name": { + "type": "string", + "description": "Name of the agent to get info for", + "required": True, + } + }, + }, + }, + ] diff --git a/submodules/agents/src/services/agents/default/tests/benchmarks/test_default_agent.py b/submodules/agents/src/services/agents/default/tests/benchmarks/test_default_agent.py new file mode 100755 index 00000000..bbb38566 --- /dev/null +++ b/submodules/agents/src/services/agents/default/tests/benchmarks/test_default_agent.py @@ -0,0 +1,82 @@ +import logging +import pytest +from unittest.mock import patch +from typing import Dict, Any + +from services.agents.default.agent import DefaultAgent +from models.service.chat_models import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore +from stores import agent_manager_instance +from langchain.schema import SystemMessage + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def default_agent(llm): + config: Dict[str, Any] = {"name": "default", "description": "Agent for general conversation"} + return DefaultAgent(config=config, llm=llm) + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_general_conversation(default_agent, make_chat_request): + request = make_chat_request(content="What is the weather like?", agent_name="default") + + with patch("stores.agent_manager_instance.get_available_agents") as mock_available: + mock_available.return_value = [] + with patch("stores.agent_manager_instance.get_selected_agents") as mock_selected: + mock_selected.return_value = [] + + response = await default_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert response.content is not None + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_agent_info_request(default_agent, make_chat_request): + request = make_chat_request(content="What can Morpheus agents do?", agent_name="default") + + mock_agents = [ + {"name": "crypto_data", "human_readable_name": "Crypto Data", "description": "Crypto data queries"}, + {"name": "dca", "human_readable_name": "DCA", "description": "DCA strategies"}, + ] + + with patch("stores.agent_manager_instance.get_available_agents") as mock_available: + mock_available.return_value = mock_agents + with patch("stores.agent_manager_instance.get_selected_agents") as mock_selected: + mock_selected.return_value = ["crypto_data", "dca"] + + response = await default_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert response.content is not None + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_error_handling(default_agent, make_chat_request): + request = make_chat_request(content="Test error handling", agent_name="default") + + with patch("stores.agent_manager_instance.get_available_agents") as mock_available: + mock_available.side_effect = Exception("Test error") + + response = await default_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Test error" in response.error_message + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_unknown_tool(default_agent): + response = await default_agent._execute_tool("unknown_function", {}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Unknown tool" in response.error_message diff --git a/submodules/moragents_dockers/agents/src/agents/mor_claims/__init__.py b/submodules/agents/src/services/agents/dexscreener/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/mor_claims/__init__.py rename to submodules/agents/src/services/agents/dexscreener/__init__.py diff --git a/submodules/agents/src/services/agents/dexscreener/agent.py b/submodules/agents/src/services/agents/dexscreener/agent.py new file mode 100755 index 00000000..1ce46ad9 --- /dev/null +++ b/submodules/agents/src/services/agents/dexscreener/agent.py @@ -0,0 +1,74 @@ +import logging +from typing import Dict, Any, Union +from models.service.agent_core import AgentCore +from models.service.chat_models import ChatRequest, AgentResponse +from .config import Config +from . import tools +from .tool_types import DexScreenerToolType +from .models import ( + TokenProfileResponse, + BoostedTokenResponse, + DexPairSearchResponse, +) + +logger = logging.getLogger(__name__) + + +class DexScreenerAgent(AgentCore): + """Agent for interacting with DexScreener Token API.""" + + def __init__(self, config: Dict[str, Any], llm: Any): + super().__init__(config, llm) + self.tools_provided = Config.tools + self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) + + async def _process_request(self, request: ChatRequest) -> AgentResponse: + """Process the validated chat request for DexScreener API interactions.""" + try: + messages = [Config.system_message, *request.messages_for_llm] + result = self.tool_bound_llm.invoke(messages) + return await self._handle_llm_response(result) + + except Exception as e: + logger.error(f"Error processing request: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) + + async def _execute_tool(self, func_name: str, args: Dict[str, Any]) -> AgentResponse: + """Execute the appropriate DexScreener API tool based on function name.""" + try: + api_result: Union[DexPairSearchResponse, TokenProfileResponse, BoostedTokenResponse] + + if func_name == DexScreenerToolType.SEARCH_DEX_PAIRS.value: + api_result = await tools.search_dex_pairs(args["query"]) + return AgentResponse.success( + content=api_result.formatted_response, + metadata=api_result.model_dump(), + action_type=DexScreenerToolType.SEARCH_DEX_PAIRS.value, + ) + elif func_name == DexScreenerToolType.GET_LATEST_TOKEN_PROFILES.value: + api_result = await tools.get_latest_token_profiles(args.get("chain_id")) + return AgentResponse.success( + content=api_result.formatted_response, + metadata=api_result.model_dump(), + action_type=DexScreenerToolType.GET_LATEST_TOKEN_PROFILES.value, + ) + elif func_name == DexScreenerToolType.GET_LATEST_BOOSTED_TOKENS.value: + api_result = await tools.get_latest_boosted_tokens(args.get("chain_id")) + return AgentResponse.success( + content=api_result.formatted_response, + metadata=api_result.model_dump(), + action_type=DexScreenerToolType.GET_LATEST_BOOSTED_TOKENS.value, + ) + elif func_name == DexScreenerToolType.GET_TOP_BOOSTED_TOKENS.value: + api_result = await tools.get_top_boosted_tokens(args.get("chain_id")) + return AgentResponse.success( + content=api_result.formatted_response, + metadata=api_result.model_dump(), + action_type=DexScreenerToolType.GET_TOP_BOOSTED_TOKENS.value, + ) + else: + return AgentResponse.error(error_message=f"Unknown tool: {func_name}") + + except Exception as e: + logger.error(f"Error executing tool {func_name}: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) diff --git a/submodules/agents/src/services/agents/dexscreener/config.py b/submodules/agents/src/services/agents/dexscreener/config.py new file mode 100755 index 00000000..44aabcfb --- /dev/null +++ b/submodules/agents/src/services/agents/dexscreener/config.py @@ -0,0 +1,118 @@ +from models.service.agent_config import AgentConfig +from langchain.schema import SystemMessage +from .tool_types import DexScreenerToolType + + +class Config: + """Configuration for DexScreener Token API.""" + + # ************* + # AGENT CONFIG + # ------------ + # This must be defined in every agent config file + # It is required to load the agent + # ************* + + agent_config = AgentConfig( + path="services.agents.dexscreener.agent", + class_name="DexScreenerAgent", + description="Fetches and analyzes cryptocurrency trading data from DexScreener.", + delegator_description=( + "Fetches token profiles, boosted tokens, and DEX trading pair data from DexScreener. " + "Use when users need to search for specific trading pairs, monitor token activity, " + "or get information about recently listed or trending tokens across different chains. " + "Can filter results by specific chains. DO NOT USE FOR TOP HOLDERS OR TOKEN HOLDER ANALYSIS." + ), + human_readable_name="DexScreener Analyst", + command="dexscreener", + upload_required=False, + ) + + # ************* + # SYSTEM MESSAGE + # ************* + + system_message = SystemMessage( + content="You are an agent that can fetch and analyze cryptocurrency token data " + "from DexScreener. You can get token profiles and information about " + "boosted tokens across different chains. When chain_id is not specified, " + "you'll get data for all chains. You can filter by specific chains like " + "'solana', 'ethereum', or 'bsc'." + ) + + # ************* + # TOOLS CONFIG + # ************* + + tools = [ + { + "name": DexScreenerToolType.GET_LATEST_TOKEN_PROFILES.value, + "description": "Get the latest token profiles from DexScreener", + "parameters": { + "type": "object", + "properties": { + "chain_id": { + "type": "string", + "description": "Optional chain ID to filter results (e.g., 'solana', 'ethereum')", + "required": False, + } + }, + }, + }, + { + "name": DexScreenerToolType.GET_LATEST_BOOSTED_TOKENS.value, + "description": "Get the latest boosted tokens from DexScreener", + "parameters": { + "type": "object", + "properties": { + "chain_id": { + "type": "string", + "description": "Optional chain ID to filter results (e.g., 'solana', 'ethereum')", + "required": False, + } + }, + }, + }, + { + "name": DexScreenerToolType.GET_TOP_BOOSTED_TOKENS.value, + "description": "Get the tokens with most active boosts", + "parameters": { + "type": "object", + "properties": { + "chain_id": { + "type": "string", + "description": "Optional chain ID to filter results (e.g., 'solana', 'ethereum')", + "required": False, + } + }, + }, + }, + { + "name": DexScreenerToolType.SEARCH_DEX_PAIRS.value, + "description": "Search for DEX trading pairs and their activity", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Search query (e.g., token symbol like 'ETH' or 'BTC')", + "required": True, + } + }, + }, + }, + ] + + # ************* + # API CONFIG + # ************* + + BASE_URL = "https://api.dexscreener.com" + RATE_LIMIT = 60 # requests per minute + + ENDPOINTS = { + DexScreenerToolType.GET_LATEST_TOKEN_PROFILES.value: "/token-profiles/latest/v1", + DexScreenerToolType.GET_LATEST_BOOSTED_TOKENS.value: "/token-boosts/latest/v1", + DexScreenerToolType.GET_TOP_BOOSTED_TOKENS.value: "/token-boosts/top/v1", + DexScreenerToolType.SEARCH_DEX_PAIRS.value: "/latest/dex/search", + } diff --git a/submodules/agents/src/services/agents/dexscreener/models.py b/submodules/agents/src/services/agents/dexscreener/models.py new file mode 100755 index 00000000..d6cc9839 --- /dev/null +++ b/submodules/agents/src/services/agents/dexscreener/models.py @@ -0,0 +1,157 @@ +from typing import List, Optional +from pydantic import BaseModel, Field + + +class TokenLink(BaseModel): + """Model representing a link associated with a token.""" + + type: Optional[str] = None # Made optional since some links only have label + label: Optional[str] = None # Made optional since some links only have type + url: str + + +class TokenProfile(BaseModel): + """Model representing a token's profile information.""" + + url: str + chainId: str + tokenAddress: str + icon: Optional[str] = None + header: Optional[str] = None + description: Optional[str] = None + links: Optional[List[TokenLink]] = None + + +class TokenProfileResponse(BaseModel): + """Model for token profile API responses with formatting capabilities.""" + + tokens: List[TokenProfile] + chain_id: Optional[str] = None + + @property + def formatted_response(self) -> str: + """Format token profile data into a readable markdown string.""" + if not self.tokens: + chain_msg = f" for chain {self.chain_id}" if self.chain_id else "" + return f"No tokens found{chain_msg}." + + tokens = self.tokens[:10] + formatted = f"# Top {len(tokens)} Tokens" + if self.chain_id: + formatted += f" on {self.chain_id}" + formatted += "\n\n" + + for token in tokens: + if token.icon: + formatted += f"![Token Icon]({token.icon})\n\n" + + formatted += f"### `{token.tokenAddress}`\n\n" + + if token.description: + formatted += f"{token.description}\n\n" + + if token.links: + formatted += "**Links**: " + link_parts = [f"[DexScreener]({token.url})"] + + for link in token.links: + link_parts.append(f"[{link.type or link.label}]({link.url})") + + formatted += " β€’ ".join(link_parts) + "\n\n" + + formatted += "\n---\n\n\n" + + return formatted + + +class BoostedToken(TokenProfile): + """Model representing a boosted token, extending TokenProfile with boost amounts.""" + + amount: float = Field(default=0.0) # Default value added + totalAmount: float = Field(default=0.0) # Default value added + + +class BoostedTokenResponse(BaseModel): + """Model for boosted token API responses with formatting capabilities.""" + + tokens: List[BoostedToken] + chain_id: Optional[str] = None + + @property + def formatted_response(self) -> str: + """Format boosted token data into a readable markdown string.""" + return TokenProfileResponse(tokens=self.tokens, chain_id=self.chain_id).formatted_response + + +class DexPair(BaseModel): + """Model representing a DEX trading pair with detailed market information.""" + + chainId: str + dexId: str + url: str + pairAddress: str + baseToken: TokenProfile + quoteToken: TokenProfile + priceNative: float + priceUsd: Optional[float] = None + txns: Optional[dict] = None + volume: Optional[dict] = None + priceChange: Optional[dict] = None + liquidity: Optional[dict] = None + fdv: Optional[float] = None + pairCreatedAt: Optional[int] = None + + +class DexPairSearchResponse(BaseModel): + """Model for DEX pair search responses with formatting capabilities.""" + + pairs: List[DexPair] + + @property + def formatted_response(self) -> str: + """Format DEX pair data into a readable markdown string.""" + if not self.pairs: + return "No DEX pairs found matching your search." + + pairs = self.pairs[:10] + formatted = f"# Found {len(pairs)} popular DEX Trading Pairs\n\n" + + for pair in pairs: + formatted += f"## {pair.baseToken.tokenAddress} / {pair.quoteToken.tokenAddress} on {pair.dexId.title()}\n" + formatted += f"Chain: {pair.chainId.upper()}\n\n" + + if pair.priceUsd: + formatted += f"Price: ${float(pair.priceUsd):.4f}\n" + + price_change = pair.priceChange.get("h24") if pair.priceChange else None + if price_change is not None: + change_symbol = "πŸ“ˆ" if float(price_change) > 0 else "πŸ“‰" + formatted += f"24h Change: {change_symbol} {price_change:.2f}%\n" + + if pair.volume and pair.volume.get("h24"): + formatted += f"24h Volume: ${float(pair.volume['h24']):,.2f}\n" + + if pair.liquidity and pair.liquidity.get("usd"): + formatted += f"Liquidity: ${float(pair.liquidity['usd']):,.2f}\n" + + if pair.txns and pair.txns.get("h24"): + txns = pair.txns["h24"] + buys = txns.get("buys", 0) + sells = txns.get("sells", 0) + formatted += f"24h Transactions: {buys + sells} (🟒 {buys} buys, πŸ”΄ {sells} sells)\n" + + formatted += "\n**Links**: " + link_parts = [f"[DexScreener]({pair.url})"] + + if pair.info: + for website in pair.info.get("websites", []): + link_parts.append(f"[{website.get('label', 'Website')}]({website.get('url')})") + + for social in pair.info.get("socials", []): + social_type = social.get("type", "").title() + link_parts.append(f"[{social_type}]({social.get('url')})") + + formatted += " β€’ ".join(link_parts) + "\n\n" + formatted += "\n---\n\n" + + return formatted diff --git a/submodules/agents/src/services/agents/dexscreener/tests/benchmarks/test_dexscreener_agent.py b/submodules/agents/src/services/agents/dexscreener/tests/benchmarks/test_dexscreener_agent.py new file mode 100755 index 00000000..76c66f70 --- /dev/null +++ b/submodules/agents/src/services/agents/dexscreener/tests/benchmarks/test_dexscreener_agent.py @@ -0,0 +1,135 @@ +import logging +import pytest +from unittest.mock import patch +from typing import Dict, Any + +from services.agents.dexscreener.agent import DexScreenerAgent +from models.service.chat_models import AgentResponse, ChatRequest +from services.agents.dexscreener.tool_types import DexScreenerToolType +from services.agents.dexscreener.models import ( + TokenProfileResponse, + BoostedTokenResponse, + DexPairSearchResponse, +) + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def dex_agent(llm): + config: Dict[str, Any] = {"name": "dexscreener", "description": "Agent for DexScreener API interactions"} + return DexScreenerAgent(config, llm) + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_search_dex_pairs_success(dex_agent, make_chat_request): + request = make_chat_request(content="Search for ETH/USDC pairs", agent_name="dexscreener") + + mock_response = DexPairSearchResponse( + pairs=[ + { + "baseToken": {"symbol": "ETH"}, + "quoteToken": {"symbol": "USDC"}, + "dexId": "uniswap", + "chainId": "ethereum", + "priceUsd": "1850.45", + "priceChange": {"h24": 2.5}, + "volume": {"h24": 1000000}, + "liquidity": {"usd": 5000000}, + "txns": {"h24": {"buys": 100, "sells": 50}}, + "url": "https://dexscreener.com/eth/pair", + } + ], + formatted_response="Found ETH/USDC pair on Uniswap", + ) + + with patch("services.agents.dexscreener.tools.search_dex_pairs") as mock_search: + mock_search.return_value = mock_response + + response = await dex_agent._execute_tool(DexScreenerToolType.SEARCH_DEX_PAIRS.value, {"query": "ETH/USDC"}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert response.action_type == DexScreenerToolType.SEARCH_DEX_PAIRS.value + assert response.metadata == mock_response.model_dump() + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_get_latest_token_profiles(dex_agent, make_chat_request): + mock_response = TokenProfileResponse( + tokens=[ + { + "tokenAddress": "0x123", + "description": "Test Token", + "icon": "https://icon.url", + "url": "https://dexscreener.com/token", + "links": [{"type": "website", "url": "https://test.com"}], + } + ], + formatted_response="Latest token profiles", + ) + + with patch("services.agents.dexscreener.tools.get_latest_token_profiles") as mock_profiles: + mock_profiles.return_value = mock_response + + response = await dex_agent._execute_tool( + DexScreenerToolType.GET_LATEST_TOKEN_PROFILES.value, {"chain_id": "ethereum"} + ) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert response.action_type == DexScreenerToolType.GET_LATEST_TOKEN_PROFILES.value + assert response.metadata == mock_response.model_dump() + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_get_boosted_tokens(dex_agent, make_chat_request): + mock_response = BoostedTokenResponse( + tokens=[ + { + "tokenAddress": "0x456", + "description": "Boosted Token", + "url": "https://dexscreener.com/boosted", + "links": [], + } + ], + formatted_response="Latest boosted tokens", + ) + + with patch("services.agents.dexscreener.tools.get_latest_boosted_tokens") as mock_boosted: + mock_boosted.return_value = mock_response + + response = await dex_agent._execute_tool( + DexScreenerToolType.GET_LATEST_BOOSTED_TOKENS.value, {"chain_id": "ethereum"} + ) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert response.action_type == DexScreenerToolType.GET_LATEST_BOOSTED_TOKENS.value + assert response.metadata == mock_response.model_dump() + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_error_handling(dex_agent, make_chat_request): + with patch("services.agents.dexscreener.tools.search_dex_pairs") as mock_search: + mock_search.side_effect = Exception("API Error") + + response = await dex_agent._execute_tool(DexScreenerToolType.SEARCH_DEX_PAIRS.value, {"query": "invalid"}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "API Error" in response.error_message + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_unknown_tool(dex_agent, make_chat_request): + response = await dex_agent._execute_tool("unknown_tool", {}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Unknown tool" in response.error_message diff --git a/submodules/agents/src/services/agents/dexscreener/tool_types.py b/submodules/agents/src/services/agents/dexscreener/tool_types.py new file mode 100644 index 00000000..8cec41b0 --- /dev/null +++ b/submodules/agents/src/services/agents/dexscreener/tool_types.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class DexScreenerToolType(Enum): + """Enum for different DexScreener API tool types""" + + SEARCH_DEX_PAIRS = "search_dex_pairs" + GET_LATEST_TOKEN_PROFILES = "get_latest_token_profiles" + GET_LATEST_BOOSTED_TOKENS = "get_latest_boosted_tokens" + GET_TOP_BOOSTED_TOKENS = "get_top_boosted_tokens" diff --git a/submodules/agents/src/services/agents/dexscreener/tools.py b/submodules/agents/src/services/agents/dexscreener/tools.py new file mode 100755 index 00000000..fe0e504a --- /dev/null +++ b/submodules/agents/src/services/agents/dexscreener/tools.py @@ -0,0 +1,87 @@ +import logging +from typing import Dict, Any, List, Optional +import aiohttp +from services.agents.dexscreener.models import ( + TokenProfile, + BoostedToken, + TokenProfileResponse, + BoostedTokenResponse, + DexPair, + DexPairSearchResponse, +) +from services.agents.dexscreener.config import Config +from services.agents.dexscreener.tool_types import DexScreenerToolType + +logger = logging.getLogger(__name__) + + +def filter_by_chain(tokens: List[Dict[str, Any]], chain_id: Optional[str] = None) -> List[Dict[str, Any]]: + """Filter tokens by chain ID if provided.""" + if not chain_id: + return tokens + return [token for token in tokens if token.get("chainId", "").lower() == chain_id.lower()] + + +async def _make_request(endpoint: str) -> Dict[str, Any]: + """Make an API request to DexScreener.""" + url = f"{Config.BASE_URL}{endpoint}" + try: + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + if response.status != 200: + raise Exception(f"API request failed with status {response.status}") + return await response.json() + except Exception as e: + logger.error(f"API request failed: {str(e)}", exc_info=True) + raise Exception(f"Failed to fetch data: {str(e)}") + + +async def get_latest_token_profiles(chain_id: Optional[str] = None) -> TokenProfileResponse: + """Get the latest token profiles, optionally filtered by chain.""" + try: + response = await _make_request(Config.ENDPOINTS[DexScreenerToolType.GET_LATEST_TOKEN_PROFILES.value]) + tokens_data: List[Dict[str, Any]] = response if isinstance(response, list) else [] + filtered_tokens = filter_by_chain(tokens_data, chain_id) + tokens = [TokenProfile(**token) for token in filtered_tokens] + return TokenProfileResponse(tokens=tokens, chain_id=chain_id) + except Exception as e: + raise Exception(f"Failed to get token profiles: {str(e)}") + + +async def get_latest_boosted_tokens(chain_id: Optional[str] = None) -> BoostedTokenResponse: + """Get the latest boosted tokens, optionally filtered by chain.""" + try: + response = await _make_request(Config.ENDPOINTS[DexScreenerToolType.GET_LATEST_BOOSTED_TOKENS.value]) + tokens_data: List[Dict[str, Any]] = response if isinstance(response, list) else [] + filtered_tokens = filter_by_chain(tokens_data, chain_id) + tokens = [BoostedToken(**token) for token in filtered_tokens] + return BoostedTokenResponse(tokens=tokens, chain_id=chain_id) + except Exception as e: + raise Exception(f"Failed to get boosted tokens: {str(e)}") + + +async def get_top_boosted_tokens(chain_id: Optional[str] = None) -> BoostedTokenResponse: + """Get tokens with most active boosts, optionally filtered by chain.""" + try: + response = await _make_request(Config.ENDPOINTS[DexScreenerToolType.GET_TOP_BOOSTED_TOKENS.value]) + tokens_data: List[Dict[str, Any]] = response if isinstance(response, list) else [] + filtered_tokens = filter_by_chain(tokens_data, chain_id) + + # Sort by total amount + sorted_tokens = sorted(filtered_tokens, key=lambda x: float(x.get("totalAmount", 0) or 0), reverse=True) + tokens = [BoostedToken(**token) for token in sorted_tokens] + return BoostedTokenResponse(tokens=tokens, chain_id=chain_id) + except Exception as e: + raise Exception(f"Failed to get top boosted tokens: {str(e)}") + + +async def search_dex_pairs(query: str) -> DexPairSearchResponse: + """Search for DEX pairs matching the query.""" + try: + endpoint = f"{Config.ENDPOINTS[DexScreenerToolType.SEARCH_DEX_PAIRS.value]}?q={query}" + response = await _make_request(endpoint) + pairs_data = response.get("pairs", []) + pairs = [DexPair(**pair) for pair in pairs_data] + return DexPairSearchResponse(pairs=pairs) + except Exception as e: + raise Exception(f"Failed to search DEX pairs: {str(e)}") diff --git a/submodules/moragents_dockers/agents/src/agents/mor_rewards/__init__.py b/submodules/agents/src/services/agents/elfa/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/mor_rewards/__init__.py rename to submodules/agents/src/services/agents/elfa/__init__.py diff --git a/submodules/agents/src/services/agents/elfa/agent.py b/submodules/agents/src/services/agents/elfa/agent.py new file mode 100755 index 00000000..e6a8997e --- /dev/null +++ b/submodules/agents/src/services/agents/elfa/agent.py @@ -0,0 +1,94 @@ +import logging +from typing import Dict, Any +from models.service.agent_core import AgentCore +from models.service.chat_models import ChatRequest, AgentResponse + +from .config import Config +from .tool_types import ElfaToolType +from .models import ( + ElfaMentionsResponse, + ElfaTopMentionsResponse, + ElfaTrendingTokensResponse, + ElfaAccountSmartStatsResponse, +) +from .tools import ( + get_top_mentions, + search_mentions, + get_trending_tokens, + get_account_smart_stats, +) + +logger = logging.getLogger(__name__) + + +class ElfaAgent(AgentCore): + """Agent for interacting with Elfa Social API.""" + + def __init__(self, config: Dict[str, Any], llm: Any): + super().__init__(config, llm) + self.tools_provided = Config.tools + self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) + + async def _process_request(self, request: ChatRequest) -> AgentResponse: + """Process the validated chat request for Elfa API interactions.""" + try: + messages = [Config.system_message, *request.messages_for_llm] + + result = self.tool_bound_llm.invoke(messages) + return await self._handle_llm_response(result) + + except Exception as e: + logger.error(f"Error processing request: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) + + async def _execute_tool(self, func_name: str, args: Dict[str, Any]) -> AgentResponse: + """Execute the appropriate Elfa API tool based on function name.""" + try: + if func_name == ElfaToolType.GET_TOP_MENTIONS.value: + top_mentions_response: ElfaTopMentionsResponse = await get_top_mentions( + ticker=args["ticker"], + time_window=args.get("timeWindow"), + include_account_details=args.get("includeAccountDetails"), + ) + return AgentResponse.success( + content=top_mentions_response.formatted_response, + metadata=top_mentions_response.model_dump(), + action_type=ElfaToolType.GET_TOP_MENTIONS.value, + ) + + if func_name == ElfaToolType.SEARCH_MENTIONS.value: + keywords = args.get("keywords", ["crypto"]) + if isinstance(keywords, str): + keywords = [keywords] + + search_mentions_response: ElfaMentionsResponse = await search_mentions(keywords=keywords) + return AgentResponse.success( + content=search_mentions_response.formatted_response, + metadata=search_mentions_response.model_dump(), + action_type=ElfaToolType.SEARCH_MENTIONS.value, + ) + + elif func_name == ElfaToolType.GET_TRENDING_TOKENS.value: + trending_tokens_response: ElfaTrendingTokensResponse = await get_trending_tokens( + time_window=args.get("timeWindow"), min_mentions=args.get("minMentions") + ) + return AgentResponse.success( + content=trending_tokens_response.formatted_response, + metadata=trending_tokens_response.model_dump(), + action_type=ElfaToolType.GET_TRENDING_TOKENS.value, + ) + + elif func_name == ElfaToolType.GET_ACCOUNT_SMART_STATS.value: + smart_stats_response: ElfaAccountSmartStatsResponse = await get_account_smart_stats(args["username"]) + return AgentResponse.success( + content=smart_stats_response.formatted_response, + metadata=smart_stats_response.model_dump(), + action_type=ElfaToolType.GET_ACCOUNT_SMART_STATS.value, + ) + + else: + return AgentResponse.error(error_message=f"Unknown tool: {func_name}") + + except Exception as e: + logger.error(f"Error executing tool {func_name}: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) diff --git a/submodules/agents/src/services/agents/elfa/config.py b/submodules/agents/src/services/agents/elfa/config.py new file mode 100755 index 00000000..71d1fbeb --- /dev/null +++ b/submodules/agents/src/services/agents/elfa/config.py @@ -0,0 +1,154 @@ +from models.service.agent_config import AgentConfig +from langchain.schema import SystemMessage +from .tool_types import ElfaToolType + + +class Config: + """Configuration for Elfa Social API.""" + + # ************* + # AGENT CONFIG + # ------------ + # This must be defined in every agent config file + # It is required to load the agent + # ************* + + agent_config = AgentConfig( + path="services.agents.elfa.agent", + class_name="ElfaAgent", + description="Fetches and analyzes social media data related to cryptocurrency from Elfa.", + delegator_description="Monitors and analyzes social sentiment and engagement metrics across crypto communities, " + "including trending topics, influential accounts, sentiment shifts, and community growth patterns. " + "Use when users want insights about social perception of crypto projects.", + human_readable_name="Elfa Social Analyst", + command="elfa", + upload_required=False, + ) + + # ************* + # SYSTEM MESSAGE + # ************* + + system_message = SystemMessage( + content=( + "You are an agent that can fetch and analyze social media data " + "from Elfa. You can get trending tokens, mentions, and smart account " + "statistics. The data is focused on cryptocurrency and blockchain " + "related social media activity." + ) + ) + + # ************* + + tools = [ + { + "name": ElfaToolType.GET_TOP_MENTIONS.value, + "description": "Get the most viewed and engaged-with social media posts mentioning a specific cryptocurrency ticker symbol, sorted by total view count", + "parameters": { + "type": "object", + "properties": { + "ticker": { + "type": "string", + "description": "The ticker symbol to get mentions for", + "required": True, + }, + "timeWindow": { + "type": "string", + "description": "Time window for mentions (e.g., '1h', '24h', '7d')", + "required": False, + }, + "includeAccountDetails": { + "type": "boolean", + "description": "Include account details in response", + "required": False, + }, + }, + }, + }, + { + "name": ElfaToolType.SEARCH_MENTIONS.value, + "description": "Search through all social media posts using custom keywords and date filters to find relevant cryptocurrency discussions and trends", + "parameters": { + "type": "object", + "properties": { + "keywords": { + "type": "array", + "items": {"type": "string"}, + "description": "Keywords to search for (max 5). Defaults to ['crypto']", + "required": False, + }, + "from": { + "type": "number", + "description": "Start timestamp (unix). Defaults to 7 days ago", + "required": False, + }, + "to": { + "type": "number", + "description": "End timestamp (unix). Defaults to now", + "required": False, + }, + "limit": { + "type": "number", + "description": "Number of results to return (default: 20, max: 30)", + "required": False, + }, + "cursor": { + "type": "string", + "description": "Cursor for pagination", + "required": False, + }, + }, + }, + }, + { + "name": ElfaToolType.GET_TRENDING_TOKENS.value, + "description": "Get trending tokens based on social media mentions", + "parameters": { + "type": "object", + "properties": { + "timeWindow": { + "type": "string", + "description": "Time window for trending analysis (default: '24h')", + "required": False, + }, + "minMentions": { + "type": "number", + "description": "Minimum number of mentions required (default: 5)", + "required": False, + }, + }, + }, + }, + { + "name": ElfaToolType.GET_ACCOUNT_SMART_STATS.value, + "description": "Get smart stats and social metrics for a given username", + "parameters": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "Username to get stats for", + "required": True, + }, + }, + }, + }, + ] + + # ************* + # API CONFIG + # ************* + + BASE_URL = "https://api.elfa.ai" + API_VERSION = "v1" + RATE_LIMIT = 60 # requests per minute + + # Headers configuration + API_KEY_HEADER = "x-elfa-api-key" # Updated header name for API key + + ENDPOINTS = { + ElfaToolType.GET_TOP_MENTIONS.value: f"/{API_VERSION}/top-mentions", + ElfaToolType.SEARCH_MENTIONS.value: f"/{API_VERSION}/mentions/search", + ElfaToolType.GET_TRENDING_TOKENS.value: f"/{API_VERSION}/trending-tokens", + ElfaToolType.GET_ACCOUNT_SMART_STATS.value: f"/{API_VERSION}/account/smart-stats", + } diff --git a/submodules/agents/src/services/agents/elfa/models.py b/submodules/agents/src/services/agents/elfa/models.py new file mode 100755 index 00000000..acb27218 --- /dev/null +++ b/submodules/agents/src/services/agents/elfa/models.py @@ -0,0 +1,262 @@ +from typing import List, Optional +from pydantic import BaseModel + + +class ElfaAccountData(BaseModel): + """Model representing account profile data.""" + + profileBannerUrl: Optional[str] = None + profileImageUrl: Optional[str] = None + description: Optional[str] = None + userSince: Optional[str] = None + location: Optional[str] = None + name: Optional[str] = None + + +class ElfaAccount(BaseModel): + """Model representing a social media account.""" + + id: Optional[int] = None + username: Optional[str] = None + data: Optional[ElfaAccountData] = None + followerCount: Optional[int] = None + followingCount: Optional[int] = None + isVerified: Optional[bool] = None + + +class MediaUrl(BaseModel): + """Model representing a media URL.""" + + url: Optional[str] = None + type: Optional[str] = None + + +class MentionData(BaseModel): + """Model representing mention data.""" + + mediaUrls: Optional[List[MediaUrl]] = [] + + +class ElfaMention(BaseModel): + """Model representing a social media mention.""" + + id: Optional[int] = None + type: Optional[str] = None + content: Optional[str] = None + originalUrl: Optional[str] = None + data: Optional[MentionData] = None + likeCount: Optional[int] = None + quoteCount: Optional[int] = None + replyCount: Optional[int] = None + repostCount: Optional[int] = None + viewCount: Optional[int] = None + mentionedAt: Optional[str] = None + bookmarkCount: Optional[int] = None + account: Optional[ElfaAccount] = None + + +class ElfaMentionsResponse(BaseModel): + """Model for mentions API responses.""" + + success: Optional[bool] = None + data: Optional[List[ElfaMention]] = None + + @property + def formatted_response(self) -> str: + """Format mentions data into a readable markdown string.""" + if not self.success: + return "Failed to get mentions." + + if not self.data: + return "No mentions found." + + mentions = self.data[:5] # Limit to 5 mentions + formatted = f"# Latest {len(mentions)} Social Media Mentions\n\n" + + for mention in mentions: + if mention.account and mention.account.username: + formatted += f"## @{mention.account.username}\n\n" + else: + formatted += "## Unknown User\n\n" + + if mention.content: + formatted += f"{mention.content}\n\n" + + formatted += "**Metrics**:\n" + formatted += f"- πŸ‘οΈ Views: {mention.viewCount or 0:,}\n" + formatted += f"- πŸ”„ Reposts: {mention.repostCount or 0:,}\n" + formatted += f"- πŸ’¬ Replies: {mention.replyCount or 0:,}\n" + formatted += f"- ❀️ Likes: {mention.likeCount or 0:,}\n\n" + + if mention.mentionedAt: + formatted += f"Posted at: {mention.mentionedAt}\n\n" + + if mention.originalUrl: + formatted += f"[View Original]({mention.originalUrl})\n\n" + + formatted += "---\n\n" + + return formatted + + +class ElfaMentionMetrics(BaseModel): + """Model representing engagement metrics for a mention.""" + + view_count: Optional[int] = None + repost_count: Optional[int] = None + reply_count: Optional[int] = None + like_count: Optional[int] = None + + +class ElfaTopMention(BaseModel): + """Model representing a top mention.""" + + id: Optional[int] = None + content: Optional[str] = None + mentioned_at: Optional[str] = None + metrics: Optional[ElfaMentionMetrics] = None + + +class ElfaTopMentionsData(BaseModel): + """Model representing paginated top mentions data.""" + + pageSize: Optional[int] = None + page: Optional[int] = None + total: Optional[int] = None + data: Optional[List[ElfaTopMention]] = None + + +class ElfaTopMentionsResponse(BaseModel): + """Model for top mentions API responses.""" + + success: Optional[bool] = None + data: Optional[ElfaTopMentionsData] = None + + @property + def formatted_response(self) -> str: + """Format top mentions data into a readable markdown string.""" + if not self.success: + return "Failed to get top mentions." + + if not self.data or not self.data.data: + return "No top mentions found for this ticker." + + mentions = self.data.data[:5] # Limit to 5 top mentions + formatted = f"# Top {len(mentions)} Mentions (Total: {self.data.total or 0})\n\n" + + for mention in mentions: + if mention.id: + formatted += f"### Post ID: {mention.id}\n\n" + else: + formatted += "### Unknown Post\n\n" + + if mention.content: + formatted += f"{mention.content}\n\n" + + formatted += "**Engagement**:\n" + if mention.metrics: + formatted += f"- πŸ‘οΈ Views: {mention.metrics.view_count or 0:,}\n" + formatted += f"- πŸ”„ Reposts: {mention.metrics.repost_count or 0:,}\n" + formatted += f"- πŸ’¬ Replies: {mention.metrics.reply_count or 0:,}\n" + formatted += f"- ❀️ Likes: {mention.metrics.like_count or 0:,}\n\n" + else: + formatted += "No metrics available\n\n" + + if mention.mentioned_at: + formatted += f"Posted at: {mention.mentioned_at}\n\n" + + formatted += "---\n\n" + + return formatted + + +class ElfaTrendingToken(BaseModel): + """Model representing a trending token.""" + + change_percent: Optional[float] = None + previous_count: Optional[int] = None + current_count: Optional[int] = None + token: Optional[str] = None + + +class ElfaTrendingTokensData(BaseModel): + """Model representing paginated trending tokens data.""" + + pageSize: Optional[int] = None + page: Optional[int] = None + total: Optional[int] = None + data: Optional[List[ElfaTrendingToken]] = None + + +class ElfaTrendingTokensResponse(BaseModel): + """Model for trending tokens API responses.""" + + success: Optional[bool] = None + data: Optional[ElfaTrendingTokensData] = None + + @property + def formatted_response(self) -> str: + """Format trending tokens data into a readable markdown string.""" + if not self.success: + return "Failed to get trending tokens." + + if not self.data or not self.data.data: + return "No trending tokens found." + + tokens = self.data.data[:5] # Limit to 5 trending tokens + formatted = f"# Top {len(tokens)} Trending Tokens\n\n" + + for token in tokens: + if token.token: + formatted += f"## ${token.token}\n\n" + else: + formatted += "## Unknown Token\n\n" + + formatted += f"Current Mentions: {token.current_count or 0:,}\n" + formatted += f"Previous Period: {token.previous_count or 0:,}\n" + + if token.change_percent is not None: + emoji = "πŸ“ˆ" if token.change_percent > 0 else "πŸ“‰" if token.change_percent < 0 else "➑️" + formatted += f"Change: {emoji} {token.change_percent:+.2f}%\n\n" + else: + formatted += "Change: Not available\n\n" + + formatted += "---\n\n" + + return formatted + + +class ElfaAccountSmartStats(BaseModel): + """Model representing account smart statistics.""" + + followerEngagementRatio: Optional[float] = None + averageEngagement: Optional[float] = None + smartFollowingCount: Optional[int] = None + + +class ElfaAccountSmartStatsResponse(BaseModel): + """Model for account smart stats API responses.""" + + success: Optional[bool] = None + data: Optional[ElfaAccountSmartStats] = None + + @property + def formatted_response(self) -> str: + """Format account smart stats into a readable markdown string.""" + if not self.success: + return "Failed to get account statistics." + + if not self.data: + return "No account statistics available." + + formatted = "# Account Smart Statistics\n\n" + formatted += "## Engagement Metrics\n\n" + formatted += f"Smart Following Count: {self.data.smartFollowingCount or 0:,}\n" + formatted += f"Average Engagement: {self.data.averageEngagement or 0:.2f}\n" + + if self.data.followerEngagementRatio is not None: + formatted += f"Follower Engagement Ratio: {self.data.followerEngagementRatio:.2%}\n" + else: + formatted += "Follower Engagement Ratio: Not available\n" + + return formatted diff --git a/submodules/moragents_dockers/agents/src/agents/news_agent/__init__.py b/submodules/agents/src/services/agents/elfa/tests/benchmarks/test_elfa_agent.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/news_agent/__init__.py rename to submodules/agents/src/services/agents/elfa/tests/benchmarks/test_elfa_agent.py diff --git a/submodules/agents/src/services/agents/elfa/tool_types.py b/submodules/agents/src/services/agents/elfa/tool_types.py new file mode 100644 index 00000000..54962f7e --- /dev/null +++ b/submodules/agents/src/services/agents/elfa/tool_types.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class ElfaToolType(Enum): + """Enum for different Elfa API tool types""" + + GET_TOP_MENTIONS = "get_top_mentions" + SEARCH_MENTIONS = "search_mentions" + GET_TRENDING_TOKENS = "get_trending_tokens" + GET_ACCOUNT_SMART_STATS = "get_account_smart_stats" diff --git a/submodules/agents/src/services/agents/elfa/tools.py b/submodules/agents/src/services/agents/elfa/tools.py new file mode 100755 index 00000000..9e67fb61 --- /dev/null +++ b/submodules/agents/src/services/agents/elfa/tools.py @@ -0,0 +1,121 @@ +import logging +import os +from typing import Optional, List, Any + +from datetime import datetime, timedelta +import aiohttp +from urllib.parse import urlencode + +from .tool_types import ElfaToolType +from .config import Config +from .models import ( + ElfaMentionsResponse, + ElfaTopMentionsResponse, + ElfaTrendingTokensResponse, + ElfaAccountSmartStatsResponse, +) +from services.secrets import get_secret + +logger = logging.getLogger(__name__) + + +async def _make_request(endpoint: str, params: Optional[dict] = None) -> Any: + """Make an API request to Elfa.""" + url = f"{Config.BASE_URL}{endpoint}" + if params: + url = f"{url}?{urlencode(params)}" + + # Get API key from environment + api_key = get_secret("ElfaApiKey") + if not api_key: + raise Exception("ELFA_API_KEY environment variable is not set") + + headers = {"x-elfa-api-key": api_key} + + try: + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=headers) as response: + if response.status != 200: + error_data = await response.json() + raise Exception(f"API request failed with status {response.status}: {error_data}") + return await response.json() + except Exception as e: + logger.error(f"API request failed: {str(e)}", exc_info=True) + raise Exception(f"Failed to fetch data: {str(e)}") + + +async def get_top_mentions( + ticker: str, time_window: Optional[str] = None, include_account_details: Optional[bool] = None +) -> ElfaTopMentionsResponse: + """Get top mentions for a specific ticker.""" + try: + params = {"ticker": ticker, "timeWindow": time_window or "1d", "page": 1, "pageSize": 10} # Sensible defaults + + # Only add if explicitly set to True + if include_account_details: + params["includeAccountDetails"] = "true" + + response = await _make_request(Config.ENDPOINTS[ElfaToolType.GET_TOP_MENTIONS.value], params) + return ElfaTopMentionsResponse.model_validate(response) + except Exception as e: + logger.error(f"Failed to get top mentions: {str(e)}", exc_info=True) + raise Exception(f"Failed to get top mentions: {str(e)}") + + +async def search_mentions( + keywords: List[str] = ["crypto"], + from_timestamp: Optional[int] = None, + to_timestamp: Optional[int] = None, + limit: Optional[int] = None, + cursor: Optional[str] = None, +) -> ElfaMentionsResponse: + """Search for mentions by keywords within a time range. Defaults to last 7 days.""" + try: + # Default to last 7 days if not specified + now = datetime.now() + default_to = int(now.timestamp()) + default_from = int((now - timedelta(days=7)).timestamp()) + + params = { + "keywords": ",".join(keywords[:5]), # API limit of 5 keywords + "from": from_timestamp or default_from, + "to": to_timestamp or default_to, + "limit": min(limit or 20, 30), # Default 20, max 30 + } + if cursor: + params["cursor"] = cursor + + response = await _make_request(Config.ENDPOINTS[ElfaToolType.SEARCH_MENTIONS.value], params) + return ElfaMentionsResponse.model_validate(response) + except Exception as e: + logger.error(f"Failed to search mentions: {str(e)}", exc_info=True) + raise Exception(f"Failed to search mentions: {str(e)}") + + +async def get_trending_tokens( + time_window: Optional[str] = None, min_mentions: Optional[int] = None +) -> ElfaTrendingTokensResponse: + """Get trending tokens based on social media mentions.""" + try: + params = { + "timeWindow": time_window or "24h", + "page": 1, # Sensible defaults + "pageSize": 50, + "minMentions": min_mentions or 5, + } + response = await _make_request(Config.ENDPOINTS[ElfaToolType.GET_TRENDING_TOKENS.value], params) + return ElfaTrendingTokensResponse.model_validate(response) + except Exception as e: + logger.error(f"Failed to get trending tokens: {str(e)}", exc_info=True) + raise Exception(f"Failed to get trending tokens: {str(e)}") + + +async def get_account_smart_stats(username: str) -> ElfaAccountSmartStatsResponse: + """Get smart stats and social metrics for a given username.""" + try: + params = {"username": username} + response = await _make_request(Config.ENDPOINTS[ElfaToolType.GET_ACCOUNT_SMART_STATS.value], params) + return ElfaAccountSmartStatsResponse.model_validate(response) + except Exception as e: + logger.error(f"Failed to get account stats: {str(e)}", exc_info=True) + raise Exception(f"Failed to get account stats: {str(e)}") diff --git a/submodules/moragents_dockers/agents/src/agents/rag/__init__.py b/submodules/agents/src/services/agents/imagen/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/rag/__init__.py rename to submodules/agents/src/services/agents/imagen/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/imagen/agent.py b/submodules/agents/src/services/agents/imagen/agent.py old mode 100644 new mode 100755 similarity index 90% rename from submodules/moragents_dockers/agents/src/agents/imagen/agent.py rename to submodules/agents/src/services/agents/imagen/agent.py index 5182e675..842496fe --- a/submodules/moragents_dockers/agents/src/agents/imagen/agent.py +++ b/submodules/agents/src/services/agents/imagen/agent.py @@ -12,9 +12,9 @@ from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait -from src.models.core import ChatRequest, AgentResponse -from src.agents.agent_core.agent import AgentCore -from langchain.schema import HumanMessage, SystemMessage +from models.service.chat_models import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore +from .config import Config logger = logging.getLogger(__name__) @@ -22,24 +22,15 @@ class ImagenAgent(AgentCore): """Agent for handling image generation requests.""" - def __init__(self, config: Dict[str, Any], llm: Any, embeddings: Any): - super().__init__(config, llm, embeddings) + def __init__(self, config: Dict[str, Any], llm: Any): + super().__init__(config, llm) self.tools_provided: List[str] = [] # No tools needed for image generation self.tool_bound_llm = self.llm async def _process_request(self, request: ChatRequest) -> AgentResponse: """Process the validated chat request for image generation.""" try: - messages = [ - SystemMessage( - content=( - "You are an image generation assistant. " - "Help users create images by understanding their prompts " - "and generating appropriate images." - ) - ), - HumanMessage(content=request.prompt.content), - ] + messages = [Config.system_message, *request.messages_for_llm] # For image generation, we'll directly use the prompt content result = self.generate_image(request.prompt.content) diff --git a/submodules/agents/src/services/agents/imagen/config.py b/submodules/agents/src/services/agents/imagen/config.py new file mode 100755 index 00000000..d003c80d --- /dev/null +++ b/submodules/agents/src/services/agents/imagen/config.py @@ -0,0 +1,50 @@ +from models.service.agent_config import AgentConfig +from langchain.schema import SystemMessage + + +class Config: + """Configuration for Image Generation Agent.""" + + # ************* + # AGENT CONFIG + # ************* + + agent_config = AgentConfig( + path="services.agents.imagen.agent", + class_name="ImagenAgent", + description="Use your imagination! Create or generate images based on your prompt", + delegator_description="Specializes EXCLUSIVELY in generating visual content like charts, infographics, token logos, " + "or explanatory diagrams. ONLY use when the user explicitly requests image creation or visual " + "representation of data.", + human_readable_name="Image Generator", + command="imagen", + upload_required=False, + ) + + # ************* + # SYSTEM MESSAGE + # ************* + + system_message = SystemMessage( + content=( + "You are an image generation assistant. " + "Help users create images by understanding their prompts " + "and generating appropriate images." + ) + ) + + # ************* + # TOOLS CONFIG + # ************* + + tools = [] # No tools needed for image generation + + # ************* + # API CONFIG + # ************* + + CHROME_BINARY = "/usr/bin/chromium" + CHROME_DRIVER = "/usr/bin/chromedriver" + FLUX_AI_URL = "https://fluxai.pro/fast-flux" + PAGE_LOAD_TIMEOUT = 30 # seconds + ELEMENT_WAIT_TIMEOUT = 30 # seconds diff --git a/submodules/moragents_dockers/agents/src/agents/realtime_search/__init__.py b/submodules/agents/src/services/agents/imagen/tests/benchmarks/test_imagen_agent.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/realtime_search/__init__.py rename to submodules/agents/src/services/agents/imagen/tests/benchmarks/test_imagen_agent.py diff --git a/submodules/moragents_dockers/agents/src/agents/rugcheck/__init__.py b/submodules/agents/src/services/agents/mor_claims/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/rugcheck/__init__.py rename to submodules/agents/src/services/agents/mor_claims/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py b/submodules/agents/src/services/agents/mor_claims/agent.py old mode 100644 new mode 100755 similarity index 70% rename from submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py rename to submodules/agents/src/services/agents/mor_claims/agent.py index f4d74150..22e7ab84 --- a/submodules/moragents_dockers/agents/src/agents/mor_claims/agent.py +++ b/submodules/agents/src/services/agents/mor_claims/agent.py @@ -1,11 +1,11 @@ import logging from typing import Dict, Any -from src.agents.mor_claims import tools -from src.models.core import ChatRequest, AgentResponse -from src.agents.agent_core.agent import AgentCore +from services.agents.mor_claims import tools +from models.service.chat_models import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore from langchain.schema import HumanMessage, SystemMessage -from src.stores import agent_manager_instance +from stores import agent_manager_instance logger = logging.getLogger(__name__) @@ -13,23 +13,19 @@ class MorClaimsAgent(AgentCore): """Agent for handling MOR token claims and rewards.""" - def __init__(self, config: Dict[str, Any], llm: Any, embeddings: Any): - super().__init__(config, llm, embeddings) + def __init__(self, config: Dict[str, Any], llm: Any): + super().__init__(config, llm) self.tools_provided = tools.get_tools() self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) - self.conversation_state = {} async def _process_request(self, request: ChatRequest) -> AgentResponse: """Process the validated chat request for MOR claims.""" try: wallet_address = request.wallet_address + chat_history = request.chat_history - if wallet_address not in self.conversation_state: - self.conversation_state[wallet_address] = {"state": "initial"} - - state = self.conversation_state[wallet_address]["state"] - - if state == "initial": + # Check if this is initial interaction by looking at chat history + if not chat_history: agent_manager_instance.set_active_agent("mor claims") rewards = { @@ -39,24 +35,25 @@ async def _process_request(self, request: ChatRequest) -> AgentResponse: available_rewards = {pool: amount for pool, amount in rewards.items() if amount > 0} if available_rewards: - selected_pool = max(available_rewards, key=available_rewards.get) - self.conversation_state[wallet_address]["available_rewards"] = { - selected_pool: available_rewards[selected_pool] - } - self.conversation_state[wallet_address]["receiver_address"] = wallet_address - self.conversation_state[wallet_address]["state"] = "awaiting_confirmation" + selected_pool = max(available_rewards.keys()) return AgentResponse.success( - content=f"You have {available_rewards[selected_pool]} MOR rewards available in pool {selected_pool}. Would you like to proceed with claiming these rewards?" + content=f"You have {available_rewards[selected_pool]} MOR rewards available in pool {selected_pool}. Would you like to proceed with claiming these rewards?", + metadata={ + "available_rewards": {selected_pool: available_rewards[selected_pool]}, + "receiver_address": wallet_address, + }, ) else: return AgentResponse.error( error_message=f"No rewards found for your wallet address {wallet_address} in either pool. Claim cannot be processed." ) - elif state == "awaiting_confirmation": + # Check last message for confirmation + last_message = chat_history[-1] + if last_message.role == "assistant" and "Would you like to proceed" in last_message.content: user_input = request.prompt.content.lower() if any(word in user_input for word in ["yes", "proceed", "confirm", "claim"]): - return await self._prepare_transactions(wallet_address) + return await self._prepare_transactions(wallet_address, last_message.metadata) else: return AgentResponse.success( content="Please confirm if you want to proceed with the claim by saying 'yes', 'proceed', 'confirm', or 'claim'." @@ -79,11 +76,11 @@ async def _process_request(self, request: ChatRequest) -> AgentResponse: logger.error(f"Error processing request: {str(e)}", exc_info=True) return AgentResponse.error(error_message=str(e)) - async def _prepare_transactions(self, wallet_address: str) -> AgentResponse: + async def _prepare_transactions(self, wallet_address: str, metadata: Dict[str, Any]) -> AgentResponse: """Prepare claim transactions for the given wallet.""" try: - available_rewards = self.conversation_state[wallet_address]["available_rewards"] - receiver_address = self.conversation_state[wallet_address]["receiver_address"] + available_rewards = metadata["available_rewards"] + receiver_address = metadata["receiver_address"] transactions = [] for pool_id in available_rewards.keys(): @@ -95,10 +92,10 @@ async def _prepare_transactions(self, wallet_address: str) -> AgentResponse: error_message=f"Error preparing transaction for pool {pool_id}: {str(e)}" ) - self.conversation_state[wallet_address]["transactions"] = transactions - return AgentResponse.action_required( - content={"transactions": transactions, "claim_tx_cb": "/claim"}, action_type="claim" + content="Transactions prepared successfully", + action_type="claim", + metadata={"transactions": transactions, "claim_tx_cb": "/claim"}, ) except Exception as e: diff --git a/submodules/agents/src/services/agents/mor_claims/config.py b/submodules/agents/src/services/agents/mor_claims/config.py new file mode 100755 index 00000000..052e4d70 --- /dev/null +++ b/submodules/agents/src/services/agents/mor_claims/config.py @@ -0,0 +1,85 @@ +from models.service.agent_config import AgentConfig + + +class Config: + """Configuration for MOR Claims Agent.""" + + # ************* + # AGENT CONFIG + # ************* + + agent_config = AgentConfig( + path="services.agents.mor_claims.agent", + class_name="MorClaimsAgent", + description="Handles MOR token claims and rewards distribution", + delegator_description="Handles the technical process of claiming MOR tokens, including wallet connection " + "troubleshooting, transaction confirmation, gas optimization, and claim verification. " + "Use when users need assistance with the actual process of claiming rewards. " + "DO NOT use for reward calculations or projections.", + human_readable_name="MOR Claims Manager", + command="morclaims", + upload_required=False, + is_enabled=False, + ) + + # ************* + # TOOLS CONFIG + # ************* + + tools = [ + { + "name": "claim_rewards", + "description": "Claim MOR rewards for a pool", + "parameters": { + "type": "object", + "properties": { + "pool_id": {"type": "string", "description": "ID of the pool to claim from"}, + "receiver": {"type": "string", "description": "Address to receive the rewards"}, + }, + "required": ["pool_id", "receiver"], + }, + }, + { + "name": "get_claimable_amount", + "description": "Get claimable MOR reward amount for a user", + "parameters": { + "type": "object", + "properties": { + "pool_id": {"type": "string", "description": "Pool ID to check rewards for"}, + "user": {"type": "string", "description": "User address to check rewards for"}, + }, + "required": ["pool_id", "user"], + }, + }, + ] + + # ************* + # CONTRACT CONFIG + # ************* + + WEB3RPCURL = {"1": "https://eth.llamarpc.com/"} + MINT_FEE = 0.001 # in ETH + + DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" + DISTRIBUTION_ABI = [ + { + "inputs": [ + {"internalType": "uint256", "name": "poolId_", "type": "uint256"}, + {"internalType": "address", "name": "receiver_", "type": "address"}, + ], + "name": "claim", + "outputs": [], + "stateMutability": "payable", + "type": "function", + }, + { + "inputs": [ + {"internalType": "uint256", "name": "poolId_", "type": "uint256"}, + {"internalType": "address", "name": "user_", "type": "address"}, + ], + "name": "getCurrentUserReward", + "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], + "stateMutability": "view", + "type": "function", + }, + ] diff --git a/submodules/moragents_dockers/agents/src/agents/mor_claims/routes.py b/submodules/agents/src/services/agents/mor_claims/routes.py old mode 100644 new mode 100755 similarity index 75% rename from submodules/moragents_dockers/agents/src/agents/mor_claims/routes.py rename to submodules/agents/src/services/agents/mor_claims/routes.py index 7f6b710b..cef192da --- a/submodules/moragents_dockers/agents/src/agents/mor_claims/routes.py +++ b/submodules/agents/src/services/agents/mor_claims/routes.py @@ -1,7 +1,7 @@ import logging from fastapi import APIRouter, Request from fastapi.responses import JSONResponse -from src.stores import chat_manager_instance, agent_manager_instance +from stores import agent_manager_instance logger = logging.getLogger(__name__) @@ -9,7 +9,7 @@ @router.post("/claim") -async def claim(request: Request): +async def claim(request: Request) -> JSONResponse: """Process a claim request""" logger.info("Received claim request") try: @@ -21,8 +21,10 @@ async def claim(request: Request): ) response = await claim_agent.claim(request) - chat_manager_instance.add_message(response) - return response + return JSONResponse( + status_code=200, + content={"status": "success", "message": "Claim processed successfully", "response": response}, + ) except Exception as e: logger.error(f"Failed to process claim: {str(e)}") return JSONResponse( diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/__init__.py b/submodules/agents/src/services/agents/mor_claims/tests/benchmarks/test_agent.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/token_swap/__init__.py rename to submodules/agents/src/services/agents/mor_claims/tests/benchmarks/test_agent.py diff --git a/submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py b/submodules/agents/src/services/agents/mor_claims/tools.py old mode 100644 new mode 100755 similarity index 89% rename from submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py rename to submodules/agents/src/services/agents/mor_claims/tools.py index f9211403..1f7c98f6 --- a/submodules/moragents_dockers/agents/src/agents/mor_claims/tools.py +++ b/submodules/agents/src/services/agents/mor_claims/tools.py @@ -1,4 +1,4 @@ -from src.agents.mor_claims.config import Config +from services.agents.mor_claims.config import Config from web3 import Web3 @@ -29,13 +29,11 @@ def prepare_claim_transaction(pool_id, wallet_address): address=web3.to_checksum_address(Config.DISTRIBUTION_PROXY_ADDRESS), abi=Config.DISTRIBUTION_ABI, ) - tx_data = contract.encode_abi( - fn_name="claim", args=[pool_id, web3.to_checksum_address(wallet_address)] - ) + tx_data = contract.encode_abi(fn_name="claim", args=[pool_id, web3.to_checksum_address(wallet_address)]) mint_fee = web3.to_wei(Config.MINT_FEE, "ether") - estimated_gas = contract.functions.claim( - pool_id, web3.to_checksum_address(wallet_address) - ).estimate_gas({"from": web3.to_checksum_address(wallet_address), "value": mint_fee}) + estimated_gas = contract.functions.claim(pool_id, web3.to_checksum_address(wallet_address)).estimate_gas( + {"from": web3.to_checksum_address(wallet_address), "value": mint_fee} + ) return { "to": Config.DISTRIBUTION_PROXY_ADDRESS, "data": tx_data, diff --git a/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/__init__.py b/submodules/agents/src/services/agents/mor_rewards/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/tweet_sizzler/__init__.py rename to submodules/agents/src/services/agents/mor_rewards/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py b/submodules/agents/src/services/agents/mor_rewards/agent.py old mode 100644 new mode 100755 similarity index 86% rename from submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py rename to submodules/agents/src/services/agents/mor_rewards/agent.py index 56a26486..69c526e2 --- a/submodules/moragents_dockers/agents/src/agents/mor_rewards/agent.py +++ b/submodules/agents/src/services/agents/mor_rewards/agent.py @@ -1,15 +1,15 @@ import logging -from src.agents.agent_core.agent import AgentCore -from src.agents.mor_rewards import tools -from src.models.core import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore +from services.agents.mor_rewards import tools +from models.service.chat_models import ChatRequest, AgentResponse logger = logging.getLogger(__name__) class MorRewardsAgent(AgentCore): - def __init__(self, config, llm, embeddings): - super().__init__(config, llm, embeddings) + def __init__(self, config, llm): + super().__init__(config, llm) self.tools_provided = tools.get_tools() async def _process_request(self, request: ChatRequest) -> AgentResponse: diff --git a/submodules/agents/src/services/agents/mor_rewards/config.py b/submodules/agents/src/services/agents/mor_rewards/config.py new file mode 100755 index 00000000..3ab029fe --- /dev/null +++ b/submodules/agents/src/services/agents/mor_rewards/config.py @@ -0,0 +1,63 @@ +from models.service.agent_config import AgentConfig + + +class Config: + """Configuration for MOR Rewards Agent.""" + + # ************* + # AGENT CONFIG + # ************* + + agent_config = AgentConfig( + path="services.agents.mor_rewards.agent", + class_name="MorRewardsAgent", + description="Handles MOR token rewards distribution and claiming", + delegator_description="Specializes in calculating, forecasting, and explaining MOR token reward mechanisms, " + "distribution schedules, and yield optimization strategies. Use when users want to understand " + "potential rewards, APY calculations, or reward-related projections. DO NOT use for actual " + "token claiming processes.", + human_readable_name="MOR Rewards Manager", + command="morrewards", + upload_required=False, + ) + + # ************* + # TOOLS CONFIG + # ************* + + tools = [ + { + "name": "get_rewards", + "description": "Get claimable MOR rewards for a user", + "parameters": { + "type": "object", + "properties": { + "pool_id": {"type": "string", "description": "Pool ID to check rewards for"}, + "user": {"type": "string", "description": "User address to check rewards for"}, + }, + "required": ["pool_id", "user"], + }, + } + ] + + # ************* + # CONTRACT CONFIG + # ************* + + WEB3RPCURL = { + "1": "https://eth.llamarpc.com/", + } + + DISTRIBUTION_PROXY_ADDRESS = "0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790" + DISTRIBUTION_ABI = [ + { + "inputs": [ + {"internalType": "uint256", "name": "poolId_", "type": "uint256"}, + {"internalType": "address", "name": "user_", "type": "address"}, + ], + "name": "getCurrentUserReward", + "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], + "stateMutability": "view", + "type": "function", + } + ] diff --git a/submodules/moragents_dockers/agents/src/models/__init__.py b/submodules/agents/src/services/agents/mor_rewards/tests/benchmarks/test_agent.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/models/__init__.py rename to submodules/agents/src/services/agents/mor_rewards/tests/benchmarks/test_agent.py diff --git a/submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py b/submodules/agents/src/services/agents/mor_rewards/tools.py old mode 100644 new mode 100755 similarity index 96% rename from submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py rename to submodules/agents/src/services/agents/mor_rewards/tools.py index a6748109..ba4a25c1 --- a/submodules/moragents_dockers/agents/src/agents/mor_rewards/tools.py +++ b/submodules/agents/src/services/agents/mor_rewards/tools.py @@ -1,4 +1,4 @@ -from src.agents.mor_rewards.config import Config +from services.agents.mor_rewards.config import Config from web3 import Web3 diff --git a/submodules/moragents_dockers/agents/src/routes/__init__.py b/submodules/agents/src/services/agents/news_agent/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/routes/__init__.py rename to submodules/agents/src/services/agents/news_agent/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py b/submodules/agents/src/services/agents/news_agent/agent.py old mode 100644 new mode 100755 similarity index 78% rename from submodules/moragents_dockers/agents/src/agents/news_agent/agent.py rename to submodules/agents/src/services/agents/news_agent/agent.py index 30b2cbbe..654525ac --- a/submodules/moragents_dockers/agents/src/agents/news_agent/agent.py +++ b/submodules/agents/src/services/agents/news_agent/agent.py @@ -1,11 +1,12 @@ import logging -import re +from typing import Any, Dict, List + import pyshorteners -from src.agents.news_agent.config import Config -from src.agents.news_agent.tools import clean_html, fetch_rss_feed, is_within_time_window -from src.models.core import ChatRequest, AgentResponse -from src.agents.agent_core.agent import AgentCore +from services.agents.news_agent.config import Config +from services.agents.news_agent.tools import clean_html, fetch_rss_feed, is_within_time_window +from models.service.chat_models import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore from langchain.schema import HumanMessage, SystemMessage logger = logging.getLogger(__name__) @@ -14,34 +15,12 @@ class NewsAgent(AgentCore): """Agent for fetching and analyzing cryptocurrency news.""" - def __init__(self, config, llm, embeddings): - super().__init__(config, llm, embeddings) - self.tools_provided = self.get_tools() + def __init__(self, config: Dict[str, Any], llm: Any) -> None: + super().__init__(config, llm) + self.tools_provided = Config.tools self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) self.url_shortener = pyshorteners.Shortener() - def get_tools(self): - return [ - { - "type": "function", - "function": { - "name": "fetch_crypto_news", - "description": "Fetch and analyze cryptocurrency news for potential price impacts", - "parameters": { - "type": "object", - "properties": { - "coins": { - "type": "array", - "items": {"type": "string"}, - "description": "List of cryptocurrency symbols to fetch news for", - } - }, - "required": ["coins"], - }, - }, - } - ] - async def _process_request(self, request: ChatRequest) -> AgentResponse: """Process the validated chat request for news-related queries.""" try: @@ -52,7 +31,7 @@ async def _process_request(self, request: ChatRequest) -> AgentResponse: "Ask for clarification if a request is ambiguous." ) ), - HumanMessage(content=request.prompt.content), + *request.messages_for_llm, ] result = self.tool_bound_llm.invoke(messages) @@ -99,7 +78,7 @@ async def _execute_tool(self, func_name: str, args: dict) -> AgentResponse: logger.error(f"Error executing tool {func_name}: {str(e)}", exc_info=True) return AgentResponse.needs_info(content="I encountered an issue fetching the news. Could you try again?") - def _check_relevance_and_summarize(self, title, content, coin): + def _check_relevance_and_summarize(self, title: str, content: str, coin: str) -> str: """Check if news is relevant and generate summary.""" logger.info(f"Checking relevance for {coin}: {title}") prompt = Config.RELEVANCE_PROMPT.format(coin=coin, title=title, content=content) @@ -108,9 +87,11 @@ def _check_relevance_and_summarize(self, title, content, coin): max_tokens=Config.LLM_MAX_TOKENS, temperature=Config.LLM_TEMPERATURE, ) + if not isinstance(result.content, str): + return str(result.content).strip() return result.content.strip() - def _process_rss_feed(self, feed_url, coin): + def _process_rss_feed(self, feed_url: str, coin: str) -> List[Dict[str, str]]: """Process RSS feed and filter relevant articles.""" logger.info(f"Processing RSS feed for {coin}: {feed_url}") feed = fetch_rss_feed(feed_url) @@ -131,7 +112,7 @@ def _process_rss_feed(self, feed_url, coin): logger.info(f"Found {len(results)} relevant articles for {coin}") return results - def _fetch_crypto_news(self, coins): + def _fetch_crypto_news(self, coins: List[str]) -> List[Dict[str, str]]: """Fetch and process news for specified coins.""" logger.info(f"Fetching news for coins: {coins}") all_news = [] diff --git a/submodules/moragents_dockers/agents/src/agents/news_agent/config.py b/submodules/agents/src/services/agents/news_agent/config.py old mode 100644 new mode 100755 similarity index 68% rename from submodules/moragents_dockers/agents/src/agents/news_agent/config.py rename to submodules/agents/src/services/agents/news_agent/config.py index 6db7ac04..ec66111a --- a/submodules/moragents_dockers/agents/src/agents/news_agent/config.py +++ b/submodules/agents/src/services/agents/news_agent/config.py @@ -1,17 +1,54 @@ -import logging - -logging.basicConfig(level=logging.INFO) +from models.service.agent_config import AgentConfig class Config: - # RSS Feed URL - GOOGLE_NEWS_BASE_URL = "https://news.google.com/rss/search?q={}&hl=en-US&gl=US&ceid=US:en" + """Configuration for News Agent.""" + + # ************* + # AGENT CONFIG + # ************* + + agent_config = AgentConfig( + path="services.agents.news_agent.agent", + class_name="NewsAgent", + description="Fetches and analyzes cryptocurrency news for potential price impacts", + delegator_description="Discovers, summarizes, and analyzes cryptocurrency news stories with potential market " + "impact, including regulatory developments, partnership announcements, protocol updates, " + "and market-moving events. Use when users seek information about crypto news or event analysis.", + human_readable_name="Crypto News Analyst", + command="news", + upload_required=False, + ) + + # ************* + # TOOLS CONFIG + # ************* - # Time window for news (in hours) - NEWS_TIME_WINDOW = 24 + tools = [ + { + "name": "fetch_crypto_news", + "description": "Fetch and analyze cryptocurrency news for potential price impacts", + "parameters": { + "type": "object", + "properties": { + "coins": { + "type": "array", + "items": {"type": "string"}, + "description": "List of cryptocurrency symbols to fetch news for", + } + }, + "required": ["coins"], + }, + } + ] - # Number of articles to show per token - ARTICLES_PER_TOKEN = 1 + # ************* + # NEWS CONFIG + # ************* + + GOOGLE_NEWS_BASE_URL = "https://news.google.com/rss/search?q={}&hl=en-US&gl=US&ceid=US:en" + NEWS_TIME_WINDOW = 24 # Time window for news (in hours) + ARTICLES_PER_TOKEN = 1 # Number of articles to show per token # LLM configuration LLM_MAX_TOKENS = 150 diff --git a/submodules/moragents_dockers/agents/tests/__init__.py b/submodules/agents/src/services/agents/news_agent/tests/benchmarks/test_agent.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/tests/__init__.py rename to submodules/agents/src/services/agents/news_agent/tests/benchmarks/test_agent.py diff --git a/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py b/submodules/agents/src/services/agents/news_agent/tools.py old mode 100644 new mode 100755 similarity index 97% rename from submodules/moragents_dockers/agents/src/agents/news_agent/tools.py rename to submodules/agents/src/services/agents/news_agent/tools.py index ac874d2d..9a21896e --- a/submodules/moragents_dockers/agents/src/agents/news_agent/tools.py +++ b/submodules/agents/src/services/agents/news_agent/tools.py @@ -7,7 +7,7 @@ import feedparser import pytz from dateutil import parser -from src.agents.news_agent.config import Config +from services.agents.news_agent.config import Config logger = logging.getLogger(__name__) diff --git a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/__init__.py b/submodules/agents/src/services/agents/rag/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/__init__.py rename to submodules/agents/src/services/agents/rag/__init__.py diff --git a/submodules/agents/src/services/agents/rag/agent.py b/submodules/agents/src/services/agents/rag/agent.py new file mode 100755 index 00000000..037d2d30 --- /dev/null +++ b/submodules/agents/src/services/agents/rag/agent.py @@ -0,0 +1,44 @@ +import logging +from typing import Dict, Any + +from langchain.schema import HumanMessage + +from models.service.agent_core import AgentCore +from models.service.chat_models import ChatRequest, AgentResponse +from config import RAG_VECTOR_STORE +from .config import Config + +logger = logging.getLogger(__name__) + + +class RagAgent(AgentCore): + """Agent for handling document Q&A using RAG.""" + + def __init__(self, config: Dict[str, Any], llm: Any) -> None: + super().__init__(config, llm) + + async def _process_request(self, request: ChatRequest) -> AgentResponse: + """Process the validated chat request for RAG.""" + try: + if RAG_VECTOR_STORE.retriever is None: + return AgentResponse.needs_info(content="Please upload a file first") + + retrieved_docs = await RAG_VECTOR_STORE.retrieve(request.prompt.content) + formatted_context = "\n\n".join(doc.page_content for doc in retrieved_docs) + + messages = [ + Config.system_message, + HumanMessage(content=f"Context from documents:\n{formatted_context}"), + *request.messages_for_llm, + ] + + result = self.llm.invoke(messages) + return AgentResponse.success(content=result.content.strip()) + + except Exception as e: + logger.error(f"Error processing request: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) + + async def _execute_tool(self, func_name: str, args: Dict[str, Any]) -> AgentResponse: + """RAG agent doesn't use any tools.""" + return AgentResponse.error(error_message=f"Unknown tool: {func_name}") diff --git a/submodules/agents/src/services/agents/rag/config.py b/submodules/agents/src/services/agents/rag/config.py new file mode 100755 index 00000000..06029e7c --- /dev/null +++ b/submodules/agents/src/services/agents/rag/config.py @@ -0,0 +1,42 @@ +from models.service.agent_config import AgentConfig +from langchain.schema import SystemMessage + + +class Config: + """Configuration for RAG Agent.""" + + # ************* + # AGENT CONFIG + # ------------ + # This must be defined in every agent config file + # It is required to load the agent + # ************* + + agent_config = AgentConfig( + path="services.agents.rag.agent", + class_name="RagAgent", + description="Processes and analyzes uploaded documents to answer questions about their contents", + delegator_description="Specializes in analyzing and extracting insights from uploaded documents, PDFs, " + "and text files. Use when users need to ask questions about specific documents they've " + "provided or want to analyze document contents. Only activates when users have uploaded " + "files to analyze.", + human_readable_name="Document Q&A", + command="rag", + upload_required=True, + ) + + # ************* + # SYSTEM MESSAGE + # ************* + + system_message = SystemMessage( + content=( + "You are a helpful assistant that can answer questions about uploaded documents. " + "You must only answer questions based on the provided context from the documents. " + "If asked about topics outside the documents, politely explain that you can only " + "answer questions about the uploaded documents' contents. " + "Always maintain a helpful and professional tone. " + "If the context is insufficient to fully answer a question, acknowledge this " + "and explain what information is missing." + ) + ) diff --git a/submodules/agents/src/services/agents/rag/routes.py b/submodules/agents/src/services/agents/rag/routes.py new file mode 100755 index 00000000..bd004b20 --- /dev/null +++ b/submodules/agents/src/services/agents/rag/routes.py @@ -0,0 +1,76 @@ +import logging +import os +from fastapi import APIRouter, File, UploadFile +from fastapi.responses import JSONResponse + +from config import RAG_VECTOR_STORE +from models.service.chat_models import AgentResponse + +logger = logging.getLogger(__name__) + +# Create router +router = APIRouter(prefix="/rag", tags=["rag"]) + +# Vector store path for persistence +VECTOR_STORE_PATH = os.path.join(os.getcwd(), "data", "vector_store") + + +@router.post("/upload") +async def upload_file(file: UploadFile = File(...)): + """Upload a file for RAG processing""" + logger.info("Received upload request") + try: + # Process the file using the existing RAG_VECTOR_STORE + result = await RAG_VECTOR_STORE.process_file(file) + + # Save the updated vector store + os.makedirs(os.path.dirname(VECTOR_STORE_PATH), exist_ok=True) + RAG_VECTOR_STORE.save(VECTOR_STORE_PATH) + logger.info(f"Vector store saved to {VECTOR_STORE_PATH}") + + response = AgentResponse.success(content=result).to_chat_message("rag").model_dump() + return JSONResponse(content=response) + + except ValueError as ve: + # Handle validation errors + logger.warning(f"Validation error: {str(ve)}") + response = AgentResponse.error(error_message=str(ve)).to_chat_message("rag").model_dump() + return JSONResponse(content=response) + except Exception as e: + # Handle other errors + logger.error(f"Failed to upload file: {str(e)}") + response = ( + AgentResponse.error(error_message=f"Failed to upload file: {str(e)}").to_chat_message("rag").model_dump() + ) + return JSONResponse(content=response) + + +@router.delete("/documents") +async def delete_documents(): + """Clear all documents from the vector store""" + try: + # Reinitialize the vector store with existing embeddings + RAG_VECTOR_STORE.vector_store = None + RAG_VECTOR_STORE.retriever = None + + # Remove existing vector store file if it exists + if os.path.exists(VECTOR_STORE_PATH): + import shutil + + shutil.rmtree(VECTOR_STORE_PATH, ignore_errors=True) + logger.info(f"Removed vector store at {VECTOR_STORE_PATH}") + + response = ( + AgentResponse.success(content="All documents have been removed from the vector store") + .to_chat_message("rag") + .model_dump() + ) + return JSONResponse(content=response) + except Exception as e: + logger.error(f"Error clearing vector store: {str(e)}") + response = ( + AgentResponse.error(error_message=f"Error clearing vector store: {str(e)}") + .to_chat_message("rag") + .model_dump() + ) + return JSONResponse(content=response) diff --git a/submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/__init__.py b/submodules/agents/src/services/agents/rag/tests/benchmarks/test_agent.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/tests/claim_agent_benchmarks/adapters/__init__.py rename to submodules/agents/src/services/agents/rag/tests/benchmarks/test_agent.py diff --git a/submodules/moragents_dockers/agents/tests/news_agent_benchmarks/__init__.py b/submodules/agents/src/services/agents/realtime_search/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/tests/news_agent_benchmarks/__init__.py rename to submodules/agents/src/services/agents/realtime_search/__init__.py diff --git a/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py b/submodules/agents/src/services/agents/realtime_search/agent.py old mode 100644 new mode 100755 similarity index 91% rename from submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py rename to submodules/agents/src/services/agents/realtime_search/agent.py index 57f960fb..b3a0c126 --- a/submodules/moragents_dockers/agents/src/agents/realtime_search/agent.py +++ b/submodules/agents/src/services/agents/realtime_search/agent.py @@ -8,10 +8,11 @@ from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys -from src.models.core import ChatRequest, AgentResponse -from src.agents.agent_core.agent import AgentCore -from langchain.schema import HumanMessage, SystemMessage -from src.agents.realtime_search.config import Config +from models.service.chat_models import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore +from langchain.schema import SystemMessage +from services.agents.realtime_search.config import Config +from config import LLM_AGENT logger = logging.getLogger(__name__) @@ -19,9 +20,8 @@ class RealtimeSearchAgent(AgentCore): """Agent for performing real-time web searches.""" - def __init__(self, config: Dict[str, Any], llm: Any, embeddings: Any): - super().__init__(config, llm, embeddings) - self.last_search_term = None + def __init__(self, config: Dict[str, Any], llm: Any): + super().__init__(config, llm) self.tools_provided = Config.tools self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) @@ -35,7 +35,7 @@ async def _process_request(self, request: ChatRequest) -> AgentResponse: "Ask for clarification if a request is ambiguous." ) ), - HumanMessage(content=request.prompt.content), + *request.messages_for_llm, ] result = self.tool_bound_llm.invoke(messages) @@ -54,6 +54,7 @@ async def _execute_tool(self, func_name: str, args: Dict[str, Any]) -> AgentResp return AgentResponse.needs_info(content="Could you please provide a search term?") search_results = self._perform_search_with_web_scraping(search_term) + logger.info(f"Search results: {search_results}") if "Error performing web search" in search_results: return AgentResponse.error(error_message=search_results) @@ -66,7 +67,7 @@ async def _execute_tool(self, func_name: str, args: Dict[str, Any]) -> AgentResp logger.error(f"Error executing tool {func_name}: {str(e)}", exc_info=True) return AgentResponse.error(error_message=str(e)) - def _perform_search_with_web_scraping(self, search_term: str) -> str: + def _perform_search_with_web_scraping(self, search_term: str) -> AgentResponse: """Perform web search using requests and BeautifulSoup.""" logger.info(f"Performing web search for: {search_term}") @@ -146,7 +147,7 @@ def _synthesize_answer(self, search_term: str, search_results: str) -> str: ] try: - result = self.llm.invoke(messages, max_tokens=Config.MAX_TOKENS, temperature=Config.TEMPERATURE) + result = LLM_AGENT.invoke(messages) if not result.content.strip(): return AgentResponse.needs_info( content="I found some results but couldn't understand them well. Could you rephrase your question?" diff --git a/submodules/moragents_dockers/agents/src/agents/realtime_search/config.py b/submodules/agents/src/services/agents/realtime_search/config.py old mode 100644 new mode 100755 similarity index 51% rename from submodules/moragents_dockers/agents/src/agents/realtime_search/config.py rename to submodules/agents/src/services/agents/realtime_search/config.py index 52b96d4d..a539c8ae --- a/submodules/moragents_dockers/agents/src/agents/realtime_search/config.py +++ b/submodules/agents/src/services/agents/realtime_search/config.py @@ -1,4 +1,30 @@ +from models.service.agent_config import AgentConfig + + class Config: + """Configuration for Realtime Search Agent.""" + + # ************* + # AGENT CONFIG + # ************* + + agent_config = AgentConfig( + path="services.agents.realtime_search.agent", + class_name="RealtimeSearchAgent", + description="Performs real-time web searches to find current information. Should be used whenever the user refers to something recent or time-sensitive.", + delegator_description="Performs up-to-the-minute web searches to retrieve current information on breaking news, " + "recent events, or time-sensitive market developments. Use whenever queries reference recent " + 'events, require current data, or include time indicators ("today", "latest", etc.).', + human_readable_name="Web Search Assistant", + command="search", + upload_required=False, + is_enabled=False, + ) + + # ************* + # TOOLS CONFIG + # ************* + tools = [ { "name": "perform_web_search", @@ -16,6 +42,10 @@ class Config: } ] + # ************* + # SEARCH CONFIG + # ************* + # Web scraping configuration USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" MAX_SEARCH_RESULTS = 5 diff --git a/submodules/agents/src/services/agents/realtime_search/tests/benchmarks/test_realtime_search_agent.py b/submodules/agents/src/services/agents/realtime_search/tests/benchmarks/test_realtime_search_agent.py new file mode 100755 index 00000000..33387438 --- /dev/null +++ b/submodules/agents/src/services/agents/realtime_search/tests/benchmarks/test_realtime_search_agent.py @@ -0,0 +1,103 @@ +import pytest +import logging +from unittest.mock import patch, Mock +from bs4 import BeautifulSoup +from langchain.schema import SystemMessage + +from services.agents.realtime_search.agent import RealtimeSearchAgent +from models.service.chat_models import AgentResponse, ChatRequest +from services.agents.realtime_search.config import Config + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def realtime_search_agent(): + config = {"name": "realtime_search", "description": "Agent for real-time web searches"} + llm = Mock() + return RealtimeSearchAgent(config=config, llm=llm) + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_web_search_success(realtime_search_agent, make_chat_request): + request = make_chat_request(content="Search for latest news about AI", agent_name="realtime_search") + + mock_results = """ + Result: + Latest breakthrough in AI technology announced + Scientists develop new machine learning model + """ + + with patch.object(realtime_search_agent.tool_bound_llm, "invoke") as mock_invoke: + mock_invoke.return_value = "Here are the latest AI developments..." + + response = await realtime_search_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert "latest AI developments" in response.content + + # Verify correct messages were passed + expected_messages = [ + SystemMessage( + content=( + "You are a real-time web search agent that helps find current information. " + "Ask for clarification if a request is ambiguous." + ) + ), + *request.messages_for_llm, + ] + mock_invoke.assert_called_once_with(expected_messages) + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_web_search_no_results(realtime_search_agent): + search_term = "nonexistent topic" + + with patch.object(realtime_search_agent, "_perform_search_with_web_scraping") as mock_search: + mock_search.return_value = AgentResponse.needs_info( + content="I couldn't find any results for that search. Could you try rephrasing it?" + ) + + response = await realtime_search_agent._execute_tool("perform_web_search", {"search_term": search_term}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "needs_info" + assert "try rephrasing" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_web_search_error_handling(realtime_search_agent): + search_term = "test search" + + with patch.object(realtime_search_agent, "_perform_search_with_web_scraping") as mock_search: + mock_search.side_effect = Exception("Search failed") + + response = await realtime_search_agent._execute_tool("perform_web_search", {"search_term": search_term}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Search failed" in response.error_message + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_unknown_tool(realtime_search_agent): + response = await realtime_search_agent._execute_tool("unknown_function", {}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Unknown tool" in response.error_message + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_empty_search_term(realtime_search_agent): + response = await realtime_search_agent._execute_tool("perform_web_search", {"search_term": ""}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "needs_info" + assert "provide a search term" in response.content diff --git a/submodules/moragents_dockers/agents/tests/price_fetching/__init__.py b/submodules/agents/src/services/agents/rugcheck/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/tests/price_fetching/__init__.py rename to submodules/agents/src/services/agents/rugcheck/__init__.py diff --git a/submodules/agents/src/services/agents/rugcheck/agent.py b/submodules/agents/src/services/agents/rugcheck/agent.py new file mode 100755 index 00000000..7688d45b --- /dev/null +++ b/submodules/agents/src/services/agents/rugcheck/agent.py @@ -0,0 +1,91 @@ +import logging +from typing import Dict, Any +from models.service.agent_core import AgentCore +from models.service.chat_models import ChatRequest, AgentResponse +from .config import Config, TokenRegistry +from .tool_types import RugcheckToolType +from .tools import ( + fetch_token_report, + fetch_most_viewed, + fetch_most_voted, + resolve_token_identifier, +) + +logger = logging.getLogger(__name__) + + +class RugcheckAgent(AgentCore): + """Agent for analyzing token safety and trends using the Rugcheck API.""" + + def __init__(self, config: Dict[str, Any], llm: Any): + super().__init__(config, llm) + self.tools_provided = Config.tools + self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) + self.api_base_url = "https://api.rugcheck.xyz/v1" + self.token_registry = TokenRegistry() + + async def _process_request(self, request: ChatRequest) -> AgentResponse: + """Process the validated chat request for token analysis.""" + try: + messages = [Config.system_message, *request.messages_for_llm] + + result = self.tool_bound_llm.invoke(messages) + return await self._handle_llm_response(result) + + except Exception as e: + logger.error(f"Error processing request: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) + + async def _execute_tool(self, func_name: str, args: Dict[str, Any]) -> AgentResponse: + """Execute the appropriate Rugcheck API tool based on function name.""" + try: + if func_name == RugcheckToolType.GET_TOKEN_REPORT.value: + identifier = args.get("identifier") + if not identifier: + return AgentResponse.error(error_message="Please provide a token name or mint address") + + try: + mint_address = await resolve_token_identifier(self.token_registry, identifier) + if not mint_address: + return AgentResponse.error(error_message=f"Could not resolve token identifier: {identifier}") + + report_response = await fetch_token_report(self.api_base_url, mint_address) + return AgentResponse.success( + content=report_response.formatted_response, + metadata=report_response.model_dump(), + action_type=RugcheckToolType.GET_TOKEN_REPORT.value, + ) + + except Exception as e: + return AgentResponse.error(error_message=f"Failed to get token report: {str(e)}") + + elif func_name == RugcheckToolType.GET_MOST_VIEWED.value: + try: + viewed_response = await fetch_most_viewed(self.api_base_url) + return AgentResponse.success( + content=viewed_response.formatted_response, + metadata=viewed_response.model_dump(), + action_type=RugcheckToolType.GET_MOST_VIEWED.value, + ) + + except Exception as e: + return AgentResponse.error(error_message=f"Failed to get most viewed tokens: {str(e)}") + + elif func_name == RugcheckToolType.GET_MOST_VOTED.value: + try: + voted_response = await fetch_most_voted(self.api_base_url) + return AgentResponse.success( + content=voted_response.formatted_response, + metadata=voted_response.model_dump(), + action_type=RugcheckToolType.GET_MOST_VOTED.value, + ) + + except Exception as e: + return AgentResponse.error(error_message=f"Failed to get most voted tokens: {str(e)}") + + else: + return AgentResponse.error(error_message=f"Unknown tool function: {func_name}") + + except Exception as e: + logger.error(f"Error executing tool {func_name}: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) diff --git a/submodules/moragents_dockers/agents/src/agents/rugcheck/client.py b/submodules/agents/src/services/agents/rugcheck/client.py old mode 100644 new mode 100755 similarity index 82% rename from submodules/moragents_dockers/agents/src/agents/rugcheck/client.py rename to submodules/agents/src/services/agents/rugcheck/client.py index 7051e2b4..25dcff8b --- a/submodules/moragents_dockers/agents/src/agents/rugcheck/client.py +++ b/submodules/agents/src/services/agents/rugcheck/client.py @@ -12,19 +12,16 @@ def __init__(self, base_url: str = "https://api.rugcheck.xyz/v1"): self.base_url = base_url self._session: Optional[aiohttp.ClientSession] = None - async def _ensure_session(self): - """Ensure aiohttp session exists.""" - if self._session is None or self._session.closed: - self._session = aiohttp.ClientSession() - - async def close(self): + async def close(self) -> None: """Close the aiohttp session.""" if self._session and not self._session.closed: await self._session.close() - async def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]: + async def _make_request(self, method: str, endpoint: str, **kwargs: Any) -> Any: """Make HTTP request to Rugcheck API.""" - await self._ensure_session() + if self._session is None or self._session.closed: + self._session = aiohttp.ClientSession() + url = f"{self.base_url}{endpoint}" try: @@ -40,17 +37,17 @@ async def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, logger.error(f"Unexpected error for {url}: {str(e)}") raise - async def get_token_report(self, mint: str) -> Dict[str, Any]: + async def get_token_report(self, mint: str) -> Any: """Get detailed report for a token mint.""" endpoint = f"/tokens/{mint}/report/summary" return await self._make_request("GET", endpoint) - async def get_most_viewed(self) -> Dict[str, Any]: + async def get_most_viewed(self) -> Any: """Get most viewed tokens in past 24 hours.""" endpoint = "/stats/recent" return await self._make_request("GET", endpoint) - async def get_most_voted(self) -> Dict[str, Any]: + async def get_most_voted(self) -> Any: """Get most voted tokens in past 24 hours.""" endpoint = "/stats/trending" return await self._make_request("GET", endpoint) diff --git a/submodules/moragents_dockers/agents/src/agents/rugcheck/config.py b/submodules/agents/src/services/agents/rugcheck/config.py old mode 100644 new mode 100755 similarity index 66% rename from submodules/moragents_dockers/agents/src/agents/rugcheck/config.py rename to submodules/agents/src/services/agents/rugcheck/config.py index 4d4ca2af..6f3436ed --- a/submodules/moragents_dockers/agents/src/agents/rugcheck/config.py +++ b/submodules/agents/src/services/agents/rugcheck/config.py @@ -1,13 +1,17 @@ -from typing import Dict, Optional import re +from typing import Dict, List, Optional, Pattern + +from langchain.schema import SystemMessage +from models.service.agent_config import AgentConfig +from services.agents.rugcheck.tool_types import RugcheckToolType class TokenRegistry: """Registry for mapping between token names and mint addresses.""" - def __init__(self): + def __init__(self) -> None: # This mapping should be regularly updated with the most popular tokens - self._name_to_mint = { + self._name_to_mint: Dict[str, str] = { "BONK": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263", # BONK "RAY": "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R", # Raydium "MEAN": "MEANeD3XDdUmNMsRGjASkSWdC8prLYsoRJ61pPeHctD", # Mean Protocol @@ -60,10 +64,10 @@ def __init__(self): } # Create reverse mapping - self._mint_to_name = {v: k for k, v in self._name_to_mint.items()} + self._mint_to_name: Dict[str, str] = {v: k for k, v in self._name_to_mint.items()} # Compile regex pattern for Solana mint addresses (base58 encoded public keys) - self._mint_pattern = re.compile(r"^[1-9A-HJ-NP-Za-km-z]{32,44}$") + self._mint_pattern: Pattern[str] = re.compile(r"^[1-9A-HJ-NP-Za-km-z]{32,44}$") def get_mint_by_name(self, name: str) -> Optional[str]: """Get mint address for a token name (case-insensitive).""" @@ -77,13 +81,56 @@ def is_valid_mint_address(self, address: str) -> bool: """Check if a string matches the pattern of a Solana mint address.""" return bool(self._mint_pattern.match(address)) + def get_all_tokens(self) -> List[str]: + """Get all tokens in the registry.""" + return list(self._name_to_mint.keys()) + class Config: - """Configuration for RugcheckAgent tools.""" + """Configuration for Rugcheck Agent.""" + + # ************* + # AGENT CONFIG + # ************* + + agent_config = AgentConfig( + path="services.agents.rugcheck.agent", + class_name="RugcheckAgent", + description="Analyzes tokens for potential risks and scams", + delegator_description="Performs comprehensive security analysis on cryptocurrency tokens and projects to identify " + "potential scam indicators, including contract audits, liquidity analysis, holder concentration " + "checks, and team verification. Use when users express any concerns about token safety or " + "project legitimacy.", + human_readable_name="Token Risk Analyzer", + command="rugcheck", + upload_required=False, + ) + + # ************* + # SYSTEM MESSAGE + # ************* + + system_message = SystemMessage( + content=( + "You are an agent that can analyze tokens for safety and view trending tokens. " + "You can handle both token names (like 'BONK' or 'RAY') and mint addresses. " + "When you need to perform an analysis, use the appropriate function call. " + "For token names, you must verify they exist in the supported token list. " + "The supported tokens are: " + ", ".join(TokenRegistry().get_all_tokens()) + ". " + "If a token name is not in this list, you must find its mint address in the " + "current chat message or in the conversation history. " + "Do not make up or hallucinate mint addresses - if you cannot find a valid mint address, " + "inform the user that the token is not supported." + ) + ) + + # ************* + # TOOLS CONFIG + # ************* tools = [ { - "name": "get_token_report", + "name": RugcheckToolType.GET_TOKEN_REPORT.value, "description": "Generate a report summary for a given token using either its name or mint address", "parameters": { "type": "object", @@ -94,13 +141,19 @@ class Config: }, }, { - "name": "get_most_viewed", + "name": RugcheckToolType.GET_MOST_VIEWED.value, "description": "Get most viewed tokens in past 24 hours", - "parameters": {"type": "object", "properties": {}}, + "parameters": { + "type": "object", + "properties": {}, + }, }, { - "name": "get_most_voted", + "name": RugcheckToolType.GET_MOST_VOTED.value, "description": "Get most voted tokens in past 24 hours", - "parameters": {"type": "object", "properties": {}}, + "parameters": { + "type": "object", + "properties": {}, + }, }, ] diff --git a/submodules/agents/src/services/agents/rugcheck/models.py b/submodules/agents/src/services/agents/rugcheck/models.py new file mode 100644 index 00000000..58ce5259 --- /dev/null +++ b/submodules/agents/src/services/agents/rugcheck/models.py @@ -0,0 +1,133 @@ +from pydantic import BaseModel +from typing import Dict, Any, Optional, List + + +# Response Models +class TokenRisk(BaseModel): + """Model for token risk assessment.""" + + name: Optional[str] = None + description: Optional[str] = None + score: Optional[float] = None + + @property + def formatted_response(self) -> str: + """Format risk assessment for display.""" + if not all([self.name, self.description, self.score]): + return "- Incomplete risk data" + return f"- {self.name}: {self.description} (Risk Score: {self.score:.2f})" + + +class TokenReport(BaseModel): + """Model for complete token analysis report.""" + + score: Optional[float] = None + risks: Optional[List[TokenRisk]] = [] + + @property + def formatted_response(self) -> str: + """Format complete report for display.""" + formatted = "# Token Risk Analysis Report\n\n" + formatted += f"Overall Risk Score: {f'{self.score:.2f}' if self.score is not None else 'Unknown'}\n\n" + formatted += "## Risk Assessments:\n\n" + if self.risks: + for risk in self.risks: + formatted += f"{risk.formatted_response}\n" + else: + formatted += "No risk assessments available\n" + return formatted + + +class TokenReportResponse(BaseModel): + """Model for token report API response.""" + + report: Optional[TokenReport] = None + mint_address: Optional[str] = None + token_name: Optional[str] = None + identifier: Optional[str] = None + + @property + def formatted_response(self) -> str: + """Format complete response for display.""" + formatted = f"# Analysis Report for {self.token_name or 'Unknown Token'}\n\n" + formatted += f"Mint Address: {self.mint_address or 'Unknown'}\n" + formatted += f"Identifier: {self.identifier or 'Unknown'}\n\n" + if self.report: + formatted += self.report.formatted_response + else: + formatted += "No report data available" + return formatted + + +class ViewedToken(BaseModel): + """Model for token view statistics.""" + + mint: Optional[str] = None + metadata: Optional[Dict[str, Any]] = {} + visits: Optional[int] = 0 + user_visits: Optional[int] = 0 + + @property + def formatted_response(self) -> str: + """Format view statistics for display.""" + name = self.metadata.get("name", "Unknown Token") if self.metadata else "Unknown Token" + symbol = self.metadata.get("symbol", "???") if self.metadata else "???" + formatted = f"## {name} ({symbol})\n" + formatted += f"- Mint Address: {self.mint or 'Unknown'}\n" + formatted += f"- Total Visits: {self.visits:,}\n" + formatted += f"- Unique Visitors: {self.user_visits:,}\n" + return formatted + + +class ViewedTokensResponse(BaseModel): + """Model for viewed tokens API response.""" + + tokens: Optional[List[ViewedToken]] = [] + + @property + def formatted_response(self) -> str: + """Format complete response for display.""" + formatted = "# Most Viewed Tokens (24h)\n\n" + if self.tokens: + for token in self.tokens: + formatted += f"{token.formatted_response}\n" + else: + formatted += "No token view data available\n" + return formatted + + +class VotedToken(BaseModel): + """Model for token voting statistics.""" + + mint: Optional[str] = None + up_count: Optional[int] = 0 + vote_count: Optional[int] = 0 + + @property + def formatted_response(self) -> str: + """Format voting statistics for display.""" + formatted = f"## Token {self.mint or 'Unknown'}\n" + formatted += f"- Upvotes: {self.up_count:,}\n" + formatted += f"- Total Votes: {self.vote_count:,}\n" + if self.vote_count and self.up_count: + formatted += f"- Approval Rate: {(self.up_count/self.vote_count)*100:.1f}%\n" + else: + formatted += "- Approval Rate: Unknown\n" + return formatted + + +class VotedTokensResponse(BaseModel): + """Model for voted tokens API response.""" + + tokens: Optional[List[VotedToken]] = [] + + @property + def formatted_response(self) -> str: + """Format complete response for display.""" + formatted = "# Most Voted Tokens (24h)\n\n" + if self.tokens: + for token in self.tokens: + formatted += f"{token.formatted_response}\n" + else: + formatted += "No token voting data available\n" + return formatted diff --git a/submodules/agents/src/services/agents/rugcheck/tests/benchmarks/test_rugcheck_agent.py b/submodules/agents/src/services/agents/rugcheck/tests/benchmarks/test_rugcheck_agent.py new file mode 100755 index 00000000..6b975378 --- /dev/null +++ b/submodules/agents/src/services/agents/rugcheck/tests/benchmarks/test_rugcheck_agent.py @@ -0,0 +1,125 @@ +import pytest +import logging +from unittest.mock import patch, Mock +from typing import Dict, Any + +from services.agents.rugcheck.agent import RugcheckAgent +from models.service.chat_models import AgentResponse, ChatRequest +from services.agents.rugcheck.tool_types import RugcheckToolType +from services.agents.rugcheck.tools import fetch_token_report, fetch_most_viewed, fetch_most_voted + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def rugcheck_agent(llm): + config: Dict[str, Any] = {"name": "rugcheck", "description": "Agent for analyzing token safety"} + return RugcheckAgent(config, llm) + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_token_report_success(rugcheck_agent, make_chat_request): + request = make_chat_request(content="Analyze token BONK") + + with patch.object(rugcheck_agent.tool_bound_llm, "invoke") as mock_invoke: + mock_invoke.return_value = { + "tool_calls": [ + {"function": {"name": RugcheckToolType.GET_TOKEN_REPORT.value, "arguments": {"identifier": "BONK"}}} + ] + } + + with patch("services.agents.rugcheck.tools.resolve_token_identifier") as mock_resolve: + mock_resolve.return_value = "BONK123" + + with patch("services.agents.rugcheck.tools.fetch_token_report") as mock_fetch: + mock_fetch.return_value.formatted_response = "Token Analysis Report\nOverall Risk Score: 85" + mock_fetch.return_value.model_dump.return_value = { + "score": 85, + "risks": [{"name": "Liquidity Risk", "description": "Low liquidity detected", "score": 60}], + } + + response = await rugcheck_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert "Token Analysis Report" in response.content + assert "Overall Risk Score: 85" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_most_viewed_success(rugcheck_agent): + with patch("services.agents.rugcheck.tools.fetch_most_viewed") as mock_fetch: + mock_fetch.return_value.formatted_response = "Most Viewed Tokens\nToken1: 1000 visits" + mock_fetch.return_value.model_dump.return_value = { + "tokens": [ + {"mint": "mint123", "metadata": {"name": "Token1", "symbol": "TK1"}, "visits": 1000, "user_visits": 500} + ] + } + + response = await rugcheck_agent._execute_tool(RugcheckToolType.GET_MOST_VIEWED.value, {}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert "Most Viewed Tokens" in response.content + assert "Token1" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_most_voted_success(rugcheck_agent): + with patch("services.agents.rugcheck.tools.fetch_most_voted") as mock_fetch: + mock_fetch.return_value.formatted_response = "Most Voted Tokens\nToken1: 100 upvotes" + mock_fetch.return_value.model_dump.return_value = { + "tokens": [{"mint": "mint123", "up_count": 100, "vote_count": 150}] + } + + response = await rugcheck_agent._execute_tool(RugcheckToolType.GET_MOST_VOTED.value, {}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert "Most Voted Tokens" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_invalid_token_identifier(rugcheck_agent): + with patch("services.agents.rugcheck.tools.resolve_token_identifier") as mock_resolve: + mock_resolve.return_value = None + + response = await rugcheck_agent._execute_tool( + RugcheckToolType.GET_TOKEN_REPORT.value, {"identifier": "INVALID_TOKEN"} + ) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Could not resolve token identifier" in response.error_message + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_unknown_tool(rugcheck_agent): + response = await rugcheck_agent._execute_tool("unknown_function", {}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Unknown tool function" in response.error_message + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_api_error_handling(rugcheck_agent): + with patch("services.agents.rugcheck.tools.resolve_token_identifier") as mock_resolve: + mock_resolve.return_value = "mint123" + + with patch("services.agents.rugcheck.tools.fetch_token_report") as mock_fetch: + mock_fetch.side_effect = Exception("API Error") + + response = await rugcheck_agent._execute_tool( + RugcheckToolType.GET_TOKEN_REPORT.value, {"identifier": "TOKEN"} + ) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Failed to get token report" in response.error_message diff --git a/submodules/agents/src/services/agents/rugcheck/tool_types.py b/submodules/agents/src/services/agents/rugcheck/tool_types.py new file mode 100644 index 00000000..621c2e0a --- /dev/null +++ b/submodules/agents/src/services/agents/rugcheck/tool_types.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class RugcheckToolType(Enum): + """Enum for different Rugcheck tool types""" + + GET_TOKEN_REPORT = "get_token_report" + GET_MOST_VIEWED = "get_most_viewed" + GET_MOST_VOTED = "get_most_voted" diff --git a/submodules/agents/src/services/agents/rugcheck/tools.py b/submodules/agents/src/services/agents/rugcheck/tools.py new file mode 100755 index 00000000..d6bce450 --- /dev/null +++ b/submodules/agents/src/services/agents/rugcheck/tools.py @@ -0,0 +1,72 @@ +import logging +from typing import Optional +from .client import RugcheckClient +from .config import TokenRegistry +from .models import ( + TokenReportResponse, + ViewedTokensResponse, + VotedTokensResponse, + TokenReport, + ViewedToken, + VotedToken, + TokenRisk, +) + +logger = logging.getLogger(__name__) + + +# Tool Functions +async def fetch_token_report(api_base_url: str, mint: str) -> TokenReportResponse: + """Fetch token report from Rugcheck API.""" + client = RugcheckClient(api_base_url) + try: + report_data = await client.get_token_report(mint) + return TokenReportResponse( + report=TokenReport( + score=report_data.get("score"), risks=[TokenRisk(**risk) for risk in report_data.get("risks", [])] + ), + mint_address=mint, + token_name=report_data.get("token_name", ""), + identifier=mint, + ) + finally: + await client.close() + + +async def fetch_most_viewed(api_base_url: str) -> ViewedTokensResponse: + """Fetch most viewed tokens from Rugcheck API.""" + client = RugcheckClient(api_base_url) + try: + viewed_data = await client.get_most_viewed() + tokens = [ViewedToken(**token) for token in viewed_data] + return ViewedTokensResponse(tokens=tokens) + finally: + await client.close() + + +async def fetch_most_voted(api_base_url: str) -> VotedTokensResponse: + """Fetch most voted tokens from Rugcheck API.""" + client = RugcheckClient(api_base_url) + try: + voted_data = await client.get_most_voted() + tokens = [VotedToken(**token) for token in voted_data] + return VotedTokensResponse(tokens=tokens) + finally: + await client.close() + + +async def resolve_token_identifier(token_registry: TokenRegistry, identifier: str) -> Optional[str]: + """ + Resolve a token identifier (name or mint address) to a mint address. + Returns None if the identifier cannot be resolved. + """ + # If it's already a mint address, return it directly + if token_registry.is_valid_mint_address(identifier): + return identifier + + # Try to resolve token name to mint address + mint_address = token_registry.get_mint_by_name(identifier) + if mint_address: + return mint_address + + return None diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/README.md b/submodules/agents/src/services/agents/token_swap/README.md old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/src/agents/token_swap/README.md rename to submodules/agents/src/services/agents/token_swap/README.md diff --git a/submodules/moragents_dockers/agents/tests/price_fetching/adapters/__init__.py b/submodules/agents/src/services/agents/token_swap/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/tests/price_fetching/adapters/__init__.py rename to submodules/agents/src/services/agents/token_swap/__init__.py diff --git a/submodules/agents/src/services/agents/token_swap/agent.py b/submodules/agents/src/services/agents/token_swap/agent.py new file mode 100755 index 00000000..8d209609 --- /dev/null +++ b/submodules/agents/src/services/agents/token_swap/agent.py @@ -0,0 +1,164 @@ +import logging +from typing import Dict, Any + +from models.service.agent_core import AgentCore +from models.service.chat_models import ChatRequest, AgentResponse + +from .config import Config +from .utils.exceptions import TokenNotFoundError, InsufficientFundsError, SwapNotPossibleError +from .tools import swap_coins, get_transaction_status +from .utils.tool_types import SwapToolType + +logger = logging.getLogger(__name__) + + +import logging +from typing import Dict, Any, Union, Optional + +from models.service.agent_core import AgentCore +from models.service.chat_models import ChatRequest, AgentResponse + +from .config import Config +from .utils.exceptions import TokenNotFoundError, InsufficientFundsError, SwapNotPossibleError +from .tools import swap_coins, get_transaction_status +from .utils.tool_types import SwapToolType +from .models import SwapQuoteResponse, TransactionResponse + +logger = logging.getLogger(__name__) + + +class TokenSwapAgent(AgentCore): + """Agent for token swapping operations.""" + + def __init__(self, config: Dict[str, Any], llm: Any): + super().__init__(config, llm) + self.tools_provided = Config.tools + self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) + self.wallet_address = None + self.chain_id = None + + async def _process_request(self, request: ChatRequest) -> AgentResponse: + """Process the validated chat request for token swap operations.""" + try: + messages = [Config.system_message, *request.messages_for_llm] + + # Store wallet and chain information + self.wallet_address = request.wallet_address + self.chain_id = request.chain_id + + # Validate wallet connection + if not self.wallet_address or not self.chain_id: + return AgentResponse.needs_info(content="Please connect your wallet to enable swap functionality") + + result = self.tool_bound_llm.invoke(messages) + return await self._handle_llm_response(result) + + except Exception as e: + logger.error(f"Error processing request: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=f"Failed to process request: {str(e)}") + + async def _execute_tool(self, func_name: str, args: Dict[str, Any]) -> AgentResponse: + """Execute the appropriate token swap tool based on function name.""" + try: + if func_name == SwapToolType.SWAP_TOKENS.value: + return await self._execute_swap_tokens(args) + elif func_name == SwapToolType.GET_TRANSACTION_STATUS.value: + return await self._execute_get_transaction_status(args) + else: + return AgentResponse.error(error_message=f"Unknown tool: {func_name}") + + except Exception as e: + logger.error(f"Unexpected error in tool execution {func_name}: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=f"Unexpected error: {str(e)}") + + async def _execute_swap_tokens(self, args: Dict[str, Any]) -> AgentResponse: + """Execute the swap tokens operation with comprehensive error handling.""" + try: + # Revalidate wallet connection + if not self.wallet_address or not self.chain_id: + return AgentResponse.needs_info(content="Please connect your wallet to enable swap functionality") + + # Validate required parameters + required_params = ["source_token", "destination_token", "amount"] + missing_params = [param for param in required_params if param not in args or args[param] is None] + + if missing_params: + return AgentResponse.needs_info( + content=f"Please provide the following information: {', '.join(missing_params)}" + ) + + # Validate amount is a positive number + try: + amount = float(args["amount"]) + if amount <= 0: + return AgentResponse.error(error_message="Swap amount must be greater than zero") + except ValueError: + return AgentResponse.error(error_message=f"Invalid amount format: {args['amount']}") + + # Execute swap operation + swap_response = await swap_coins( + source_token=args["source_token"], + destination_token=args["destination_token"], + amount=amount, + chain_id=self.chain_id, + wallet_address=self.wallet_address, + ) + + return AgentResponse.success( + content=swap_response.formatted_response, + metadata=swap_response.model_dump(), + action_type="swap", + ) + + except TokenNotFoundError as e: + logger.error(f"Token not found: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=f"Token not found: {str(e)}") + + except InsufficientFundsError as e: + logger.error(f"Insufficient funds: {str(e)}", exc_info=True) + logger.info("We get here") + return AgentResponse.error(error_message=f"Insufficient funds: {str(e)}") + + except SwapNotPossibleError as e: + logger.error(f"Swap not possible: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=f"Swap not possible: {str(e)}") + + except Exception as e: + logger.error(f"Error executing swap: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=f"Error executing swap: {str(e)}") + + async def _execute_get_transaction_status(self, args: Dict[str, Any]) -> AgentResponse: + """Execute the get transaction status operation with comprehensive error handling.""" + try: + # Extract and validate transaction hash + tx_hash = args.get("tx_hash") + if not tx_hash: + return AgentResponse.needs_info(content="Please provide a transaction hash") + + # Use provided chain_id and wallet_address from args if available, otherwise use class values + chain_id = args.get("chainId", self.chain_id) + wallet_address = args.get("walletAddress", self.wallet_address) + + # Validate we have the necessary parameters + if not chain_id: + return AgentResponse.needs_info(content="Please provide a chain ID or connect your wallet") + + if not wallet_address: + return AgentResponse.needs_info(content="Please provide a wallet address or connect your wallet") + + # Execute transaction status check + tx_response = await get_transaction_status( + tx_hash=tx_hash, + chain_id=chain_id, + wallet_address=wallet_address, + ) + + return AgentResponse.success( + content=tx_response.formatted_response, + metadata=tx_response.model_dump(), + action_type="transaction_status", + ) + + except Exception as e: + logger.error(f"Error getting transaction status: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=f"Error retrieving transaction status: {str(e)}") diff --git a/submodules/agents/src/services/agents/token_swap/config.py b/submodules/agents/src/services/agents/token_swap/config.py new file mode 100755 index 00000000..299b66c4 --- /dev/null +++ b/submodules/agents/src/services/agents/token_swap/config.py @@ -0,0 +1,165 @@ +from models.service.agent_config import AgentConfig +from langchain.schema import SystemMessage +from .utils.tool_types import SwapToolType + + +class Config: + """Configuration for Token Swap Agent.""" + + # ************* + # AGENT CONFIG + # ************* + + agent_config = AgentConfig( + path="services.agents.token_swap.agent", + class_name="TokenSwapAgent", + description="Handles token swaps across multiple networks", + delegator_description="Executes and optimizes token exchange transactions across multiple blockchains, " + "including route optimization, gas fee estimation, slippage protection, and cross-chain " + "bridge coordination. Use when users want to exchange one token for another or need swap " + "execution guidance.", + human_readable_name="Token Swap Manager", + command="swap", + upload_required=False, + is_enabled=False, + ) + + # *************# + # SYSTEM MESSAGE + # *************# + system_message = SystemMessage( + content=( + "You are a helpful assistant for token swapping operations. " + "You can help users swap between different cryptocurrencies on various blockchain networks. " + "You can also check the status of pending transactions. " + "When a user wants to swap tokens, ask for the following information if not provided: " + "- Source token (token1) " + "- Destination token (token2) " + "- Amount to swap " + "When a user wants to check transaction status, ask for: " + "- Transaction hash " + "- Chain ID" + ) + ) + + # ************* + # TOOLS CONFIG + # ************* + + tools = [ + { + "type": "function", + "function": { + "name": SwapToolType.SWAP_TOKENS.value, + "description": ( + "Construct a token swap transaction with validation and quote. " + "Make sure the source token and destination token ONLY include " + "the token symbol, not the amount. The amount is a separate field." + ), + "parameters": { + "type": "object", + "properties": { + "source_token": { + "type": "string", + "description": "Name or address of the source token to sell", + "required": False, + }, + "destination_token": { + "type": "string", + "description": "Name or address of the destination token to buy", + "required": False, + }, + "amount": { + "type": "string", + "description": "Amount of source token to swap", + "required": False, + }, + }, + }, + }, + }, + { + "type": "function", + "function": { + "name": SwapToolType.GET_TRANSACTION_STATUS.value, + "description": "Get the current status of a transaction", + "parameters": { + "type": "object", + "properties": { + "tx_hash": { + "type": "string", + "description": "Transaction hash to check status for", + "required": False, + }, + "tx_type": { + "type": "string", + "description": "Type of transaction (approve/swap)", + "enum": ["approve", "swap"], + "required": False, + }, + }, + }, + }, + }, + ] + + # ************* + # API CONFIG + # ************* + + INCH_URL = "https://api.1inch.dev/token" + QUOTE_URL = "https://api.1inch.dev/swap" + APIBASEURL = f"https://api.1inch.dev/swap/v6.0/" + HEADERS = { + "Authorization": "Bearer WvQuxaMYpPvDiiOL5RHWUm7OzOd20nt4", + "accept": "application/json", + } + + # ************* + # NETWORK CONFIG + # ************* + + WEB3RPCURL = { + "56": "https://bsc-dataseed.binance.org", + "42161": "https://arb1.arbitrum.io/rpc", + "137": "https://polygon-rpc.com", + "1": "https://eth.llamarpc.com/", + "10": "https://mainnet.optimism.io", + "8453": "https://mainnet.base.org", + } + + NATIVE_TOKENS = { + "137": "MATIC", + "56": "BNB", + "1": "ETH", + "42161": "ETH", + "10": "ETH", + "8453": "ETH", + } + + # ************* + # CONTRACT CONFIG + # ************* + + ERC20_ABI = [ + { + "constant": True, + "inputs": [], + "name": "decimals", + "outputs": [{"name": "", "type": "uint8"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + { + "constant": True, + "inputs": [{"name": "_owner", "type": "address"}], + "name": "balanceOf", + "outputs": [{"name": "balance", "type": "uint256"}], + "payable": False, + "stateMutability": "view", + "type": "function", + }, + ] + + INCH_NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" diff --git a/submodules/agents/src/services/agents/token_swap/models.py b/submodules/agents/src/services/agents/token_swap/models.py new file mode 100644 index 00000000..f175ac81 --- /dev/null +++ b/submodules/agents/src/services/agents/token_swap/models.py @@ -0,0 +1,136 @@ +from pydantic import BaseModel, Field +from typing import Optional, Dict, Any, List +from enum import Enum + + +class TokenInfo(BaseModel): + """Model for token information.""" + + symbol: str + address: str + + +class TransactionStatus(str, Enum): + """Status of a blockchain transaction.""" + + PENDING = "pending" + SUBMITTED = "submitted" + CONFIRMED = "confirmed" + FAILED = "failed" + REJECTED = "rejected" + + +class TransactionResponse(BaseModel): + """Model for transaction response.""" + + success: bool = True + status: TransactionStatus + tx_hash: Optional[str] = None + from_address: str + to_address: str + value: Optional[float] = None + token_address: Optional[str] = None + token_symbol: Optional[str] = None + error_message: Optional[str] = None + network_id: int + gas_used: Optional[int] = None + gas_price: Optional[int] = None + metadata: Optional[Dict[str, Any]] = None + + @property + def formatted_response(self) -> str: + """Format transaction response for display.""" + if not self.success: + return f"Transaction failed: {self.error_message}" + + response = f"# Transaction {self.status.value.title()}\n\n" + + if self.tx_hash: + response += f"Transaction Hash: `{self.tx_hash}`\n\n" + + response += f"From: `{self.from_address}`\n" + response += f"To: `{self.to_address}`\n" + + if self.token_address and self.token_symbol: + response += f"Token: {self.token_symbol} (`{self.token_address}`)\n" + + if self.value is not None: + response += f"Value: {self.value}\n" + + if self.gas_used is not None and self.gas_price is not None: + gas_cost = self.gas_used * self.gas_price / 1e18 + response += f"Gas Used: {self.gas_used}\n" + response += f"Gas Price: {self.gas_price} wei\n" + response += f"Gas Cost: {gas_cost:.8f} ETH\n" + + return response + + +class SwapRoute(BaseModel): + """Model for swap route information.""" + + name: str + part: float + from_token_address: str + to_token_address: str + + +class SwapQuoteResponse(BaseModel): + """Model for swap quote response.""" + + success: bool = True + src: str + src_address: str + src_amount: float + dst: str + dst_address: str + dst_amount: float + approve_tx_cb: str = "/approve" + swap_tx_cb: str = "/swap" + estimated_gas: Optional[int] = None + routes: Optional[List[SwapRoute]] = None + error_message: Optional[str] = None + + @property + def formatted_response(self) -> str: + """Format swap quote for response.""" + if not self.success: + return f"Failed to generate swap quote: {self.error_message}" + + response = ( + f"Swap Quote: {self.src_amount} {self.src}\n" + f"for {self.dst_amount} {self.dst}\n" + f"Rate: 1 {self.src} = {self.dst_amount / self.src_amount:.2f} {self.dst}\n" + ) + + if self.estimated_gas: + response += f"## Estimated Gas\n{self.estimated_gas} units\n\n" + + if self.routes and len(self.routes) > 0: + response += f"## Routes\n" + for idx, route in enumerate(self.routes): + response += f"{idx+1}. {route.name} ({route.part * 100:.1f}%)\n" + + return response + + +class ApproveTransactionRequest(BaseModel): + """Model for approve transaction request.""" + + token_address: str + spender_address: str + amount: int + wallet_address: str + chain_id: int + + +class SwapTransactionRequest(BaseModel): + """Model for swap transaction request.""" + + from_token_address: str + to_token_address: str + amount: int + from_address: str + slippage: float = 0.5 + chain_id: int + receiver_address: Optional[str] = None diff --git a/submodules/moragents_dockers/agents/src/agents/token_swap/routes.py b/submodules/agents/src/services/agents/token_swap/routes.py old mode 100644 new mode 100755 similarity index 95% rename from submodules/moragents_dockers/agents/src/agents/token_swap/routes.py rename to submodules/agents/src/services/agents/token_swap/routes.py index 31057471..aff5aea1 --- a/submodules/moragents_dockers/agents/src/agents/token_swap/routes.py +++ b/submodules/agents/src/services/agents/token_swap/routes.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, Request from fastapi.responses import JSONResponse -from src.stores import chat_manager_instance, agent_manager_instance +from stores import agent_manager_instance logger = logging.getLogger(__name__) @@ -22,7 +22,6 @@ async def tx_status(request: Request): ) response = await swap_agent.tx_status(request) - chat_manager_instance.add_response(response.dict(), "swap") return response except Exception as e: logger.error(f"Failed to check tx status: {str(e)}") diff --git a/submodules/agents/src/services/agents/token_swap/tests/benchmarks/test_token_swap_agent.py b/submodules/agents/src/services/agents/token_swap/tests/benchmarks/test_token_swap_agent.py new file mode 100755 index 00000000..24acdcb6 --- /dev/null +++ b/submodules/agents/src/services/agents/token_swap/tests/benchmarks/test_token_swap_agent.py @@ -0,0 +1,122 @@ +import logging +import pytest +from unittest.mock import patch, Mock +from typing import Dict, Any + +from services.agents.token_swap.agent import TokenSwapAgent +from models.service.chat_models import AgentResponse, ChatRequest +from services.agents.token_swap.tools import InsufficientFundsError, TokenNotFoundError, SwapNotPossibleError +from services.agents.token_swap.utils.tool_types import SwapToolType +from services.agents.token_swap.models import SwapQuoteResponse, TransactionResponse + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def token_swap_agent(llm): + config: Dict[str, Any] = { + "name": "token_swap", + "description": "Agent for handling token swaps", + "APIBASEURL": "https://api.1inch.io/v5.0/", + } + return TokenSwapAgent(config, llm) + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_swap_success(token_swap_agent, make_chat_request): + request = make_chat_request( + content="Swap 1 ETH for USDT", agent_name="token_swap", chain_id="1", wallet_address="0x123" + ) + + mock_swap_result = SwapQuoteResponse( + fromToken={"symbol": "ETH"}, + toToken={"symbol": "USDT"}, + toAmount="1000000000000000000", + formatted_response="Successfully quoted swap of 1 ETH for USDT", + ) + + with patch("services.agents.token_swap.tools.swap_coins") as mock_swap: + mock_swap.return_value = mock_swap_result + + response = await token_swap_agent._execute_tool( + SwapToolType.SWAP_TOKENS.value, {"source_token": "ETH", "destination_token": "USDT", "amount": "1.0"} + ) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert response.action_type == "swap" + assert response.metadata == mock_swap_result.model_dump() + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_swap_insufficient_funds(token_swap_agent): + with patch("services.agents.token_swap.tools.swap_coins") as mock_swap: + mock_swap.side_effect = InsufficientFundsError("Insufficient funds") + + response = await token_swap_agent._execute_tool( + SwapToolType.SWAP_TOKENS.value, {"source_token": "ETH", "destination_token": "USDT", "amount": "1000.0"} + ) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Insufficient funds" in response.error_message + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_swap_token_not_found(token_swap_agent): + with patch("services.agents.token_swap.tools.swap_coins") as mock_swap: + mock_swap.side_effect = TokenNotFoundError("Token not found") + + response = await token_swap_agent._execute_tool( + SwapToolType.SWAP_TOKENS.value, {"source_token": "INVALID", "destination_token": "USDT", "amount": "1.0"} + ) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Token not found" in response.error_message + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_get_transaction_status(token_swap_agent): + mock_tx_response = TransactionResponse( + status="success", tx_hash="0x123", tx_type="swap", formatted_response="Transaction 0x123 was successful" + ) + + with patch("services.agents.token_swap.tools.get_transaction_status") as mock_status: + mock_status.return_value = mock_tx_response + + response = await token_swap_agent._execute_tool( + SwapToolType.GET_TRANSACTION_STATUS.value, + {"tx_hash": "0x123", "chain_id": "1", "wallet_address": "0xwallet"}, + ) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert response.action_type == "transaction_status" + assert response.metadata == mock_tx_response.model_dump() + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_missing_parameters(token_swap_agent): + response = await token_swap_agent._execute_tool( + SwapToolType.SWAP_TOKENS.value, {"source_token": "ETH"} # Missing destination_token and amount + ) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "needs_info" + assert "Please provide the following information" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_unknown_tool(token_swap_agent): + response = await token_swap_agent._execute_tool("unknown_tool", {}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Unknown tool" in response.error_message diff --git a/submodules/agents/src/services/agents/token_swap/tools.py b/submodules/agents/src/services/agents/token_swap/tools.py new file mode 100755 index 00000000..c3f3de3b --- /dev/null +++ b/submodules/agents/src/services/agents/token_swap/tools.py @@ -0,0 +1,231 @@ +import logging +import time +from typing import Optional + +from web3 import Web3 + +from .config import Config +from .utils.exceptions import SwapNotPossibleError, TokenNotFoundError, InsufficientFundsError +from .models import SwapQuoteResponse, TransactionResponse, TransactionStatus +from .utils.helpers import ( + validate_token_pair, + get_swap_quote, + convert_to_smallest_unit, + convert_to_readable_unit, +) + +logger = logging.getLogger(__name__) + + +async def swap_coins( + source_token: str, destination_token: str, amount: float, chain_id: int, wallet_address: str +) -> SwapQuoteResponse: + """ + Get a quote for swapping tokens. + + Args: + source_token: Symbol of source token + destination_token: Symbol of destination token + amount: Amount of source token to swap + chain_id: Blockchain network ID + wallet_address: User's wallet address + + Returns: + SwapQuoteResponse object with swap details + """ + logger.info( + f"Swapping {amount} {source_token} to {destination_token} on chain {chain_id} for wallet {wallet_address}" + ) + + try: + # Validate inputs + if not source_token or not destination_token: + raise ValueError("Source and destination tokens must be provided") + + if amount <= 0: + raise ValueError("Swap amount must be greater than zero") + + if not wallet_address: + raise ValueError("Wallet address must be provided") + + if not chain_id: + raise ValueError("Chain ID must be provided") + + # Initialize Web3 with appropriate provider + if str(chain_id) not in Config.WEB3RPCURL: + raise SwapNotPossibleError(f"Unsupported chain ID: {chain_id}") + + web3 = Web3(Web3.HTTPProvider(Config.WEB3RPCURL[str(chain_id)])) + + if not web3.is_connected(): + raise SwapNotPossibleError(f"Cannot connect to RPC for chain ID: {chain_id}") + + # Validate the swap and get token addresses and symbols + source_token_address, source_token_symbol, destination_token_address, destination_token_symbol = ( + await validate_token_pair(web3, source_token, destination_token, chain_id, amount, wallet_address) + ) + + # Get quote from exchange API + time.sleep(1) # Rate limiting + source_amount_in_wei = convert_to_smallest_unit(web3, amount, source_token_address) + + quote_result = await get_swap_quote( + source_token_address, destination_token_address, source_amount_in_wei, chain_id + ) + + if not quote_result: + raise SwapNotPossibleError( + f"Failed to generate a quote for {source_token_symbol} to {destination_token_symbol}. " + "Please ensure you're on the correct network." + ) + + # Extract estimated destination amount from quote + destination_amount_in_wei = int(quote_result["dstAmount"]) + destination_amount = convert_to_readable_unit(web3, destination_amount_in_wei, destination_token_address) + + # Create successful response + return SwapQuoteResponse( + success=True, + src=source_token_symbol, + src_address=source_token_address, + src_amount=amount, + dst=destination_token_symbol, + dst_address=destination_token_address, + dst_amount=float(destination_amount), + approve_tx_cb="/approve", + swap_tx_cb="/swap", + estimated_gas=quote_result.get("estimatedGas"), + ) + except (TokenNotFoundError, InsufficientFundsError, SwapNotPossibleError, ValueError) as e: + # Convert ValueError to SwapNotPossibleError for consistency + if isinstance(e, ValueError): + error = SwapNotPossibleError(str(e)) + else: + error = e + + # Let expected errors propagate to be handled by the agent + logger.error(f"Swap error: {str(error)}", exc_info=True) + raise error + except Exception as e: + # Handle unexpected errors + logger.error(f"Unexpected error during swap: {str(e)}", exc_info=True) + return SwapQuoteResponse( + success=False, + src=source_token, + src_address="", + src_amount=amount, + dst=destination_token, + dst_address="", + dst_amount=0.0, + error_message=f"Swap failed: {str(e)}", + ) + + +async def get_transaction_status(tx_hash: str, chain_id: int, wallet_address: str) -> TransactionResponse: + """ + Get the status of a transaction. + + Args: + tx_hash: Transaction hash + chain_id: Blockchain network ID + wallet_address: User's wallet address + + Returns: + TransactionResponse object with transaction details + """ + logger.info(f"Getting status for transaction {tx_hash} on chain {chain_id}") + + try: + # Validate inputs + if not tx_hash: + raise ValueError("Transaction hash must be provided") + + if not chain_id: + raise ValueError("Chain ID must be provided") + + if not wallet_address: + raise ValueError("Wallet address must be provided") + + # Check if chain is supported + if str(chain_id) not in Config.WEB3RPCURL: + raise ValueError(f"Unsupported chain ID: {chain_id}") + + # Initialize Web3 with appropriate provider + web3 = Web3(Web3.HTTPProvider(Config.WEB3RPCURL[str(chain_id)])) + + if not web3.is_connected(): + raise ValueError(f"Cannot connect to RPC for chain ID: {chain_id}") + + # Get transaction receipt + try: + receipt = web3.eth.get_transaction_receipt(tx_hash) + except Exception as e: + logger.warning(f"Failed to get transaction receipt: {str(e)}") + receipt = None + + # Get transaction details + try: + tx = web3.eth.get_transaction(tx_hash) + except Exception as e: + logger.warning(f"Failed to get transaction: {str(e)}") + # If we can't get the transaction, return a minimal response + return TransactionResponse( + success=False, + status=TransactionStatus.PENDING, + tx_hash=tx_hash, + from_address=wallet_address, + to_address="", + network_id=chain_id, + error_message=f"Transaction not found: {str(e)}", + ) + + # Determine status + if receipt is None: + status = TransactionStatus.PENDING + elif receipt.status == 1: + status = TransactionStatus.CONFIRMED + else: + status = TransactionStatus.FAILED + + # Create response + return TransactionResponse( + success=True, + status=status, + tx_hash=tx_hash, + from_address=tx["from"], + to_address=tx["to"], + value=web3.from_wei(tx["value"], "ether") if tx["value"] else None, + network_id=chain_id, + gas_used=receipt.gasUsed if receipt else None, + gas_price=tx["gasPrice"], + metadata={ + "block_number": receipt.blockNumber if receipt else None, + "block_hash": receipt.blockHash.hex() if receipt and receipt.blockHash else None, + "confirmations": web3.eth.block_number - receipt.blockNumber if receipt and receipt.blockNumber else 0, + }, + ) + + except ValueError as e: + # Handle validation errors + logger.error(f"Validation error in transaction status: {str(e)}") + return TransactionResponse( + success=False, + status=TransactionStatus.FAILED, + tx_hash=tx_hash if tx_hash else "", + from_address=wallet_address, + to_address="", + network_id=chain_id if chain_id else 0, + error_message=str(e), + ) + except Exception as e: + # Handle unexpected errors + logger.error(f"Error getting transaction status: {str(e)}", exc_info=True) + return TransactionResponse( + success=False, + status=TransactionStatus.FAILED, + tx_hash=tx_hash if tx_hash else "", + from_address=wallet_address if wallet_address else "", + to_address="", + network_id=chain_id if chain_id else 0, + error_message=f"Failed to get transaction status: {str(e)}", + ) diff --git a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/__init__.py b/submodules/agents/src/services/agents/token_swap/utils/__init__.py similarity index 100% rename from submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/__init__.py rename to submodules/agents/src/services/agents/token_swap/utils/__init__.py diff --git a/submodules/agents/src/services/agents/token_swap/utils/exceptions.py b/submodules/agents/src/services/agents/token_swap/utils/exceptions.py new file mode 100644 index 00000000..722024be --- /dev/null +++ b/submodules/agents/src/services/agents/token_swap/utils/exceptions.py @@ -0,0 +1,16 @@ +class InsufficientFundsError(Exception): + """Exception raised when user has insufficient funds for a swap.""" + + pass + + +class TokenNotFoundError(Exception): + """Exception raised when a token cannot be found.""" + + pass + + +class SwapNotPossibleError(Exception): + """Exception raised when a swap cannot be executed.""" + + pass diff --git a/submodules/agents/src/services/agents/token_swap/utils/helpers.py b/submodules/agents/src/services/agents/token_swap/utils/helpers.py new file mode 100644 index 00000000..482839c8 --- /dev/null +++ b/submodules/agents/src/services/agents/token_swap/utils/helpers.py @@ -0,0 +1,365 @@ +import logging +import time +from typing import Dict, List, Tuple, Optional, Any + +import requests +from web3 import Web3 +from web3.exceptions import ContractLogicError, BadFunctionCallOutput + +from ..config import Config +from .exceptions import TokenNotFoundError, InsufficientFundsError, SwapNotPossibleError +from ..models import TokenInfo, SwapRoute + +logger = logging.getLogger(__name__) + + +def get_api_headers() -> Dict[str, str]: + """Get headers for API requests with API key.""" + return { + "Authorization": f"Bearer {get_secret('1inchApiKey')}", + "accept": "application/json", + } + + +async def search_token( + query: str, + chain_id: int, + limit: int = 1, + ignore_listed: str = "false", +) -> List[TokenInfo]: + """ + Search for token by name or symbol. + + Args: + query: Token symbol or name to search for + chain_id: Blockchain network ID + limit: Maximum number of results to return + ignore_listed: Whether to ignore listed tokens + + Returns: + List of matching TokenInfo objects + + Raises: + TokenNotFoundError: If token cannot be found + """ + logger.info(f"Searching for token - Query: {query}, Chain ID: {chain_id}") + endpoint = f"/v1.2/{chain_id}/search" + params = {"query": str(query), "limit": str(limit), "ignore_listed": str(ignore_listed)} + + try: + response = requests.get(Config.INCH_URL + endpoint, params=params, headers=get_api_headers()) + logger.info(f"Search token response status: {response.status_code}") + + if response.status_code == 200: + result = response.json() + logger.info(f"Found token results: {result}") + + if not result: + raise TokenNotFoundError(f"Token '{query}' not found on chain {chain_id}.") + + return [TokenInfo(symbol=token["symbol"], address=token["address"]) for token in result] + else: + error_message = f"API error searching for token '{query}': {response.status_code}" + try: + error_details = response.json() + error_message += f" - {error_details.get('description', '')}" + except: + error_message += f" - {response.text}" + + logger.error(error_message) + raise TokenNotFoundError(error_message) + except requests.RequestException as e: + error_message = f"Network error searching for token '{query}': {str(e)}" + logger.error(error_message) + raise TokenNotFoundError(error_message) + + +def get_token_balance(web3: Web3, wallet_address: str, token_address: str, abi: List[Any]) -> int: + """ + Get the balance of a token for a given wallet address. + + Args: + web3: Web3 instance + wallet_address: Address of the wallet to check balance for + token_address: Address of the token (empty for native token) + abi: ABI for the token contract + + Returns: + Token balance in smallest units + + Raises: + InsufficientFundsError: If balance cannot be checked + """ + try: + # Ensure wallet address is valid + checksum_wallet = web3.to_checksum_address(wallet_address) + + if not token_address: # Native token (ETH, BNB, etc.) + return web3.eth.get_balance(checksum_wallet) + else: + # ERC-20 token + checksum_token = web3.to_checksum_address(token_address) + contract = web3.eth.contract(address=checksum_token, abi=abi) + return contract.functions.balanceOf(checksum_wallet).call() + except ValueError as e: + raise InsufficientFundsError(f"Invalid wallet or token address: {str(e)}") + except (ContractLogicError, BadFunctionCallOutput) as e: + raise InsufficientFundsError(f"Contract error checking balance: {str(e)}") + except Exception as e: + logger.error(f"Error getting token balance: {str(e)}", exc_info=True) + raise InsufficientFundsError(f"Failed to check token balance: {str(e)}") + + +def get_token_decimals(web3: Web3, token_address: str) -> int: + """ + Get the number of decimals for a token. + + Args: + web3: Web3 instance + token_address: Address of the token (empty for native token) + + Returns: + Number of decimals (defaults to 18 for native tokens or if error) + """ + try: + if not token_address: + return 18 # Native tokens (ETH, BNB, etc.) use 18 decimals + else: + checksum_address = web3.to_checksum_address(token_address) + contract = web3.eth.contract(address=checksum_address, abi=Config.ERC20_ABI) + return contract.functions.decimals().call() + except Exception as e: + logger.warning(f"Error getting token decimals for {token_address}: {str(e)}") + return 18 # Default to 18 decimals if we can't get it + + +def convert_to_smallest_unit(web3: Web3, amount: float, token_address: str) -> int: + """ + Convert a human-readable amount to the smallest token unit. + + Args: + web3: Web3 instance + amount: Amount in human-readable format + token_address: Address of the token (empty for native token) + + Returns: + Amount in smallest units (wei, satoshi, etc.) + + Raises: + SwapNotPossibleError: If conversion fails + """ + try: + decimals = get_token_decimals(web3, token_address) + # Convert using raw multiplication to avoid web3 unit issues + return int(amount * (10**decimals)) + except Exception as e: + logger.error(f"Error converting to smallest unit: {str(e)}", exc_info=True) + raise SwapNotPossibleError(f"Failed to convert token amount: {str(e)}") + + +def convert_to_readable_unit(web3: Web3, smallest_unit_amount: int, token_address: str) -> float: + """ + Convert from smallest unit to human-readable amount. + + Args: + web3: Web3 instance + smallest_unit_amount: Amount in smallest units + token_address: Address of the token (empty for native token) + + Returns: + Human-readable amount + """ + try: + decimals = get_token_decimals(web3, token_address) + # Convert using raw division to avoid web3 unit issues + return float(smallest_unit_amount) / (10**decimals) + except Exception as e: + logger.error(f"Error converting to readable unit: {str(e)}", exc_info=True) + # Return 0 as fallback + return 0.0 + + +async def validate_token_pair( + web3: Web3, source_token: str, destination_token: str, chain_id: int, amount: float, wallet_address: str +) -> Tuple[str, str, str, str]: + """ + Validate that a swap can be performed and return token addresses and symbols. + + Args: + web3: Web3 instance + source_token: Symbol of source token + destination_token: Symbol of destination token + chain_id: Blockchain network ID + amount: Amount to swap + wallet_address: User's wallet address + + Returns: + Tuple of (source_token_address, source_token_symbol, destination_token_address, destination_token_symbol) + + Raises: + TokenNotFoundError: If either token cannot be found + InsufficientFundsError: If user has insufficient balance + SwapNotPossibleError: If swap validation fails for other reasons + """ + try: + chain_id_str = str(chain_id) + native_tokens = Config.NATIVE_TOKENS + + # Validate chain ID + if chain_id_str not in native_tokens: + raise SwapNotPossibleError(f"Unsupported chain ID: {chain_id}") + + native_token_symbol = native_tokens[chain_id_str] + + # Get source token information + if source_token.lower() == native_token_symbol.lower(): + # Source is native token (ETH, BNB, etc.) + source_token_info = TokenInfo( + symbol=native_token_symbol, + address=Config.INCH_NATIVE_TOKEN_ADDRESS, + ) + source_token_balance = get_token_balance(web3, wallet_address, "", Config.ERC20_ABI) + amount_in_wei = Web3.to_wei(amount, "ether") + else: + # Source is ERC-20 token + source_token_results = await search_token(source_token, chain_id) + source_token_info = source_token_results[0] + source_token_balance = get_token_balance(web3, wallet_address, source_token_info.address, Config.ERC20_ABI) + amount_in_wei = convert_to_smallest_unit(web3, amount, source_token_info.address) + + # Get destination token information + if destination_token.lower() == native_token_symbol.lower(): + # Destination is native token + destination_token_info = TokenInfo( + symbol=native_token_symbol, + address=Config.INCH_NATIVE_TOKEN_ADDRESS, + ) + else: + # Destination is ERC-20 token + destination_token_results = await search_token(destination_token, chain_id) + destination_token_info = destination_token_results[0] + + # Check if user has sufficient balance + if source_token_balance < amount_in_wei: + readable_balance = ( + web3.from_wei(source_token_balance, "ether") + if source_token.lower() == native_token_symbol.lower() + else convert_to_readable_unit(web3, source_token_balance, source_token_info.address) + ) + + raise InsufficientFundsError( + f"Insufficient funds to perform the swap. You have {readable_balance} {source_token_info.symbol} " + f"but need {amount} {source_token_info.symbol}." + ) + + # Successful validation + return ( + source_token_info.address, + source_token_info.symbol, + destination_token_info.address, + destination_token_info.symbol, + ) + + except (TokenNotFoundError, InsufficientFundsError): + # Let specific exceptions pass through + raise + except Exception as e: + logger.error(f"Error validating swap: {str(e)}", exc_info=True) + raise SwapNotPossibleError(f"Failed to validate swap: {str(e)}") + + +async def get_swap_quote( + source_token_address: str, destination_token_address: str, amount_in_wei: int, chain_id: int +) -> Optional[Dict[str, Any]]: + """ + Get a quote for swapping between tokens. + + Args: + source_token_address: Address of source token + destination_token_address: Address of destination token + amount_in_wei: Amount to swap in smallest units + chain_id: Blockchain network ID + + Returns: + Quote information dictionary or None if quote fails + + Raises: + SwapNotPossibleError: If quote cannot be obtained + """ + logger.info( + f"Getting quote - Source: {source_token_address}, " + f"Destination: {destination_token_address}, " + f"Amount: {amount_in_wei}, " + f"Chain ID: {chain_id}" + ) + + try: + endpoint = f"/v6.0/{chain_id}/quote" + params = { + "src": source_token_address, + "dst": destination_token_address, + "amount": str(amount_in_wei), # Convert to string to avoid potential overflow + } + + logger.debug(f"Quote request - URL: {Config.QUOTE_URL + endpoint}, Params: {params}") + + response = requests.get(Config.QUOTE_URL + endpoint, params=params, headers=get_api_headers()) + logger.info(f"Quote response status: {response.status_code}") + + if response.status_code == 200: + result = response.json() + logger.info(f"Quote received successfully") + return result + else: + error_message = f"Failed to get quote. Status code: {response.status_code}" + try: + error_details = response.json() + error_message += f" - {error_details.get('description', '')}" + except: + error_message += f" - {response.text}" + + logger.error(error_message) + raise SwapNotPossibleError(error_message) + + except requests.RequestException as e: + error_message = f"Network error getting quote: {str(e)}" + logger.error(error_message) + raise SwapNotPossibleError(error_message) + except Exception as e: + error_message = f"Unexpected error getting quote: {str(e)}" + logger.error(error_message, exc_info=True) + raise SwapNotPossibleError(error_message) + + +def parse_swap_routes(route_data: Dict[str, Any]) -> List[SwapRoute]: + """ + Parse routing information from a swap quote. + + Args: + route_data: Raw route data from API + + Returns: + List of SwapRoute objects + """ + routes = [] + + try: + if "protocols" in route_data: + for protocol_group in route_data["protocols"]: + for protocol in protocol_group: + if not protocol: + continue + + for route in protocol: + routes.append( + SwapRoute( + name=route.get("name", "Unknown"), + part=float(route.get("part", 0)), + from_token_address=route.get("fromTokenAddress", ""), + to_token_address=route.get("toTokenAddress", ""), + ) + ) + except Exception as e: + logger.warning(f"Error parsing swap routes: {str(e)}") + + return routes diff --git a/submodules/agents/src/services/agents/token_swap/utils/tool_types.py b/submodules/agents/src/services/agents/token_swap/utils/tool_types.py new file mode 100644 index 00000000..f6e70154 --- /dev/null +++ b/submodules/agents/src/services/agents/token_swap/utils/tool_types.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class SwapToolType(Enum): + """Tool types for the Token Swap Agent.""" + + SWAP_TOKENS = "swap_tokens" + GET_TRANSACTION_STATUS = "get_transaction_status" diff --git a/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/README.md b/submodules/agents/src/services/agents/tweet_sizzler/README.md old mode 100644 new mode 100755 similarity index 97% rename from submodules/moragents_dockers/agents/src/agents/tweet_sizzler/README.md rename to submodules/agents/src/services/agents/tweet_sizzler/README.md index 47db47d6..4f3c7ae2 --- a/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/README.md +++ b/submodules/agents/src/services/agents/tweet_sizzler/README.md @@ -20,10 +20,10 @@ A typical flow looks like this: Docker compose will build and run two containers. One will be for the agents, the other will be for the UI. -1. Ensure you're in the submodules/moragents_dockers folder +1. Ensure you're in the submodules folder ```sh - $ cd submodules/moragents_dockers + $ cd submodules ``` 2. Build Images and Launch Containers: diff --git a/submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/__init__.py b/submodules/agents/src/services/agents/tweet_sizzler/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/agents/tests/reward_check_agent_benchmarks/adapters/__init__.py rename to submodules/agents/src/services/agents/tweet_sizzler/__init__.py diff --git a/submodules/agents/src/services/agents/tweet_sizzler/agent.py b/submodules/agents/src/services/agents/tweet_sizzler/agent.py new file mode 100755 index 00000000..19502cb0 --- /dev/null +++ b/submodules/agents/src/services/agents/tweet_sizzler/agent.py @@ -0,0 +1,46 @@ +import logging +from typing import Dict, Any +from models.service.chat_models import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore +from .tools import generate_tweet +from .config import Config + +logger = logging.getLogger(__name__) + + +class TweetSizzlerAgent(AgentCore): + """Agent for generating and posting tweets.""" + + def __init__(self, config: Dict[str, Any], llm: Any): + super().__init__(config, llm) + self.tools_provided = Config.tools + self.tool_bound_llm = self.llm.bind_tools(self.tools_provided) + + async def _process_request(self, request: ChatRequest) -> AgentResponse: + """Process the validated chat request for tweet generation and posting.""" + try: + messages = [Config.system_message, *request.messages_for_llm] + result = self.tool_bound_llm.invoke(messages) + return await self._handle_llm_response(result) + + except Exception as e: + logger.error(f"Error processing request: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) + + async def _execute_tool(self, func_name: str, args: Dict[str, Any]) -> AgentResponse: + """Execute the appropriate tool based on function name.""" + try: + if func_name == "generate_tweet": + content = args.get("content") + if not content: + return AgentResponse.error(error_message="Please provide content for tweet generation") + + result = await generate_tweet(content) + return AgentResponse.success(content=result) + + else: + return AgentResponse.error(error_message=f"Unknown tool function: {func_name}") + + except Exception as e: + logger.error(f"Error executing tool {func_name}: {str(e)}", exc_info=True) + return AgentResponse.error(error_message=str(e)) diff --git a/submodules/agents/src/services/agents/tweet_sizzler/config.py b/submodules/agents/src/services/agents/tweet_sizzler/config.py new file mode 100755 index 00000000..c0cbbf2c --- /dev/null +++ b/submodules/agents/src/services/agents/tweet_sizzler/config.py @@ -0,0 +1,73 @@ +from models.service.agent_config import AgentConfig +from langchain.schema import SystemMessage + + +class Config: + """Configuration for Tweet Sizzler Agent.""" + + # ************* + # AGENT CONFIG + # ************* + + agent_config = AgentConfig( + path="services.agents.tweet_sizzler.agent", + class_name="TweetSizzlerAgent", + description="Creates engaging social media content", + delegator_description="Creates engaging cryptocurrency-related social media content, generates tweet drafts, " + "analyzes tweet performance metrics, and suggests hashtags or posting strategies. Use when users " + "want to create or optimize social media content related to crypto.", + human_readable_name="Tweet Generator", + command="tweet", + upload_required=False, + ) + + # ************* + # SYSTEM MESSAGE + # ************* + + system_message = SystemMessage( + content=( + "You are a witty and engaging tweet generator. Your task is to create spicy, " + "attention-grabbing tweets based on the user's prompt. It is CRUCIAL that you " + "keep the tweets strictly under 280 characters - this is a hard limit. Make the " + "tweets as engaging as possible while adhering to this character limit. Do not " + "surround your tweet with quotes or any other formatting. Do not preface it with " + "any text like 'here is your tweet'. Simply generate and output the tweet, ensuring " + "it is less than 280 characters long. Use newlines sparingly. Do not surrounded with " + "quotes or braces. Do not use any other formatting." + ) + ) + + # ************* + # TOOLS CONFIG + # ************* + + tools = [ + { + "name": "generate_tweet", + "description": "Generate an engaging tweet based on provided content", + "parameters": { + "type": "object", + "properties": {"content": {"type": "string", "description": "Content to base the tweet on"}}, + "required": ["content"], + }, + } + ] + + # ************* + # TWITTER CONFIG + # ************* + + TWITTER_API_VERSION = "2" + TWEET_MAX_LENGTH = 280 + + # LLM configuration + LLM_MAX_TOKENS = 280 + LLM_TEMPERATURE = 0.7 + + # Error messages + ERROR_NO_TWEET_CONTENT = "No tweet content provided" + ERROR_TWITTER_CLIENT_NOT_INITIALIZED = "Twitter client not initialized. Please set X API credentials first." + ERROR_MISSING_API_CREDENTIALS = "Missing required X API credentials" + ERROR_INVALID_ACTION = "Invalid action" + ERROR_MISSING_PARAMETERS = "Missing required parameters" diff --git a/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/routes.py b/submodules/agents/src/services/agents/tweet_sizzler/routes.py old mode 100644 new mode 100755 similarity index 51% rename from submodules/moragents_dockers/agents/src/agents/tweet_sizzler/routes.py rename to submodules/agents/src/services/agents/tweet_sizzler/routes.py index 880c0653..ac171161 --- a/submodules/moragents_dockers/agents/src/agents/tweet_sizzler/routes.py +++ b/submodules/agents/src/services/agents/tweet_sizzler/routes.py @@ -1,8 +1,10 @@ import logging -from fastapi import APIRouter, Request +import tweepy + +from fastapi import APIRouter from fastapi.responses import JSONResponse -from src.models.core import AgentResponse -from src.stores import chat_manager_instance, agent_manager_instance, key_manager_instance +from pydantic import BaseModel +from stores import agent_manager_instance logger = logging.getLogger(__name__) @@ -23,7 +25,6 @@ async def regenerate_tweet(): ) response = tweet_agent.generate_tweet() - chat_manager_instance.add_message(response) return response except Exception as e: logger.error(f"Failed to regenerate tweet: {str(e)}") @@ -33,31 +34,36 @@ async def regenerate_tweet(): ) +class TweetRequest(BaseModel): + """Request body for posting a tweet""" + + post_content: str + api_key: str + api_secret: str + access_token: str + access_token_secret: str + + @router.post("/post") -async def post_tweet(request: Request): +async def post_tweet(request: TweetRequest): """Post a tweet""" logger.info("Received post tweet request") try: - tweet_agent = agent_manager_instance.get_agent("tweet sizzler") - if not tweet_agent: - return JSONResponse( - status_code=400, - content={"status": "error", "message": "Tweet sizzler agent not found"}, - ) - request_body = await request.json() - tweet_response = await tweet_agent.post_tweet(request_body["post_content"]) - if "error" in tweet_response: - agent_response = AgentResponse.error(error_message=tweet_response["error"]) - else: - agent_response = AgentResponse.success( - content=f"Tweet posted successfully: {tweet_response['tweet']}", - metadata={"tweet_id": tweet_response["tweet_id"]}, - ) - chat_manager_instance.add_response(agent_response.dict(), "tweet sizzler") + client = tweepy.Client( + consumer_key=request.api_key, + consumer_secret=request.api_secret, + access_token=request.access_token, + access_token_secret=request.access_token_secret, + ) + + response = client.create_tweet(text=request.post_content) + logger.info(f"Tweet posted successfully: {response}") + return JSONResponse( status_code=200, - content={"status": "success", "tweet": tweet_response["tweet"], "tweet_id": tweet_response["tweet_id"]}, + content={"status": "success", "tweet": response.data["text"], "tweet_id": response.data["id"]}, ) + except Exception as e: logger.error(f"Failed to post tweet: {str(e)}") return JSONResponse( diff --git a/submodules/agents/src/services/agents/tweet_sizzler/tests/benchmarks/test_tweet_sizzler_agent.py b/submodules/agents/src/services/agents/tweet_sizzler/tests/benchmarks/test_tweet_sizzler_agent.py new file mode 100755 index 00000000..ea5f285a --- /dev/null +++ b/submodules/agents/src/services/agents/tweet_sizzler/tests/benchmarks/test_tweet_sizzler_agent.py @@ -0,0 +1,94 @@ +import pytest +import logging +from unittest.mock import patch +from typing import Dict, Any + +from services.agents.tweet_sizzler.agent import TweetSizzlerAgent +from models.service.chat_models import ChatRequest, AgentResponse +from models.service.agent_core import AgentCore +from services.agents.tweet_sizzler.tools import generate_tweet +from services.agents.tweet_sizzler.config import Config + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def tweet_sizzler_agent(llm): + config: Dict[str, Any] = {"name": "tweet_sizzler", "description": "Agent for generating and posting tweets"} + return TweetSizzlerAgent(config, llm) + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_generate_tweet_success(tweet_sizzler_agent, make_chat_request): + request = make_chat_request(content="Write a tweet about AI") + + with patch.object(tweet_sizzler_agent.tool_bound_llm, "ainvoke") as mock_invoke: + mock_invoke.return_value = { + "tool_calls": [ + { + "function": { + "name": "generate_tweet", + "arguments": {"content": "AI is transforming our world! #AI #Technology"}, + } + } + ] + } + + with patch("services.agents.tweet_sizzler.tools.generate_tweet") as mock_generate: + mock_generate.return_value = "AI is transforming our world! #AI #Technology" + response = await tweet_sizzler_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert "AI is transforming" in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_execute_tool_generate_tweet(tweet_sizzler_agent): + args: Dict[str, Any] = {"content": "Test tweet content"} + + with patch("services.agents.tweet_sizzler.tools.generate_tweet") as mock_generate: + mock_generate.return_value = ( + "Just testing the waters with this tweet, stay tuned for more excitement to come #testing" + ) + response = await tweet_sizzler_agent._execute_tool("generate_tweet", args) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "success" + assert mock_generate.return_value in response.content + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_execute_tool_missing_content(tweet_sizzler_agent): + args: Dict[str, Any] = {} + response = await tweet_sizzler_agent._execute_tool("generate_tweet", args) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Please provide content" in response.error_message + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_execute_unknown_tool(tweet_sizzler_agent): + response = await tweet_sizzler_agent._execute_tool("unknown_tool", {}) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "Unknown tool function" in response.error_message + + +@pytest.mark.benchmark +@pytest.mark.asyncio +async def test_tweet_generation_error(tweet_sizzler_agent, make_chat_request): + request = make_chat_request(content="Write a tweet") + + with patch.object(tweet_sizzler_agent.tool_bound_llm, "ainvoke", side_effect=Exception("LLM Error")): + response = await tweet_sizzler_agent._process_request(request) + + assert isinstance(response, AgentResponse) + assert response.response_type.value == "error" + assert "LLM Error" in response.error_message diff --git a/submodules/agents/src/services/agents/tweet_sizzler/tools.py b/submodules/agents/src/services/agents/tweet_sizzler/tools.py new file mode 100644 index 00000000..c7a8c3bd --- /dev/null +++ b/submodules/agents/src/services/agents/tweet_sizzler/tools.py @@ -0,0 +1,40 @@ +import logging +from typing import Dict +from langchain.schema import HumanMessage +from services.agents.tweet_sizzler.config import Config +from config import LLM_AGENT + +logger = logging.getLogger(__name__) + + +async def generate_tweet(content: str) -> str: + """ + Generate a tweet based on the provided content. + + Args: + content (str): The content to generate a tweet about + + Returns: + Dict[str, str]: Dictionary containing the generated tweet content + """ + try: + result = LLM_AGENT.invoke( + [ + Config.system_message, + HumanMessage(content=f"Generate a tweet for: {content}"), + ] + ) + + tweet = result.content.strip() + tweet = " ".join(tweet.split()) # Normalize whitespace + + # Remove any dictionary-like formatting + if tweet.startswith("{") and tweet.endswith("}"): + tweet = tweet.strip("{}").split(":", 1)[-1].strip().strip('"') + + logger.info(f"Tweet generated successfully: {tweet}") + return tweet + + except Exception as e: + logger.error(f"Error generating tweet: {str(e)}") + return str(e) diff --git a/utils/__init__.py b/submodules/agents/src/services/delegator/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from utils/__init__.py rename to submodules/agents/src/services/delegator/__init__.py diff --git a/submodules/agents/src/services/delegator/delegator.py b/submodules/agents/src/services/delegator/delegator.py new file mode 100755 index 00000000..97b03a09 --- /dev/null +++ b/submodules/agents/src/services/delegator/delegator.py @@ -0,0 +1,116 @@ +import logging +import json +import importlib + +from typing import List, Optional, Tuple, Any + +from langchain.schema import BaseMessage, SystemMessage +from langchain.output_parsers import PydanticOutputParser +from pydantic import BaseModel, Field +from stores import agent_manager_instance +from models.service.chat_models import ChatRequest, AgentResponse, ResponseType +from config import load_agent_config, LLM_AGENT, LLM_DELEGATOR +from .system_prompt import get_system_prompt + +logger = logging.getLogger(__name__) + + +class RankAgentsOutput(BaseModel): + """JSON schema for agent ranking output""" + + agents: List[str] = Field(..., description="List of up to 3 agent names, ordered by relevance") + + +class Delegator: + def __init__(self, llm: Any): + self.llm = LLM_DELEGATOR + self.attempted_agents: set[str] = set() + self.selected_agents_for_request: list[str] = [] + self.parser = PydanticOutputParser(pydantic_object=RankAgentsOutput) + + async def _try_agent(self, agent_name: str, chat_request: ChatRequest) -> Optional[AgentResponse]: + """Attempt to use a single agent, with error handling""" + try: + agent_config = load_agent_config(agent_name) + if not agent_config: + logger.error(f"Could not load config for agent {agent_name}") + return None + + module = importlib.import_module(agent_config["path"]) + agent_class = getattr(module, agent_config["class_name"]) + agent = agent_class(agent_config, LLM_AGENT) + + result: AgentResponse = await agent.chat(chat_request) + if result.response_type == ResponseType.ERROR: + logger.warning(f"Agent {agent_name} returned error response. You should probably look into this") + logger.error(f"Error message: {result.error_message}") + + return result + + except Exception as e: + logger.error(f"Error using agent {agent_name}: {str(e)}") + return None + + def get_delegator_response(self, prompt: ChatRequest, max_retries: int = 3) -> List[str]: + """Get ranked list of appropriate agents with retry logic""" + available_agents = agent_manager_instance.get_available_agents() + logger.info(f"Available agents: {available_agents}") + + if not available_agents: + if "default" not in self.attempted_agents: + return ["default"] + raise ValueError("No remaining agents available") + + system_prompt = get_system_prompt(available_agents) + + # Build message history from chat history + messages: List[BaseMessage] = [SystemMessage(content=system_prompt)] + messages.extend(prompt.messages_for_llm) + + for attempt in range(max_retries): + try: + response = self.llm(messages) + try: + # First try parsing as JSON directly + json_response = json.loads(response.content) + if isinstance(json_response, dict) and "agents" in json_response: + agents = json_response["agents"] + if isinstance(agents, list) and all(isinstance(a, str) for a in agents): + self.selected_agents_for_request = agents + logger.info(f"Selected agents (attempt {attempt+1}): {agents}") + return agents + except json.JSONDecodeError: + pass + + # Fallback to pydantic parser + parsed = self.parser.parse(response.content) + self.selected_agents_for_request = parsed.agents + logger.info(f"Selected agents (attempt {attempt+1}): {parsed.agents}") + return parsed.agents + + except Exception as e: + logger.warning(f"Attempt {attempt+1} failed: {str(e)}") + if attempt == max_retries - 1: + logger.error("All retries failed") + return ["default"] + return [] + + async def delegate_chat(self, chat_request: ChatRequest) -> Tuple[Optional[str], AgentResponse]: + """Delegate chat to ranked agents with fallback""" + try: + ranked_agents = self.get_delegator_response(chat_request) + + for agent_name in ranked_agents: + self.attempted_agents.add(agent_name) + logger.info(f"Attempting agent: {agent_name}") + + result = await self._try_agent(agent_name, chat_request) + if result: + logger.info(f"Successfully used agent: {agent_name}") + return agent_name, result + + return None, AgentResponse.error(error_message="All agents have been attempted without success") + + except ValueError as ve: + logger.error(f"No available agents: {str(ve)}") + return None, AgentResponse.error(error_message="No suitable agents available for the request") diff --git a/submodules/agents/src/services/delegator/system_prompt.py b/submodules/agents/src/services/delegator/system_prompt.py new file mode 100644 index 00000000..28bd62ec --- /dev/null +++ b/submodules/agents/src/services/delegator/system_prompt.py @@ -0,0 +1,58 @@ +from typing import List, Dict + + +def get_system_prompt(available_agents: List[Dict]) -> str: + """Returns the system prompt for the delegator agent. + + Args: + available_agents: List of dictionaries containing agent info with 'name' and 'description' keys + + Returns: + str: The formatted system prompt + """ + # Build agent descriptions section + agent_descriptions = "\n".join(f"- {agent['name']}: {agent['delegator_description']}" for agent in available_agents) + + return f"""You are Morpheus, an intelligent agent delegator designed to route user queries to the most appropriate specialized agents. + +RESPONSE FORMAT REQUIREMENTS: +- You MUST respond ONLY in valid JSON format +- Your response must contain an "agents" array with 1-3 agent names +- Example valid response: {{"agents": ["agent1", "agent2"]}} + +AVAILABLE AGENTS: +{agent_descriptions} + +AGENT SELECTION PRIORITIES: + +1. QUERY SPECIFICITY: Prioritize agents that specialize in the exact task or data type requested by the user + +2. RECENCY REQUIREMENTS: If the query mentions current events or today's data, prioritize agents with real-time capabilities + +3. SECURITY CONCERNS: When users express any doubt about security, prioritize security-focused agents + +4. TASK COMPLEXITY: + - For technical operations β†’ specialized operation agents + - For data analysis β†’ data-focused agents + - For content creation β†’ creative agents + +5. CONTEXTUAL AWARENESS: + - Consider the full conversation history for context + - The most recent user message takes highest priority + - Identify implied needs even when not explicitly stated + +SELECTION PROCESS: +1. Carefully read the user's complete query, especially their most recent message +2. Identify the core intent and any secondary requests +3. Match each component to the most specialized agent +4. Verify selections against the priority framework +5. Arrange selected agents in order of relevance +6. Return ONLY the JSON response with selected agents + +When uncertain between multiple suitable agents, prioritize: +- Specialized agents over general ones +- Security over speed +- Accuracy over volume + +FALLBACK STRATEGY: +If no agent clearly matches the query, include "default" as the primary agent""" diff --git a/submodules/agents/src/services/secrets.py b/submodules/agents/src/services/secrets.py new file mode 100644 index 00000000..f1212add --- /dev/null +++ b/submodules/agents/src/services/secrets.py @@ -0,0 +1,46 @@ +# Use this code snippet in your app. +# If you need more information about configurations +# or implementing the sample code, visit the AWS docs: +# https://aws.amazon.com/developer/language/python/ + +import boto3 +import json +from botocore.exceptions import ClientError +from models.config.config import Config +from functools import lru_cache +from logs import setup_logging + +logger = setup_logging() + +CONF = Config.get_instance() + + +@lru_cache(maxsize=128) +def get_secret(secret_name: str, region_name: str = "us-west-1") -> str: + """Get a secret from AWS Secrets Manager""" + + # Check if secret exists in config first + if CONF.has(secret_name, "integrations"): + logger.info(f"Returning secret value for '{secret_name}' from config") + return CONF.get(secret_name, "integrations") + + # Create a Secrets Manager client + session = boto3.session.Session() + client = session.client(service_name="secretsmanager", region_name=region_name) + + try: + logger.info(f"Attempting to retrieve secret '{secret_name}' from region '{region_name}'") + get_secret_value_response = client.get_secret_value(SecretId=secret_name) + logger.info(f"Successfully retrieved secret '{secret_name}'") + except ClientError as e: + # For a list of exceptions thrown, see + # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html + logger.error(f"Failed to retrieve secret '{secret_name}': {str(e)}") + raise e + + secret = get_secret_value_response["SecretString"] + logger.info(f"Returning secret value for '{secret_name}'") + + # Parse the JSON string into a dictionary and get the value + secret_dict = json.loads(secret) + return secret_dict.get(secret_name) diff --git a/submodules/agents/src/services/vectorstore/__init__.py b/submodules/agents/src/services/vectorstore/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/submodules/agents/src/services/vectorstore/together_embeddings.py b/submodules/agents/src/services/vectorstore/together_embeddings.py new file mode 100644 index 00000000..8e996570 --- /dev/null +++ b/submodules/agents/src/services/vectorstore/together_embeddings.py @@ -0,0 +1,60 @@ +import logging +from typing import List, Optional + +from langchain_core.embeddings import Embeddings +from together import Together + +logger = logging.getLogger(__name__) + + +class TogetherEmbeddings(Embeddings): + """Wrapper around Together AI embedding models.""" + + def __init__( + self, + model_name: str = "togethercomputer/m2-bert-80M-8k-retrieval", + api_key: Optional[str] = None, + **kwargs, + ) -> None: + """Initialize the Together Embeddings client. + + Args: + model_name: Name of the embedding model to use. + api_key: Together API key (defaults to environment variable). + **kwargs: Additional keyword arguments to pass to the Together client. + """ + self.model_name = model_name + self.client = Together(api_key=api_key, **kwargs) + logger.info(f"Initialized TogetherEmbeddings with model: {model_name}") + + def embed_documents(self, texts: List[str]) -> List[List[float]]: + """Embed a list of documents. + + Args: + texts: The list of texts to embed. + + Returns: + List of embeddings, one for each text. + """ + try: + response = self.client.embeddings.create(model=self.model_name, input=texts) + return [item.embedding for item in response.data] + except Exception as e: + logger.error(f"Error generating embeddings: {str(e)}") + raise + + def embed_query(self, text: str) -> List[float]: + """Embed a single text query. + + Args: + text: The text to embed. + + Returns: + Embedding for the text. + """ + try: + response = self.client.embeddings.create(model=self.model_name, input=text) + return response.data[0].embedding + except Exception as e: + logger.error(f"Error generating query embedding: {str(e)}") + raise diff --git a/submodules/agents/src/services/vectorstore/vector_store_service.py b/submodules/agents/src/services/vectorstore/vector_store_service.py new file mode 100644 index 00000000..9fc54cd1 --- /dev/null +++ b/submodules/agents/src/services/vectorstore/vector_store_service.py @@ -0,0 +1,156 @@ +import logging +import os +from typing import Any, List + +from langchain_community.document_loaders import PyMuPDFLoader +from langchain_community.vectorstores import FAISS +from langchain_core.documents import Document +from langchain_text_splitters.character import RecursiveCharacterTextSplitter +from werkzeug.utils import secure_filename +from fastapi import UploadFile + +logger = logging.getLogger(__name__) + +UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads") + + +class VectorStoreService: + """Service for managing document embeddings and retrieval using vector stores.""" + + def __init__(self, embeddings: Any) -> None: + """Initialize the vector store service. + + Args: + embeddings: The embedding model to use for document vectorization. + """ + self.embeddings = embeddings + self.vector_store = None + self.retriever = None + self.text_splitter = RecursiveCharacterTextSplitter( + chunk_size=1024, + chunk_overlap=20, + length_function=len, + is_separator_regex=False, + ) + self.max_size = 5 * 1024 * 1024 # 5 MB + + # Ensure upload folder exists + if not os.path.exists(UPLOAD_FOLDER): + os.makedirs(UPLOAD_FOLDER, exist_ok=True) + + async def process_file(self, file: UploadFile) -> str: + """Process an uploaded file and add it to the vector store. + + Args: + file: The uploaded file to process. + + Returns: + A message indicating success or failure. + + Raises: + ValueError: If the file is too large or empty. + """ + if file.filename == "": + raise ValueError("Please select a file to upload") + + # Check file size to ensure it's less than max size + content = await file.read() + await file.seek(0) + if len(content) > self.max_size: + raise ValueError( + f"The file is too large. Please upload a file less than {self.max_size // (1024 * 1024)} MB" + ) + + filename = secure_filename(file.filename) + file_path = os.path.join(UPLOAD_FOLDER, filename) + + # Save the file + with open(file_path, "wb") as buffer: + buffer.write(content) + + # Process the document + documents = self._load_documents(file_path) + await self.add_documents(documents) + + return f"Successfully processed and indexed file: {filename}" + + def _load_documents(self, file_path: str) -> List[Document]: + """Load documents from a file path. + + Args: + file_path: The path to the file to load. + + Returns: + A list of Document objects. + """ + loader = PyMuPDFLoader(file_path) + return loader.load() + + async def add_documents(self, documents: List[Document]) -> None: + """Add documents to the vector store. + + Args: + documents: The documents to add to the vector store. + """ + split_documents = self.text_splitter.split_documents(documents) + + if self.vector_store is None: + # Initialize the vector store if it doesn't exist + self.vector_store = FAISS.from_documents(split_documents, self.embeddings) + else: + # Add documents to existing vector store + self.vector_store.add_documents(split_documents) + + # Update the retriever + self._update_retriever() + + def _update_retriever(self, k: int = 7) -> None: + """Update the retriever with the current vector store. + + Args: + k: The number of documents to retrieve. + """ + if self.vector_store is not None: + self.retriever = self.vector_store.as_retriever(search_kwargs={"k": k}) + + async def retrieve(self, query: str) -> List[Document]: + """Retrieve documents relevant to a query. + + Args: + query: The query to retrieve documents for. + + Returns: + A list of retrieved documents. + + Raises: + ValueError: If the retriever is not initialized. + """ + if self.retriever is None: + raise ValueError("Retriever not initialized. Please add documents first.") + + return self.retriever.invoke(query) + + def save(self, path: str) -> None: + """Save the vector store to disk. + + Args: + path: The path to save the vector store to. + """ + if self.vector_store is not None: + self.vector_store.save_local(path) + + @classmethod + def load(cls, path: str, embeddings: Any) -> "VectorStoreService": + """Load a vector store from disk. + + Args: + path: The path to load the vector store from. + embeddings: The embedding model to use. + + Returns: + A new VectorStoreService instance with the loaded vector store. + """ + service = cls(embeddings) + service.vector_store = FAISS.load_local(path, embeddings) + service._update_retriever() + return service diff --git a/submodules/agents/src/stores/__init__.py b/submodules/agents/src/stores/__init__.py new file mode 100755 index 00000000..c8c24cfe --- /dev/null +++ b/submodules/agents/src/stores/__init__.py @@ -0,0 +1,3 @@ +from stores.agent_manager import agent_manager_instance +from stores.wallet_manager import wallet_manager_instance +from stores.workflow_manager import workflow_manager_instance diff --git a/submodules/moragents_dockers/agents/src/stores/agent_manager.py b/submodules/agents/src/stores/agent_manager.py old mode 100644 new mode 100755 similarity index 85% rename from submodules/moragents_dockers/agents/src/stores/agent_manager.py rename to submodules/agents/src/stores/agent_manager.py index 842cdc8e..25c54d20 --- a/submodules/moragents_dockers/agents/src/stores/agent_manager.py +++ b/submodules/agents/src/stores/agent_manager.py @@ -1,13 +1,11 @@ import importlib -import logging from typing import Any, Dict, List, Optional, Tuple from langchain_ollama import ChatOllama -from langchain_community.embeddings import OllamaEmbeddings -from src.config import Config +from config import load_agent_configs, setup_logging -logger = logging.getLogger(__name__) +logger = setup_logging() class AgentManager: @@ -20,7 +18,6 @@ class AgentManager: config (Dict): Configuration dictionary for agents agents (Dict[str, Any]): Dictionary of loaded agent instances llm (ChatOllama): Language model instance - embeddings (OllamaEmbeddings): Embeddings model instance """ def __init__(self, config: Dict) -> None: @@ -35,11 +32,11 @@ def __init__(self, config: Dict) -> None: self.config = config self.agents: Dict[str, Any] = {} self.llm: Optional[ChatOllama] = None - self.embeddings: Optional[OllamaEmbeddings] = None # Select first 6 agents by default - self.set_selected_agents([agent["name"] for agent in config["agents"][:6]]) - logger.info(f"AgentManager initialized with {len(self.selected_agents)} default agents") + self.set_selected_agents([agent["name"] for agent in config]) + # self.load_all_agents(LLM, EMBEDDINGS) + logger.info(f"AgentManager initialized with {len(self.selected_agents)} agents") def _load_agent(self, agent_config: Dict) -> bool: """ @@ -54,23 +51,21 @@ def _load_agent(self, agent_config: Dict) -> bool: try: module = importlib.import_module(agent_config["path"]) agent_class = getattr(module, agent_config["class"]) - self.agents[agent_config["name"]] = agent_class(agent_config, self.llm, self.embeddings) + self.agents[agent_config["name"]] = agent_class(agent_config, self.llm) logger.info(f"Loaded agent: {agent_config['name']}") return True except Exception as e: logger.error(f"Failed to load agent {agent_config['name']}: {str(e)}") return False - def load_all_agents(self, llm: ChatOllama, embeddings: OllamaEmbeddings) -> None: + def load_all_agents(self, llm: ChatOllama) -> None: """ Load all available agents with the given language and embedding models. Args: llm (ChatOllama): Language model instance - embeddings (OllamaEmbeddings): Embeddings model instance """ self.llm = llm - self.embeddings = embeddings for agent_config in self.get_available_agents(): self._load_agent(agent_config) logger.info(f"Loaded {len(self.agents)} agents") @@ -100,14 +95,14 @@ def clear_active_agent(self) -> None: """Clear the currently active agent.""" self.active_agent = None - def get_available_agents(self) -> List[Dict]: + def get_available_agents(self): """ Get list of all available agents from config. Returns: List[Dict]: List of agent configurations """ - return self.config["agents"] + return self.config def get_selected_agents(self) -> List[str]: """ @@ -128,7 +123,7 @@ def set_selected_agents(self, agent_names: List[str]) -> None: Raises: ValueError: If any agent name is invalid """ - valid_names = {agent["name"] for agent in self.config["agents"]} + valid_names = {agent["name"] for agent in self.config} invalid_names = [name for name in agent_names if name not in valid_names] if invalid_names: @@ -149,7 +144,7 @@ def get_agent_config(self, agent_name: str) -> Optional[Dict]: Returns: Optional[Dict]: Agent configuration if found, None otherwise """ - return next((agent for agent in self.config["agents"] if agent["name"] == agent_name), None) + return next((agent for agent in self.config if agent["name"] == agent_name), None) def get_agent(self, agent_name: str) -> Optional[Any]: """ @@ -173,7 +168,7 @@ def get_agent_by_command(self, command: str) -> Optional[str]: Returns: Optional[str]: Agent name if found, None otherwise """ - for agent in self.config["agents"]: + for agent in self.config: if agent["command"] == command: return agent["name"] return None @@ -200,4 +195,6 @@ def parse_command(self, message: str) -> Tuple[Optional[str], str]: # Create an instance to act as a singleton store -agent_manager_instance = AgentManager(Config.AGENTS_CONFIG) +agent_configs = load_agent_configs() +logger.info(f"Loaded {len(agent_configs)} agents") +agent_manager_instance = AgentManager(agent_configs) diff --git a/submodules/moragents_dockers/agents/src/stores/wallet_manager.py b/submodules/agents/src/stores/wallet_manager.py old mode 100644 new mode 100755 similarity index 97% rename from submodules/moragents_dockers/agents/src/stores/wallet_manager.py rename to submodules/agents/src/stores/wallet_manager.py index 94d17d01..d4bfbaaf --- a/submodules/moragents_dockers/agents/src/stores/wallet_manager.py +++ b/submodules/agents/src/stores/wallet_manager.py @@ -3,7 +3,6 @@ from typing import Dict, Optional from cdp import Cdp, Wallet, WalletData from pathlib import Path -from src.stores.key_manager import key_manager_instance logger = logging.getLogger(__name__) @@ -50,11 +49,11 @@ def configure_cdp_client(self) -> bool: try: if self.cdp_client: return True - if not key_manager_instance.has_coinbase_keys(): - logger.error("CDP credentials not found") - return False + # if not key_manager_instance.has_coinbase_keys(): + # logger.error("CDP credentials not found") + # return False - keys = key_manager_instance.get_coinbase_keys() + # keys = key_manager_instance.get_coinbase_keys() logger.info("Configuring CDP client with stored credentials") self.cdp_client = Cdp.configure(keys.cdp_api_key, keys.cdp_api_secret) diff --git a/submodules/moragents_dockers/agents/src/stores/workflow_manager.py b/submodules/agents/src/stores/workflow_manager.py old mode 100644 new mode 100755 similarity index 99% rename from submodules/moragents_dockers/agents/src/stores/workflow_manager.py rename to submodules/agents/src/stores/workflow_manager.py index 69b5bf41..1544daa5 --- a/submodules/moragents_dockers/agents/src/stores/workflow_manager.py +++ b/submodules/agents/src/stores/workflow_manager.py @@ -7,7 +7,7 @@ from datetime import datetime, timedelta from dataclasses import dataclass, field from enum import Enum -from src.agents.dca_agent.tools import DCAActionHandler +from services.agents.dca_agent.tools import DCAActionHandler logger = logging.getLogger(__name__) diff --git a/submodules/agents/src/workflows.json b/submodules/agents/src/workflows.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/submodules/agents/src/workflows.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/submodules/agents/tests/controllers/test_delegation_controller.py b/submodules/agents/tests/controllers/test_delegation_controller.py new file mode 100644 index 00000000..84cec13b --- /dev/null +++ b/submodules/agents/tests/controllers/test_delegation_controller.py @@ -0,0 +1,119 @@ +import pytest +from unittest.mock import Mock, patch, AsyncMock +from fastapi import HTTPException +from langchain.schema import AIMessage, HumanMessage, SystemMessage + +from src.models.service.chat_models import ChatRequest, AgentResponse, ChatMessage, ResponseType +from src.models.service.service_models import GenerateConversationTitleRequest +from src.services.delegator.delegator import Delegator +from src.controllers.delegation_controller import DelegationController + + +@pytest.fixture +def mock_delegator(): + return Mock(spec=Delegator) + + +@pytest.fixture +def controller(mock_delegator): + return DelegationController(delegator=mock_delegator) + + +@pytest.fixture +def chat_request(): + return ChatRequest( + conversation_id="test-conv-id", + prompt=ChatMessage(role="user", content="test message"), + chain_id="1", + wallet_address="0x123", + ) + + +@pytest.fixture +def agent_response(): + return AgentResponse.success(content="test response") + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_handle_chat_agent_not_found(controller, chat_request): + # Setup + chat_request.prompt.content = "/nonexistent_agent test message" + + with patch( + "stores.agent_manager_instance.parse_command", return_value=("nonexistent_agent", "test message") + ), patch("stores.agent_manager_instance.get_agent", return_value=None), patch( + "stores.agent_manager_instance.set_active_agent" + ): + # Execute and verify + with pytest.raises(HTTPException) as exc_info: + await controller.handle_chat(chat_request) + assert exc_info.value.status_code == 404 + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_handle_chat_invalid_response(controller, chat_request, mock_delegator): + # Setup + mock_delegator.delegate_chat = AsyncMock(return_value=("test_agent", "invalid response type")) + + with patch("stores.agent_manager_instance.parse_command", return_value=(None, None)), patch( + "stores.agent_manager_instance.clear_active_agent" + ): + # Execute and verify + with pytest.raises(HTTPException) as exc_info: + await controller.handle_chat(chat_request) + assert exc_info.value.status_code == 500 + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_handle_chat_timeout(controller, chat_request, mock_delegator): + # Setup + mock_delegator.delegate_chat = AsyncMock(side_effect=TimeoutError()) + + with patch("stores.agent_manager_instance.parse_command", return_value=(None, None)), patch( + "stores.agent_manager_instance.clear_active_agent" + ): + # Execute and verify + with pytest.raises(HTTPException) as exc_info: + await controller.handle_chat(chat_request) + assert exc_info.value.status_code == 504 + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_generate_conversation_title(controller): + # Setup + request = GenerateConversationTitleRequest( + messages_for_llm=[HumanMessage(content="Hello"), AIMessage(content="Hi there")] + ) + + mock_llm = Mock() + mock_llm.invoke = Mock(return_value=AIMessage(content="Test Conversation Title")) + + with patch("src.controllers.delegation_controller.LLM_DELEGATOR", mock_llm): + # Execute + title = await controller.generate_conversation_title(request) + + # Verify + assert title == "Test Conversation Title" + mock_llm.invoke.assert_called_once() + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_generate_conversation_title_failure(controller): + # Setup + request = GenerateConversationTitleRequest( + messages_for_llm=[HumanMessage(content="Hello"), AIMessage(content="Hi there")] + ) + + mock_llm = Mock() + mock_llm.invoke = Mock(side_effect=Exception("Test error")) + + with patch("src.controllers.delegation_controller.LLM_DELEGATOR", mock_llm): + # Execute and verify + with pytest.raises(HTTPException) as exc_info: + await controller.generate_conversation_title(request) + assert exc_info.value.status_code == 500 diff --git a/submodules/agents/tests/routes/test_delegation_routes.py b/submodules/agents/tests/routes/test_delegation_routes.py new file mode 100644 index 00000000..2ccfc348 --- /dev/null +++ b/submodules/agents/tests/routes/test_delegation_routes.py @@ -0,0 +1,186 @@ +import pytest +from unittest.mock import Mock, patch, AsyncMock +from fastapi.testclient import TestClient +from fastapi import FastAPI +from fastapi.responses import JSONResponse +from src.models.service.chat_models import ChatRequest, ChatMessage +from src.models.service.service_models import GenerateConversationTitleRequest +from src.routes.delegation_routes import router +from src.services.delegator.delegator import Delegator +from src.config import LLM_DELEGATOR + +app = FastAPI() +app.include_router(router) +client = TestClient(app) + + +@pytest.fixture +def chat_request(): + return ChatRequest( + conversation_id="test-conv-id", + prompt=ChatMessage(role="user", content="test message"), + chain_id="1", + wallet_address="0x123", + ) + + +@pytest.fixture +def title_request(): + return GenerateConversationTitleRequest(conversation_id="test-conv-id", messages_for_llm=[]) + + +@pytest.fixture +def mock_delegator(): + return Mock(spec=Delegator) + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_chat_success(chat_request): + response_content = {"content": "test response"} + mock_response = JSONResponse(content=response_content) + + with patch("src.routes.delegation_routes.Delegator") as mock_delegator_class, patch( + "src.routes.delegation_routes.DelegationController" + ) as mock_controller_class, patch("src.routes.delegation_routes.logger") as mock_logger: + + mock_controller = mock_controller_class.return_value + mock_controller.handle_chat = AsyncMock(return_value=mock_response) + + response = client.post("/api/v1/chat", json=chat_request.model_dump()) + + assert response.status_code == 200 + assert response.json() == response_content + + mock_logger.info.assert_called_once_with( + f"Received chat request for conversation {chat_request.conversation_id}" + ) + mock_controller_class.assert_called_once() + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_chat_timeout(chat_request): + with patch("src.routes.delegation_routes.Delegator"), patch( + "src.routes.delegation_routes.DelegationController" + ) as mock_controller_class, patch("src.routes.delegation_routes.logger") as mock_logger: + + mock_controller = mock_controller_class.return_value + mock_controller.handle_chat = AsyncMock(side_effect=TimeoutError()) + + response = client.post("/api/v1/chat", json=chat_request.model_dump()) + + assert response.status_code == 504 + assert response.json()["detail"] == "Request timed out" + mock_logger.error.assert_called_once_with("Chat request timed out") + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_chat_value_error(chat_request): + error_msg = "Invalid input" + with patch("src.routes.delegation_routes.Delegator"), patch( + "src.routes.delegation_routes.DelegationController" + ) as mock_controller_class, patch("src.routes.delegation_routes.logger") as mock_logger: + + mock_controller = mock_controller_class.return_value + mock_controller.handle_chat = AsyncMock(side_effect=ValueError(error_msg)) + + response = client.post("/api/v1/chat", json=chat_request.model_dump()) + + assert response.status_code == 400 + assert response.json()["detail"] == error_msg + mock_logger.error.assert_called_once_with(f"Input formatting error: {error_msg}") + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_chat_generic_error(chat_request): + error_msg = "Something went wrong" + with patch("src.routes.delegation_routes.Delegator"), patch( + "src.routes.delegation_routes.DelegationController" + ) as mock_controller_class, patch("src.routes.delegation_routes.logger") as mock_logger: + + mock_controller = mock_controller_class.return_value + mock_controller.handle_chat = AsyncMock(side_effect=Exception(error_msg)) + + response = client.post("/api/v1/chat", json=chat_request.model_dump()) + + assert response.status_code == 500 + assert response.json()["detail"] == error_msg + mock_logger.error.assert_called_once_with(f"Error in chat route: {error_msg}", exc_info=True) + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_generate_title_success(title_request): + title = "Test Title" + with patch("src.routes.delegation_routes.DelegationController") as mock_controller_class, patch( + "src.routes.delegation_routes.logger" + ) as mock_logger: + + mock_controller = mock_controller_class.return_value + mock_controller.generate_conversation_title = AsyncMock(return_value=title) + + response = client.post("/api/v1/generate-title", json=title_request.model_dump()) + + assert response.status_code == 200 + assert response.json()["title"] == title + + mock_logger.info.assert_called_once_with( + f"Received title generation request for conversation {title_request.conversation_id}" + ) + mock_controller_class.assert_called_once_with() + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_generate_title_timeout(title_request): + with patch("src.routes.delegation_routes.DelegationController") as mock_controller_class, patch( + "src.routes.delegation_routes.logger" + ) as mock_logger: + + mock_controller = mock_controller_class.return_value + mock_controller.generate_conversation_title = AsyncMock(side_effect=TimeoutError()) + + response = client.post("/api/v1/generate-title", json=title_request.model_dump()) + + assert response.status_code == 504 + assert response.json()["detail"] == "Request timed out" + mock_logger.error.assert_called_once_with("Title generation request timed out") + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_generate_title_value_error(title_request): + error_msg = "Invalid input" + with patch("src.routes.delegation_routes.DelegationController") as mock_controller_class, patch( + "src.routes.delegation_routes.logger" + ) as mock_logger: + + mock_controller = mock_controller_class.return_value + mock_controller.generate_conversation_title = AsyncMock(side_effect=ValueError(error_msg)) + + response = client.post("/api/v1/generate-title", json=title_request.model_dump()) + + assert response.status_code == 400 + assert response.json()["detail"] == error_msg + mock_logger.error.assert_called_once_with(f"Input formatting error: {error_msg}") + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_generate_title_generic_error(title_request): + error_msg = "Something went wrong" + with patch("src.routes.delegation_routes.DelegationController") as mock_controller_class, patch( + "src.routes.delegation_routes.logger" + ) as mock_logger: + + mock_controller = mock_controller_class.return_value + mock_controller.generate_conversation_title = AsyncMock(side_effect=Exception(error_msg)) + + response = client.post("/api/v1/generate-title", json=title_request.model_dump()) + + assert response.status_code == 500 + assert response.json()["detail"] == error_msg + mock_logger.error.assert_called_once_with(f"Error in generate title route: {error_msg}", exc_info=True) diff --git a/submodules/agents/tests/routes/test_user_routes.py b/submodules/agents/tests/routes/test_user_routes.py new file mode 100644 index 00000000..3fdab104 --- /dev/null +++ b/submodules/agents/tests/routes/test_user_routes.py @@ -0,0 +1,209 @@ +import pytest +from unittest.mock import patch, Mock, AsyncMock +from fastapi import FastAPI +from fastapi.testclient import TestClient +from agents.src.models.service.user_service_models import UserModel, UserSettingModel +from agents.src.routes.user_routes import router + +app = FastAPI() +app.include_router(router) +client = TestClient(app) + + +@pytest.fixture +def mock_user_controller(): + with patch("agents.src.routes.user_routes.UserController") as mock: + yield mock + + +@pytest.fixture +def sample_user(): + return UserModel(id=1, wallet_address="0x123") + + +@pytest.fixture +def sample_setting(): + return UserSettingModel(id=1, user_id=1, settings_key="test_key", settings_value={"test": "value"}) + + +@pytest.mark.asyncio +async def test_get_user_success(mock_user_controller, sample_user): + mock_controller = Mock() + mock_controller.get_user.return_value = sample_user + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.get("/api/v1/users/1") + + assert response.status_code == 200 + assert response.json() == sample_user.model_dump() + mock_logger.info.assert_called_once_with("Received request to get user 1") + + +@pytest.mark.asyncio +async def test_get_user_not_found(mock_user_controller): + mock_controller = Mock() + mock_controller.get_user.return_value = None + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.get("/api/v1/users/1") + + assert response.status_code == 404 + assert response.json()["detail"] == "User 1 not found" + mock_logger.info.assert_called_once_with("Received request to get user 1") + + +@pytest.mark.asyncio +async def test_get_user_by_wallet_success(mock_user_controller, sample_user): + mock_controller = Mock() + mock_controller.get_user_by_wallet.return_value = sample_user + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.get("/api/v1/users/wallet/0x123") + + assert response.status_code == 200 + assert response.json() == sample_user.model_dump() + mock_logger.info.assert_called_once_with("Received request to get user by wallet 0x123") + + +@pytest.mark.asyncio +async def test_list_users_success(mock_user_controller, sample_user): + mock_controller = Mock() + mock_controller.list_users.return_value = [sample_user] + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.get("/api/v1/users") + + assert response.status_code == 200 + assert response.json() == [sample_user.model_dump()] + mock_logger.info.assert_called_once_with("Received request to list all users") + + +@pytest.mark.asyncio +async def test_create_user_success(mock_user_controller, sample_user): + mock_controller = Mock() + mock_controller.create_user.return_value = sample_user + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.post("/api/v1/users", params={"wallet_address": "0x123"}) + + assert response.status_code == 201 + assert response.json() == sample_user.model_dump() + mock_logger.info.assert_called_once_with("Received request to create user with wallet 0x123") + + +@pytest.mark.asyncio +async def test_update_user_success(mock_user_controller, sample_user): + mock_controller = Mock() + mock_controller.update_user.return_value = sample_user + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.put("/api/v1/users/1", params={"wallet_address": "0x123"}) + + assert response.status_code == 200 + assert response.json() == sample_user.model_dump() + mock_logger.info.assert_called_once_with("Received request to update user 1") + + +@pytest.mark.asyncio +async def test_delete_user_success(mock_user_controller): + mock_controller = Mock() + mock_controller.delete_user.return_value = True + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.delete("/api/v1/users/1") + + assert response.status_code == 200 + assert response.json() == {"status": "success"} + mock_logger.info.assert_called_once_with("Received request to delete user 1") + + +@pytest.mark.asyncio +async def test_get_user_setting_success(mock_user_controller, sample_setting): + mock_controller = Mock() + mock_controller.get_setting.return_value = sample_setting + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.get("/api/v1/users/1/settings/test_key") + + assert response.status_code == 200 + assert response.json() == sample_setting.model_dump() + mock_logger.info.assert_called_once_with("Received request to get setting test_key for user 1") + + +@pytest.mark.asyncio +async def test_list_user_settings_success(mock_user_controller, sample_setting): + mock_controller = Mock() + mock_controller.list_user_settings.return_value = [sample_setting] + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.get("/api/v1/users/1/settings") + + assert response.status_code == 200 + assert response.json() == [sample_setting.model_dump()] + mock_logger.info.assert_called_once_with("Received request to list settings for user 1") + + +@pytest.mark.asyncio +async def test_create_user_setting_success(mock_user_controller, sample_setting): + mock_controller = Mock() + mock_controller.create_setting.return_value = sample_setting + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.post("/api/v1/users/1/settings/test_key", json={"test": "value"}) + + assert response.status_code == 201 + assert response.json() == sample_setting.model_dump() + mock_logger.info.assert_called_once_with("Received request to create setting test_key for user 1") + + +@pytest.mark.asyncio +async def test_update_user_setting_success(mock_user_controller, sample_setting): + mock_controller = Mock() + mock_controller.update_setting.return_value = sample_setting + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.put("/api/v1/users/1/settings/test_key", json={"test": "value"}) + + assert response.status_code == 200 + assert response.json() == sample_setting.model_dump() + mock_logger.info.assert_called_once_with("Received request to update setting test_key for user 1") + + +@pytest.mark.asyncio +async def test_delete_user_setting_success(mock_user_controller): + mock_controller = Mock() + mock_controller.delete_setting.return_value = True + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.delete("/api/v1/users/1/settings/test_key") + + assert response.status_code == 200 + assert response.json() == {"status": "success"} + mock_logger.info.assert_called_once_with("Received request to delete setting test_key for user 1") + + +@pytest.mark.asyncio +async def test_error_handling(mock_user_controller): + mock_controller = Mock() + mock_controller.get_user.side_effect = Exception("Test error") + mock_user_controller.return_value.__enter__.return_value = mock_controller + + with patch("agents.src.routes.user_routes.logger") as mock_logger: + response = client.get("/api/v1/users/1") + + assert response.status_code == 500 + assert response.json()["detail"] == "Test error" + mock_logger.info.assert_called_once_with("Received request to get user 1") + mock_logger.error.assert_called_once_with("Error getting user: Test error", exc_info=True) diff --git a/submodules/agents/tests/services/delegator/test_delegator_benchmark.py b/submodules/agents/tests/services/delegator/test_delegator_benchmark.py new file mode 100644 index 00000000..e69de29b diff --git a/submodules/agents/tests/services/delegator/test_delegator_unit.py b/submodules/agents/tests/services/delegator/test_delegator_unit.py new file mode 100644 index 00000000..4be0f0a6 --- /dev/null +++ b/submodules/agents/tests/services/delegator/test_delegator_unit.py @@ -0,0 +1,156 @@ +import pytest +import logging +import json +from unittest.mock import Mock, patch, AsyncMock +from src.services.delegator.delegator import Delegator, RankAgentsOutput +from src.models.service.chat_models import ChatRequest, AgentResponse, ResponseType, ChatMessage +from langchain.schema import AIMessage, HumanMessage, SystemMessage + + +@pytest.fixture +def mock_llm(): + return Mock() + + +@pytest.fixture +def delegator(mock_llm): + with patch("src.services.delegator.delegator.LLM_DELEGATOR", mock_llm): + return Delegator(mock_llm) + + +@pytest.fixture +def sample_chat_request(): + return ChatRequest( + conversation_id="test_conv", + prompt=ChatMessage(role="user", content="What's the weather?", agentName="base"), + chain_id="1", + wallet_address="0x123", + chat_history=[ + ChatMessage(role="user", content="Hello", agentName="base"), + ChatMessage(role="assistant", content="Hi there", agentName="base"), + ], + ) + + +@pytest.mark.unit +def test_build_system_prompt(delegator): + available_agents = [ + {"name": "weather", "delegator_description": "Gets weather info"}, + {"name": "math", "delegator_description": "Does calculations"}, + ] + + with patch("src.services.delegator.delegator.get_system_prompt") as mock_get_prompt: + mock_get_prompt.return_value = "You are Morpheus\n- weather: Gets weather info\n- math: Does calculations" + prompt = mock_get_prompt(available_agents) + + assert "weather: Gets weather info" in prompt + assert "math: Does calculations" in prompt + assert "You are Morpheus" in prompt + + +@pytest.mark.unit +@patch("src.services.delegator.delegator.agent_manager_instance.get_available_agents") +def test_get_delegator_response_no_agents(mock_get_agents, delegator, sample_chat_request): + mock_get_agents.return_value = [] + + result = delegator.get_delegator_response(sample_chat_request) + + assert result == ["default"] + assert "default" not in delegator.attempted_agents + + +@pytest.mark.unit +@patch("src.services.delegator.delegator.agent_manager_instance.get_available_agents") +def test_get_delegator_response_valid_json(mock_get_agents, delegator, sample_chat_request, mock_llm): + mock_get_agents.return_value = [{"name": "agent1", "delegator_description": "test"}] + mock_llm.return_value = Mock(content='{"agents": ["agent1", "agent2"]}') + + result = delegator.get_delegator_response(sample_chat_request) + + assert result == ["agent1", "agent2"] + assert delegator.selected_agents_for_request == ["agent1", "agent2"] + + +@pytest.mark.unit +@patch("src.services.delegator.delegator.agent_manager_instance.get_available_agents") +def test_get_delegator_response_fallback_parser(mock_get_agents, delegator, sample_chat_request, mock_llm): + mock_get_agents.return_value = [{"name": "agent1", "delegator_description": "test"}] + mock_llm.return_value = Mock(content='{"agents": ["agent1"]}') + + result = delegator.get_delegator_response(sample_chat_request) + + assert result == ["agent1"] + + +@pytest.mark.unit +@pytest.mark.asyncio +@patch("src.services.delegator.delegator.load_agent_config") +async def test_try_agent_success(mock_load_config, delegator, sample_chat_request): + mock_load_config.return_value = {"path": "test.path", "class_name": "TestAgent"} + + mock_module = Mock() + mock_agent_class = Mock() + mock_agent = Mock() + mock_agent.chat = AsyncMock(return_value=AgentResponse.success(content="Success")) + mock_agent_class.return_value = mock_agent + mock_module.TestAgent = mock_agent_class + + with patch.dict("sys.modules", {"test.path": mock_module}): + result = await delegator._try_agent("test_agent", sample_chat_request) + + assert result is not None + assert result.content == "Success" + assert result.response_type == ResponseType.SUCCESS + + +@pytest.mark.unit +@pytest.mark.asyncio +@patch("src.services.delegator.delegator.load_agent_config") +async def test_try_agent_error_response(mock_load_config, delegator, sample_chat_request): + mock_load_config.return_value = {"path": "test.path", "class_name": "TestAgent"} + + mock_module = Mock() + mock_agent_class = Mock() + mock_agent = Mock() + error_response = AgentResponse.error(error_message="Error occurred") + mock_agent.chat = AsyncMock(return_value=error_response) + mock_agent_class.return_value = mock_agent + mock_module.TestAgent = mock_agent_class + + with patch.dict("sys.modules", {"test.path": mock_module}): + result = await delegator._try_agent("test_agent", sample_chat_request) + + assert result is not None + assert result.response_type == ResponseType.ERROR + assert result.error_message == "Error occurred" + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_delegate_chat_success(delegator, sample_chat_request): + with patch.object(delegator, "get_delegator_response") as mock_get_response: + mock_get_response.return_value = ["agent1"] + with patch.object(delegator, "_try_agent") as mock_try: + mock_try.return_value = AgentResponse.success(content="Success") + + agent_name, response = await delegator.delegate_chat(sample_chat_request) + + assert agent_name == "agent1" + assert response.content == "Success" + assert "agent1" in delegator.attempted_agents + + +@pytest.mark.unit +@pytest.mark.asyncio +async def test_delegate_chat_all_agents_fail(delegator, sample_chat_request): + with patch.object(delegator, "get_delegator_response") as mock_get_response: + mock_get_response.return_value = ["agent1", "agent2"] + with patch.object(delegator, "_try_agent") as mock_try: + mock_try.return_value = None + + agent_name, response = await delegator.delegate_chat(sample_chat_request) + + assert agent_name is None + assert response.error_message == "All agents have been attempted without success" + assert "agent1" in delegator.attempted_agents + assert "agent2" in delegator.attempted_agents diff --git a/submodules/moragents_dockers/frontend/.eslintrc.json b/submodules/frontend/.eslintrc.json old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/.eslintrc.json rename to submodules/frontend/.eslintrc.json diff --git a/submodules/moragents_dockers/frontend/.gitignore b/submodules/frontend/.gitignore old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/.gitignore rename to submodules/frontend/.gitignore diff --git a/submodules/moragents_dockers/frontend/.npmrc b/submodules/frontend/.npmrc old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/.npmrc rename to submodules/frontend/.npmrc diff --git a/submodules/moragents_dockers/frontend/Dockerfile b/submodules/frontend/Dockerfile old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/Dockerfile rename to submodules/frontend/Dockerfile diff --git a/submodules/frontend/Dockerfile.dev b/submodules/frontend/Dockerfile.dev new file mode 100644 index 00000000..251b995b --- /dev/null +++ b/submodules/frontend/Dockerfile.dev @@ -0,0 +1,18 @@ +FROM node:18 + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm install -g npm@10 && npm install + +# Copy the rest of the application code +COPY . . + +# Expose port 80 for internal container port +EXPOSE 80 + +# Start the development server with specific host and port +CMD ["sh", "-c", "npm run dev -- -p 80 -H 0.0.0.0"] \ No newline at end of file diff --git a/submodules/moragents_dockers/frontend/README.md b/submodules/frontend/README.md old mode 100644 new mode 100755 similarity index 75% rename from submodules/moragents_dockers/frontend/README.md rename to submodules/frontend/README.md index 606d7e39..4becfb1b --- a/submodules/moragents_dockers/frontend/README.md +++ b/submodules/frontend/README.md @@ -1,20 +1,18 @@ - # Morpheus Front-end This is a front-end for the Morpheus local install. It connects directly to agents that expose an API in OpenAI chat completion format, e.g. -```messages = [{"role":"user","content":"swap 1 eth"}]``` - +`messages = [{"role":"user","content":"swap 1 eth"}]` ## Configuration ### Adding a new agent -Edit ```frontend/config.ts``` +Edit `frontend/config.ts` ## Usage -```npm install``` +`npm install` Run the development server: @@ -24,21 +22,20 @@ npm run dev Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - ## API Endpoints Your agent should expose the following endpoints: ### 1. Chat + This is the main endpoint for chatting with the agent. -```http://127.0.0.1:5000/``` +`http://127.0.0.1:3333/` The chat API accepts inputs in OpenAI chat completion format - see the example below: - ```sh -url = 'http://127.0.0.1:5000/ +url = 'http://127.0.0.1:3333/ message={"role":"user","content":"swap 1 eth"} data = {'prompt':message,'chain_id':56} response = requests.post(url, json=data) @@ -55,8 +52,7 @@ If the agent has enough information (buy token, sell token, amount) it will then If the token symbols are valid, it will then check the user has sufficient balance of the sell token. -If there is sufficient balance, the agent will provide a response containing a quote. This quote is structured with json so that the UI may present the information in a suitable way. - +If there is sufficient balance, the agent will provide a response containing a quote. This quote is structured with json so that the UI may present the information in a suitable way. ```sh response = {"role": "swap", @@ -66,7 +62,7 @@ response = {"role": "swap", "swap_tx_cb": "/swap" } } - ``` +``` If the user wants to perform a swap based on the quote, the following steps are required: @@ -74,31 +70,30 @@ If the user wants to perform a swap based on the quote, the following steps are 2) If allowance < swap amount, send an approve transaction 3) If allowance >= swap amount, send a swap transaction - ### 2. Check Allowance - ```http://127.0.0.1:5000/allowance``` +`http://127.0.0.1:3333/allowance` - ```sh -url='http://127.0.0.1:5000/allowance - data = { - "tokenAddress":"token address here", - "walletAddress":"wallet address here", - "chain_id":56 - } - response = requests.post(url, json=data) - swap_transaction=response.text +```sh +url='http://127.0.0.1:3333/allowance + data = { + "tokenAddress":"token address here", + "walletAddress":"wallet address here", + "chain_id":56 + } + response = requests.post(url, json=data) + swap_transaction=response.text - ``` - And then this api will return allowance value either 0 or 1 +``` +And then this api will return allowance value either 0 or 1 ### 3. Generate Approve TX -```http://127.0.0.1:5000/approve``` +`http://127.0.0.1:3333/approve` ```sh -url='http://127.0.0.1:5000/approve +url='http://127.0.0.1:3333/approve data = { "tokenAddress":"token address here", "amount": 10 , #amount to be swapped here @@ -111,10 +106,10 @@ url='http://127.0.0.1:5000/approve ### 4. Generate Swap tx -```http://127.0.0.1:5000/swap``` +`http://127.0.0.1:3333/swap` ```sh -url='http://127.0.0.1:5000/swap +url='http://127.0.0.1:3333/swap data = { "src": token1_address, "dst": token2_address, @@ -127,52 +122,47 @@ url='http://127.0.0.1:5000/swap ``` - - ### 5. Transaction status - -```http://127.0.0.1:5000/tx_status``` +`http://127.0.0.1:3333/tx_status` This endpoint is used to inform the back-end of the status of transactions that have been signed by the user's wallet on the front-end. Status values: -* "initiated" = tx has been sent to the wallet -* "cancelled" = tx cancelled by user -* "success" = tx successful -* "failed" = tx failed for some other reason, including being rejected by the wallet +- "initiated" = tx has been sent to the wallet +- "cancelled" = tx cancelled by user +- "success" = tx successful +- "failed" = tx failed for some other reason, including being rejected by the wallet Tx_type values: -* "swap" -* "approve" +- "swap" +- "approve" ``` -url = 'http://127.0.0.1:5000/tx_status' +url = 'http://127.0.0.1:3333/tx_status' data={'status':'success', 'tx_type':'approve'} response = requests.post(url, json=data) print(response.text) ``` - ### 6. Messages #### Get message history -```http://127.0.0.1:5000/messages``` +`http://127.0.0.1:3333/messages` Conversation history is stored in the backend. ``` -url = 'http://127.0.0.1:5000/messages' +url = 'http://127.0.0.1:3333/messages' response = requests.get(url) result=response.text ``` - ``` {"messages":[ {"content":"swap 1 eth","role":"user"}, @@ -182,18 +172,15 @@ result=response.text #### Clear message history - ``` -url = http://127.0.0.1:5000/clear_messages +url = http://127.0.0.1:3333/clear_messages response = requests.get(url, json=data) ``` - ## Learn More This is a [RainbowKit](https://rainbowkit.com) + [wagmi](https://wagmi.sh) + [Next.js](https://nextjs.org/) project bootstrapped with [`create-rainbowkit`](/packages/create-rainbowkit). - To learn more about this stack, take a look at the following resources: - [RainbowKit Documentation](https://rainbowkit.com) - Learn how to customize your wallet connection flow. diff --git a/submodules/moragents_dockers/frontend/app/apple-touch-icon.png b/submodules/frontend/app/apple-touch-icon.png old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/app/apple-touch-icon.png rename to submodules/frontend/app/apple-touch-icon.png diff --git a/submodules/moragents_dockers/frontend/app/favicon-16x16.png b/submodules/frontend/app/favicon-16x16.png old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/app/favicon-16x16.png rename to submodules/frontend/app/favicon-16x16.png diff --git a/submodules/moragents_dockers/frontend/app/favicon-32x32.png b/submodules/frontend/app/favicon-32x32.png old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/app/favicon-32x32.png rename to submodules/frontend/app/favicon-32x32.png diff --git a/submodules/moragents_dockers/frontend/app/favicon.ico b/submodules/frontend/app/favicon.ico old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/app/favicon.ico rename to submodules/frontend/app/favicon.ico diff --git a/submodules/moragents_dockers/frontend/app/mstile-150x150.png b/submodules/frontend/app/mstile-150x150.png old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/app/mstile-150x150.png rename to submodules/frontend/app/mstile-150x150.png diff --git a/submodules/moragents_dockers/frontend/app/safari-pinned-tab.svg b/submodules/frontend/app/safari-pinned-tab.svg old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/app/safari-pinned-tab.svg rename to submodules/frontend/app/safari-pinned-tab.svg diff --git a/submodules/frontend/components/Agents/Base/SwapMessage.tsx b/submodules/frontend/components/Agents/Base/SwapMessage.tsx new file mode 100644 index 00000000..ab9afb5b --- /dev/null +++ b/submodules/frontend/components/Agents/Base/SwapMessage.tsx @@ -0,0 +1,223 @@ +import React, { useState } from "react"; +import { + VStack, + Box, + Text, + Select, + Input, + Button, + FormControl, + FormLabel, + useToast, + HStack, + IconButton, + Collapse, +} from "@chakra-ui/react"; +import { ArrowLeftRight } from "lucide-react"; +import { BASE_AVAILABLE_TOKENS } from "@/services/constants"; + +interface SwapConfig { + fromToken: string; + toToken: string; + amount: number; +} + +const BaseSwapMessage = ({ + content, + metadata, +}: { + content: any; + metadata: any; +}) => { + const toast = useToast(); + const [showForm, setShowForm] = useState(false); + const [inputValue, setInputValue] = useState("0"); + + const [config, setConfig] = useState({ + fromToken: "usdc", + toToken: "weth", + amount: 0, + }); + + const handleAmountChange = (value: string) => { + // Allow empty string, "0", decimal point, and valid numbers + if (value === "" || value === "0" || value === ".") { + setInputValue(value); + setConfig({ ...config, amount: 0 }); + return; + } + + // Validate the input matches a valid number pattern + if (/^\d*\.?\d*$/.test(value) && value !== ".") { + setInputValue(value); + setConfig({ ...config, amount: parseFloat(value) }); + } + }; + + const handleSwap = async () => { + if (config.fromToken === config.toToken) { + toast({ + title: "Invalid Configuration", + description: "Source and destination tokens must be different", + status: "error", + duration: 3000, + isClosable: true, + }); + return; + } + + if (config.amount <= 0) { + toast({ + title: "Invalid Amount", + description: "Amount must be greater than 0", + status: "error", + duration: 3000, + isClosable: true, + }); + return; + } + + try { + const response = await fetch("http://localhost:8888/base/swap", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + fromAsset: config.fromToken, + toAsset: config.toToken, + amount: config.amount, + }), + }); + + const data = await response.json(); + + if (data.status === "success") { + toast({ + title: "Swap Initiated", + description: "Your swap request has been submitted", + status: "success", + duration: 3000, + isClosable: true, + }); + setShowForm(false); + } else { + throw new Error(data.message); + } + } catch (error) { + toast({ + title: "Swap Failed", + description: + error instanceof Error ? error.message : "Failed to execute swap", + status: "error", + duration: 3000, + isClosable: true, + }); + } + }; + + return ( + + + {content} + setShowForm(!showForm)} + variant="ghost" + size="sm" + color="gray.400" + _hover={{ color: "blue.400" }} + aria-label="Configure swap" + icon={} + /> + + + + + + + + From Token + + + + + To Token + + + + + + Amount + handleAmountChange(e.target.value)} + placeholder="0.0" + color="white" + type="text" + /> + + Amount of {config.fromToken} to swap + + + + + + + + + ); +}; + +export default BaseSwapMessage; diff --git a/submodules/frontend/components/Agents/Base/TransferMessage.tsx b/submodules/frontend/components/Agents/Base/TransferMessage.tsx new file mode 100644 index 00000000..ec3420e7 --- /dev/null +++ b/submodules/frontend/components/Agents/Base/TransferMessage.tsx @@ -0,0 +1,198 @@ +import React, { useState } from "react"; +import { + VStack, + Box, + Text, + Select, + Button, + FormControl, + FormLabel, + useToast, + Input, + IconButton, + Collapse, +} from "@chakra-ui/react"; +import { SendHorizontal } from "lucide-react"; +import { BASE_AVAILABLE_TOKENS } from "@/services/constants"; + +interface TransferConfig { + token: string; + amount: string; + destinationAddress: string; +} + +const BaseTransferMessage = ({ + content, + metadata, +}: { + content: any; + metadata: any; +}) => { + const toast = useToast(); + const [showForm, setShowForm] = useState(false); + + const [config, setConfig] = useState({ + token: "usdc", + amount: "", + destinationAddress: "", + }); + + const handleTransfer = async () => { + // Validate destination address + if (!config.destinationAddress) { + toast({ + title: "Invalid Configuration", + description: "Destination address is required", + status: "error", + duration: 3000, + isClosable: true, + }); + return; + } + + const parsedAmount = parseFloat(config.amount); + if (isNaN(parsedAmount) || parsedAmount <= 0) { + toast({ + title: "Invalid Amount", + description: "Please enter a valid number greater than 0", + status: "error", + duration: 3000, + isClosable: true, + }); + return; + } + + try { + const response = await fetch("http://localhost:8888/base/transfer", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + asset: config.token, + amount: parsedAmount, + destinationAddress: config.destinationAddress, + }), + }); + + const data = await response.json(); + + if (data.status === "success") { + toast({ + title: "Transfer Successful", + description: data.message, + status: "success", + duration: 3000, + isClosable: true, + }); + setShowForm(false); + } else { + throw new Error(data.message); + } + } catch (error) { + toast({ + title: "Transfer Failed", + description: + error instanceof Error ? error.message : "Unknown error occurred", + status: "error", + duration: 3000, + isClosable: true, + }); + } + }; + + return ( + + + {content} + setShowForm(!showForm)} + variant="ghost" + size="sm" + color="gray.400" + _hover={{ color: "blue.400" }} + aria-label="Configure transfer" + icon={} + /> + + + + + + + Token + + + + + Amount + + setConfig({ + ...config, + amount: e.target.value, + }) + } + /> + + Amount of {config.token} to transfer + + + + + Destination Address + + setConfig({ ...config, destinationAddress: e.target.value }) + } + placeholder="Enter destination address" + color="white" + /> + + + + + + + + ); +}; + +export default BaseTransferMessage; diff --git a/submodules/frontend/components/Agents/Codex/NftSearch/CodexNftSearchMessage.module.css b/submodules/frontend/components/Agents/Codex/NftSearch/CodexNftSearchMessage.module.css new file mode 100644 index 00000000..951db67b --- /dev/null +++ b/submodules/frontend/components/Agents/Codex/NftSearch/CodexNftSearchMessage.module.css @@ -0,0 +1,228 @@ +/* CodexNftSearchMessage.module.css */ + +.container { + width: 100%; + max-width: 800px; + margin: 0 auto; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.title { + font-size: 16px; + font-weight: 500; + color: #e4e4e4; +} + +.total { + color: #888; + font-size: 12px; +} + +.nftCard { + background: rgba(255, 255, 255, 0.05); + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.cardHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.collectionInfo { + display: flex; + align-items: center; + gap: 12px; +} + +.collectionImage { + width: 40px; + height: 40px; + border-radius: 8px; + object-fit: cover; +} + +.imagePlaceholder { + width: 40px; + height: 40px; + border-radius: 8px; + background: rgba(255, 255, 255, 0.1); + display: flex; + align-items: center; + justify-content: center; + color: #888; +} + +.collectionDetails { + display: flex; + flex-direction: column; +} + +.collectionName { + font-weight: 500; + color: #e4e4e4; +} + +.collectionSymbol { + font-size: 12px; + color: #888; +} + +.networkInfo { + font-size: 12px; + color: #888; +} + +.priceGrid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; + margin-bottom: 16px; + padding-bottom: 16px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.priceItem { + text-align: center; +} + +.priceLabel { + font-size: 12px; + color: #888; + margin-bottom: 4px; +} + +.priceValue { + display: flex; + align-items: center; + justify-content: center; + font-weight: 500; + color: #e4e4e4; + gap: 2px; +} + +.activityContainer { + display: flex; + justify-content: space-between; + margin-bottom: 16px; + padding-bottom: 16px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.activityItem { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +} + +.activityLabel { + font-size: 12px; + color: #888; +} + +.activityValue { + display: flex; + flex-direction: column; + align-items: center; + font-weight: 500; + color: #e4e4e4; +} + +.changeValue { + display: flex; + align-items: center; + gap: 2px; + font-size: 12px; + margin-top: 4px; +} + +.positive { + display: flex; + align-items: center; + gap: 2px; + color: #4caf50; +} + +.negative { + display: flex; + align-items: center; + gap: 2px; + color: #f44336; +} + +.footer { + display: flex; + justify-content: space-between; + align-items: center; +} + +.address { + font-family: monospace; + font-size: 12px; + color: #888; +} + +.explorerLink { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: #64b5f6; + text-decoration: none; +} + +.explorerLink:hover { + text-decoration: underline; +} + +.showMoreButton { + width: 100%; + padding: 8px; + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 8px; + color: #888; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 12px; + margin-top: 16px; +} + +.showMoreButton:hover { + background: rgba(255, 255, 255, 0.15); + color: #aaa; +} + +@media (max-width: 640px) { + .priceGrid { + gap: 8px; + } + + .networkInfo { + display: none; + } + + .nftCard { + padding: 12px; + } + + .collectionImage, + .imagePlaceholder { + width: 32px; + height: 32px; + } +} diff --git a/submodules/frontend/components/Agents/Codex/NftSearch/CodexNftSearchMessage.tsx b/submodules/frontend/components/Agents/Codex/NftSearch/CodexNftSearchMessage.tsx new file mode 100644 index 00000000..746caa20 --- /dev/null +++ b/submodules/frontend/components/Agents/Codex/NftSearch/CodexNftSearchMessage.tsx @@ -0,0 +1,181 @@ +import React, { useState } from "react"; +import { + Image, + DollarSign, + TrendingUp, + TrendingDown, + ChevronDown, + ChevronUp, + ExternalLink, +} from "lucide-react"; +import styles from "./CodexNftSearchMessage.module.css"; +import { + CodexNftSearchMessageProps, + NftItem, +} from "./CodexNftSearchMessage.types"; +import { Text } from "@chakra-ui/react"; + +const INITIAL_DISPLAY_COUNT = 3; + +const formatNumber = (num: string | number): string => { + const value = typeof num === "string" ? parseFloat(num) : num; + if (value >= 1000000000) { + return `${(value / 1000000000).toFixed(2)}B`; + } + if (value >= 1000000) { + return `${(value / 1000000).toFixed(2)}M`; + } + if (value >= 1000) { + return `${(value / 1000).toFixed(1)}K`; + } + return value.toLocaleString(); +}; + +const ChangeDisplay: React.FC<{ value: number }> = ({ value }) => { + const isPositive = value > 0; + return ( + + {isPositive ? : } + {value.toFixed(2)}% + + ); +}; + +export const CodexNftSearchMessage: React.FC = ({ + metadata, +}) => { + const [showAll, setShowAll] = useState(false); + + if (!metadata.success) { + return Failed to load NFT data.; + } + + if (!metadata.items || metadata.items.length === 0) { + return No NFT collections found matching your search.; + } + + const displayItems = showAll + ? metadata.items + : metadata.items.slice(0, INITIAL_DISPLAY_COUNT); + + return ( +
+
+ NFT Collection Search Results + + Found: {metadata.items.length}{" "} + {metadata.hasMore > 0 && `(${metadata.hasMore} more available)`} + +
+ + {displayItems.map((item: NftItem) => ( +
+
+
+ {item.imageUrl ? ( + {item.name + ) : ( +
+ +
+ )} +
+
+ {item.name || "Unnamed Collection"} +
+ {item.symbol && ( +
{item.symbol}
+ )} +
+
+
+ ID: {item.networkId} β€’ {item.window} +
+
+ +
+
+
Floor
+
+ + {item.floor} +
+
+
+
Ceiling
+
+ + {item.ceiling} +
+
+
+
Average
+
+ + {item.average} +
+
+
+ +
+
+
Volume
+
+ {formatNumber(item.volume)} +
+ +
+
+
+
+
Trades
+
+ {formatNumber(item.tradeCount)} +
+ +
+
+
+
+ +
+
+ {item.address.slice(0, 8)}...{item.address.slice(-8)} +
+ + View + +
+
+ ))} + + {metadata.items.length > INITIAL_DISPLAY_COUNT && ( + + )} +
+ ); +}; + +export default CodexNftSearchMessage; diff --git a/submodules/frontend/components/Agents/Codex/NftSearch/CodexNftSearchMessage.types.ts b/submodules/frontend/components/Agents/Codex/NftSearch/CodexNftSearchMessage.types.ts new file mode 100644 index 00000000..45ebab4e --- /dev/null +++ b/submodules/frontend/components/Agents/Codex/NftSearch/CodexNftSearchMessage.types.ts @@ -0,0 +1,30 @@ +// CodexNftSearchMessage.types.ts + +interface NftItem { + address: string; + average: string; + ceiling: string; + floor: string; + id: string; + imageUrl?: string | null; + name?: string | null; + networkId: number; + symbol?: string | null; + tradeCount: string; + tradeCountChange: number; + volume: string; + volumeChange: number; + window: string; +} + +interface NftSearchMetadata { + success: boolean; + hasMore: number; + items: NftItem[]; +} + +interface CodexNftSearchMessageProps { + metadata: NftSearchMetadata; +} + +export type { CodexNftSearchMessageProps, NftItem, NftSearchMetadata }; diff --git a/submodules/frontend/components/Agents/Codex/TopHolders/CodexTopHoldersMessage.module.css b/submodules/frontend/components/Agents/Codex/TopHolders/CodexTopHoldersMessage.module.css new file mode 100644 index 00000000..7db0de26 --- /dev/null +++ b/submodules/frontend/components/Agents/Codex/TopHolders/CodexTopHoldersMessage.module.css @@ -0,0 +1,237 @@ +.container { + width: 100%; + max-width: 800px; + margin: 0 auto; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.titleContainer { + display: flex; + align-items: center; + gap: 6px; +} + +.titleIcon { + color: #aaa; +} + +.title { + font-size: 15px; + font-weight: 500; + color: #e4e4e4; +} + +.tokenInfo { + display: flex; + align-items: center; + gap: 6px; +} + +.tokenSymbol { + font-weight: 600; + font-size: 14px; + color: #e4e4e4; +} + +.tokenName { + font-size: 12px; + color: #aaa; +} + +.card { + background: rgba(255, 255, 255, 0.05); + border-radius: 10px; + padding: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.mainContent { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 14px; +} + +.percentageDisplay { + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.percentageValue { + font-size: 28px; + font-weight: 600; + color: #e4e4e4; + line-height: 1; +} + +.percentageLabel { + font-size: 12px; + color: #aaa; + margin-top: 4px; +} + +.tokenMetrics { + display: flex; + gap: 16px; +} + +.metricItem { + text-align: right; +} + +.metricLabel { + font-size: 11px; + color: #aaa; + margin-bottom: 2px; +} + +.metricValue { + font-size: 14px; + font-weight: 500; + color: #e4e4e4; +} + +.progressContainer { + margin-bottom: 14px; +} + +.progressBar { + height: 8px; + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; + overflow: hidden; + margin-bottom: 4px; +} + +.progressFill { + height: 100%; + transition: width 0.5s ease; +} + +.highConcentration { + background: linear-gradient(90deg, #ff4d4d, #ff8080); +} + +.moderateConcentration { + background: linear-gradient(90deg, #ffb74d, #ffd280); +} + +.lowConcentration { + background: linear-gradient(90deg, #4caf50, #80e27e); +} + +.progressLabels { + display: flex; + justify-content: space-between; + color: #888; + font-size: 11px; +} + +.analysisContainer { + display: flex; + align-items: center; + padding: 12px; + background: rgba(255, 255, 255, 0.03); + border-radius: 6px; + gap: 12px; +} + +.analysisIcon { + display: flex; + align-items: center; + justify-content: center; +} + +.warningIcon { + color: #ff9800; +} + +.successIcon { + color: #4caf50; +} + +.analysisContent { + flex: 1; +} + +.analysisTitle { + font-weight: 500; + font-size: 13px; + margin-bottom: 2px; + color: #e4e4e4; +} + +.analysisDescription { + font-size: 12px; + color: #aaa; + line-height: 1.3; +} + +.pairInfo { + display: flex; + flex-direction: column; + align-items: flex-end; + border-left: 1px solid rgba(255, 255, 255, 0.1); + padding-left: 12px; +} + +.pairLabel { + font-size: 11px; + color: #aaa; + margin-bottom: 2px; +} + +.pairValue { + display: flex; + align-items: center; + gap: 4px; + font-size: 13px; + color: #e4e4e4; +} + +.linkIcon { + color: #aaa; +} + +@media (max-width: 640px) { + .card { + padding: 12px; + } + + .mainContent { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } + + .percentageDisplay { + align-items: center; + width: 100%; + } + + .tokenMetrics { + width: 100%; + justify-content: space-between; + } + + .analysisContainer { + flex-wrap: wrap; + } + + .pairInfo { + width: 100%; + border-left: none; + border-top: 1px solid rgba(255, 255, 255, 0.1); + padding-left: 0; + padding-top: 8px; + margin-top: 8px; + align-items: flex-start; + } +} diff --git a/submodules/frontend/components/Agents/Codex/TopHolders/CodexTopHoldersMessage.tsx b/submodules/frontend/components/Agents/Codex/TopHolders/CodexTopHoldersMessage.tsx new file mode 100644 index 00000000..ba529da7 --- /dev/null +++ b/submodules/frontend/components/Agents/Codex/TopHolders/CodexTopHoldersMessage.tsx @@ -0,0 +1,158 @@ +import React from "react"; +import { AlertTriangle, Check, PieChart, ExternalLink } from "lucide-react"; +import styles from "./CodexTopHoldersMessage.module.css"; +import { CodexTopHoldersMessageProps } from "./CodexTopHoldersMessage.types"; +import { Text } from "@chakra-ui/react"; + +export const CodexTopHoldersMessage: React.FC = ({ + metadata, +}) => { + if (!metadata.success) { + return Failed to load token holder data.; + } + + const percentage = metadata.data; + const tokenInfo = metadata.token_info; + + // Format dollar values + const formatUSD = (value?: string) => { + if (!value) return "N/A"; + const num = parseFloat(value); + if (isNaN(num)) return "N/A"; + + if (num >= 1e9) return `$${(num / 1e9).toFixed(2)}B`; + if (num >= 1e6) return `$${(num / 1e6).toFixed(2)}M`; + if (num >= 1e3) return `$${(num / 1e3).toFixed(2)}K`; + return `$${num.toFixed(2)}`; + }; + + // Determine concentration level and styling + const getConcentrationLevel = (percentage: number) => { + if (percentage > 80) { + return { + icon: , + label: "High Concentration", + description: "Token ownership is highly concentrated", + barClass: styles.highConcentration, + iconClass: styles.warningIcon, + }; + } else if (percentage > 50) { + return { + icon: , + label: "Moderate Concentration", + description: "Notable concentration among top holders", + barClass: styles.moderateConcentration, + iconClass: styles.warningIcon, + }; + } else { + return { + icon: , + label: "Well Distributed", + description: "Relatively well distributed ownership", + barClass: styles.lowConcentration, + iconClass: styles.successIcon, + }; + } + }; + + const concentrationInfo = getConcentrationLevel(percentage); + + return ( +
+
+
+ + Token Holder Concentration +
+ {tokenInfo?.token && ( +
+ {tokenInfo.token.symbol} + {tokenInfo.token.name && ( + {tokenInfo.token.name} + )} +
+ )} +
+ +
+
+
+
+ {percentage.toFixed(2)}% +
+
Top 10 holders
+
+ +
+ {tokenInfo?.priceUSD && ( +
+
Price
+
+ {formatUSD(tokenInfo.priceUSD)} +
+
+ )} + {tokenInfo?.marketCap && ( +
+
Market Cap
+
+ {formatUSD(tokenInfo.marketCap)} +
+
+ )} + {tokenInfo?.liquidity && ( +
+
Liquidity
+
+ {formatUSD(tokenInfo.liquidity)} +
+
+ )} +
+
+ +
+
+
+
+
+ 0% + 50% + 100% +
+
+ +
+
+ {concentrationInfo.icon} +
+
+
+ {concentrationInfo.label} +
+
+ {concentrationInfo.description} +
+
+ + {tokenInfo?.pair && ( +
+
Paired with
+
+ {tokenInfo.pair.token1} + +
+
+ )} +
+
+
+ ); +}; + +export default CodexTopHoldersMessage; diff --git a/submodules/frontend/components/Agents/Codex/TopHolders/CodexTopHoldersMessage.types.ts b/submodules/frontend/components/Agents/Codex/TopHolders/CodexTopHoldersMessage.types.ts new file mode 100644 index 00000000..03d9e1ef --- /dev/null +++ b/submodules/frontend/components/Agents/Codex/TopHolders/CodexTopHoldersMessage.types.ts @@ -0,0 +1,44 @@ +// CodexTopHoldersMessage.types.ts + +interface TokenPair { + token0: string; + token1: string; +} + +interface Exchange { + name: string; +} + +interface TokenDetails { + address: string; + decimals: number; + name: string; + networkId: number; + symbol: string; +} + +interface TokenFilterResult { + buyCount1?: number; + high1?: string; + txnCount1?: number; + uniqueTransactions1?: number; + volume1?: string; + liquidity?: string; + marketCap?: string; + priceUSD?: string; + pair?: TokenPair; + exchanges?: Exchange[]; + token?: TokenDetails; +} + +interface TopHoldersMetadata { + success: boolean; + data: number; // Percentage owned by top 10 holders + token_info?: TokenFilterResult; +} + +interface CodexTopHoldersMessageProps { + metadata: TopHoldersMetadata; +} + +export type { CodexTopHoldersMessageProps, TopHoldersMetadata }; diff --git a/submodules/frontend/components/Agents/Codex/TopTokens/CodexTopTokensMessage.module.css b/submodules/frontend/components/Agents/Codex/TopTokens/CodexTopTokensMessage.module.css new file mode 100644 index 00000000..3b7800e1 --- /dev/null +++ b/submodules/frontend/components/Agents/Codex/TopTokens/CodexTopTokensMessage.module.css @@ -0,0 +1,186 @@ +/* CodexTopTokensMessage.module.css */ +.container { + width: 100%; + max-width: 800px; + margin: 0 auto; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.title { + font-size: 16px; + font-weight: 500; + color: #e4e4e4; +} + +.total { + color: #888; + font-size: 12px; +} + +.tokenCard { + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + padding: 12px; + margin-bottom: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.mainInfo { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 8px; +} + +.tokenInfo { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + flex: 1; +} + +.tokenImage { + width: 24px; + height: 24px; + border-radius: 50%; + flex-shrink: 0; +} + +.tokenDetails { + min-width: 0; + flex: 1; +} + +.tokenSymbol { + font-size: 12px; + font-weight: 500; + color: #e4e4e4; +} + +.tokenName { + font-size: 12px; + color: #888; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.priceSection { + text-align: right; + min-width: 120px; +} + +.currentPrice { + display: flex; + align-items: center; + gap: 2px; + font-size: 12px; + font-weight: 500; + color: #e4e4e4; + justify-content: flex-end; +} + +.contractInfo { + font-size: 11px; + color: #666; + margin-top: 2px; +} + +.metricsGrid { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + gap: 12px; + border-top: 1px solid rgba(255, 255, 255, 0.1); + padding-top: 8px; +} + +.metricsColumn { + display: flex; + flex-direction: column; + gap: 4px; +} + +.metric { + display: flex; + justify-content: space-between; + font-size: 12px; + color: #888; +} + +.metricLabel { + color: #666; +} + +.priceChanges { + display: flex; + flex-direction: column; + gap: 4px; + text-align: right; +} + +.priceChangeItem { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 4px; + font-size: 12px; + white-space: nowrap; +} + +.timeLabel { + color: #666; + width: 20px; + text-align: right; +} + +.positive { + color: #4caf50; + display: flex; + align-items: center; + gap: 2px; +} + +.negative { + color: #f44336; + display: flex; + align-items: center; + gap: 2px; +} + +.showMoreButton { + width: 100%; + padding: 8px; + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 8px; + color: #888; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 12px; + margin-top: 12px; +} + +.showMoreButton:hover { + background: rgba(255, 255, 255, 0.15); + color: #aaa; +} + +@media (max-width: 640px) { + .metricsGrid { + grid-template-columns: 1fr 1fr; + } + + .priceChanges { + display: none; + } +} diff --git a/submodules/frontend/components/Agents/Codex/TopTokens/CodexTopTokensMessage.tsx b/submodules/frontend/components/Agents/Codex/TopTokens/CodexTopTokensMessage.tsx new file mode 100644 index 00000000..035bdfa9 --- /dev/null +++ b/submodules/frontend/components/Agents/Codex/TopTokens/CodexTopTokensMessage.tsx @@ -0,0 +1,177 @@ +import React, { useState } from "react"; +import { + TrendingUp, + TrendingDown, + DollarSign, + ChevronDown, + ChevronUp, +} from "lucide-react"; +import styles from "./CodexTopTokensMessage.module.css"; +import { + CodexTopTokensMessageProps, + Token, +} from "@/components/Agents/Codex/TopTokens/CodexTopTokensMessage.types"; +import { Text } from "@chakra-ui/react"; + +const INITIAL_DISPLAY_COUNT = 3; + +const formatNumber = (num: string | number): string => { + const value = typeof num === "string" ? parseFloat(num) : num; + if (value >= 1000000000) { + return `${(value / 1000000000).toFixed(2)}B`; + } + if (value >= 1000000) { + return `${(value / 1000000).toFixed(2)}M`; + } + if (value >= 1000) { + return `${(value / 1000).toFixed(1)}K`; + } + return value.toLocaleString(); +}; + +const formatPrice = (price: number): string => { + if (price < 0.00001) { + return price.toExponential(4); + } + return price.toFixed(6); +}; + +const PriceChange: React.FC<{ change?: number; label: string }> = ({ + change, + label, +}) => { + if (change === undefined) return null; + + const displayChange = change * 100; // Convert from decimal to percentage + const isPositive = displayChange > 0; + + return ( +
+ {label} + + {isPositive ? : } + {displayChange.toFixed(2)}% + +
+ ); +}; + +export const CodexTopTokensMessage: React.FC = ({ + metadata, +}) => { + const [showAll, setShowAll] = useState(false); + + if (!metadata.success || !metadata.data) { + return Failed to load token data.; + } + + const displayTokens = showAll + ? metadata.data + : metadata.data.slice(0, INITIAL_DISPLAY_COUNT); + + return ( +
+
+ Top Trending Tokens + Total: {metadata.data.length} +
+ + {displayTokens.map((token: Token) => ( +
+
+
+ {token.imageThumbUrl && ( + {token.symbol} + )} +
+
${token.symbol}
+
{token.name}
+
+
+ +
+
+ + {formatPrice(token.price)} +
+
+ {token.networkId} β€’ {token.address.slice(0, 6)}... + {token.address.slice(-4)} +
+
+
+ +
+
+
+ Market Cap: + + {token.marketCap ? formatNumber(token.marketCap) : "N/A"} + +
+
+ Liquidity: + {formatNumber(token.liquidity)} +
+
+ Volume: + {formatNumber(token.volume)} +
+
+ +
+ {token.txnCount24 && ( +
+ 24h Txns: + {formatNumber(token.txnCount24)} +
+ )} + {token.uniqueBuys24 && ( +
+ Buyers: + {formatNumber(token.uniqueBuys24)} +
+ )} + {token.uniqueSells24 && ( +
+ Sellers: + {formatNumber(token.uniqueSells24)} +
+ )} +
+ +
+ + + + +
+
+
+ ))} + + {metadata.data.length > INITIAL_DISPLAY_COUNT && ( + + )} +
+ ); +}; + +export default CodexTopTokensMessage; diff --git a/submodules/frontend/components/Agents/Codex/TopTokens/CodexTopTokensMessage.types.ts b/submodules/frontend/components/Agents/Codex/TopTokens/CodexTopTokensMessage.types.ts new file mode 100644 index 00000000..736b1c4e --- /dev/null +++ b/submodules/frontend/components/Agents/Codex/TopTokens/CodexTopTokensMessage.types.ts @@ -0,0 +1,36 @@ +// CodexTopTokensMessage.types.ts + +interface Token { + address: string; + name: string; + symbol: string; + networkId: number; + createdAt: number; + lastTransaction: number; + imageBannerUrl?: string; + imageLargeUrl?: string; + imageSmallUrl?: string; + imageThumbUrl?: string; + price: number; + marketCap?: string; + liquidity: string; + volume: string; + priceChange1?: number; + priceChange4?: number; + priceChange12?: number; + priceChange24?: number; + txnCount24?: number; + uniqueBuys24?: number; + uniqueSells24?: number; +} + +interface TopTokensMetadata { + success: boolean; + data: Token[]; +} + +interface CodexTopTokensMessageProps { + metadata: TopTokensMetadata; +} + +export type { CodexTopTokensMessageProps, TopTokensMetadata, Token }; diff --git a/submodules/frontend/components/Agents/CryptoData/CryptoChartMessage.tsx b/submodules/frontend/components/Agents/CryptoData/CryptoChartMessage.tsx new file mode 100644 index 00000000..cf83363a --- /dev/null +++ b/submodules/frontend/components/Agents/CryptoData/CryptoChartMessage.tsx @@ -0,0 +1,49 @@ +import React, { useState } from "react"; +import { Box, IconButton, Collapse, Text } from "@chakra-ui/react"; +import { BarChart2 } from "lucide-react"; +import ReactMarkdown from "react-markdown"; + +const TradingViewWidget = React.lazy(() => import("./TradingViewWidget")); + +const CryptoChartMessage = ({ + content, + metadata, +}: { + content: any; + metadata: any; +}) => { + const [show, setShow] = useState(true); + + return ( + + + {content} + {metadata?.coinId && ( + setShow(!show)} + variant="ghost" + size="sm" + color="gray.400" + _hover={{ color: "blue.400" }} + aria-label="Toggle chart" + icon={} + /> + )} + + + + + + Loading chart...}> + {metadata?.coinId && ( + + )} + + + + + + ); +}; + +export default CryptoChartMessage; diff --git a/submodules/moragents_dockers/frontend/components/Widgets/TradingViewWidget.tsx b/submodules/frontend/components/Agents/CryptoData/TradingViewWidget.tsx old mode 100644 new mode 100755 similarity index 95% rename from submodules/moragents_dockers/frontend/components/Widgets/TradingViewWidget.tsx rename to submodules/frontend/components/Agents/CryptoData/TradingViewWidget.tsx index 11255c40..0bb31565 --- a/submodules/moragents_dockers/frontend/components/Widgets/TradingViewWidget.tsx +++ b/submodules/frontend/components/Agents/CryptoData/TradingViewWidget.tsx @@ -42,7 +42,7 @@ const TradingViewWidget = memo(({ symbol }: TradingViewWidgetProps) => { }, [symbol]); return ( -
+
{ + const toast = useToast(); + const { isOpen, onToggle } = useDisclosure(); + const [showForm, setShowForm] = useState(false); + + const [config, setConfig] = useState({ + originToken: "usdc", + destinationToken: "weth", + stepSize: "100", + frequency: "weekly", + pauseOnVolatility: false, + }); + + const handleSave = async () => { + // Validation checks + if (config.originToken === config.destinationToken) { + toast({ + title: "Invalid Configuration", + description: "Origin and destination tokens must be different", + status: "error", + duration: 3000, + isClosable: true, + }); + return; + } + + const stepSizeNum = parseFloat(config.stepSize); + if (isNaN(stepSizeNum) || stepSizeNum <= 0) { + toast({ + title: "Invalid Step Size", + description: "Step size must be a valid number greater than 0", + status: "error", + duration: 3000, + isClosable: true, + }); + return; + } + + try { + const response = await fetch( + "http://localhost:8888/dca/create_strategy", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + ...config, + stepSize: stepSizeNum, + totalInvestmentAmount: config.totalInvestmentAmount + ? parseFloat(config.totalInvestmentAmount) + : undefined, + maxPurchaseAmount: config.maxPurchaseAmount + ? parseFloat(config.maxPurchaseAmount) + : undefined, + priceThreshold: config.priceThreshold + ? parseFloat(config.priceThreshold) + : undefined, + }), + } + ); + + const data = await response.json(); + + if (data.status === "success") { + toast({ + title: "Strategy Created", + description: "Your DCA strategy has been created successfully", + status: "success", + duration: 3000, + isClosable: true, + }); + setShowForm(false); + } else { + throw new Error(data.message); + } + } catch (error) { + toast({ + title: "Strategy Creation Failed", + description: + error instanceof Error + ? error.message + : "Failed to create DCA strategy", + status: "error", + duration: 3000, + isClosable: true, + }); + } + }; + + const calculateTimeToCompletion = () => { + const stepSizeNum = parseFloat(config.stepSize); + const totalInvestmentNum = config.totalInvestmentAmount + ? parseFloat(config.totalInvestmentAmount) + : undefined; + + if ( + !totalInvestmentNum || + isNaN(totalInvestmentNum) || + !stepSizeNum || + isNaN(stepSizeNum) || + stepSizeNum === 0 + ) { + return "Ongoing strategy"; + } + + const totalPurchases = Math.ceil(totalInvestmentNum / stepSizeNum); + const frequencyInDays = + { + hourly: 1 / 24, + daily: 1, + weekly: 7, + biweekly: 14, + monthly: 30, + }[config.frequency] || 0; + + const daysToComplete = totalPurchases * frequencyInDays; + + if (daysToComplete < 1) return "Less than a day"; + if (daysToComplete < 30) return `${Math.ceil(daysToComplete)} days`; + if (daysToComplete < 365) return `${Math.ceil(daysToComplete / 30)} months`; + return `${Math.round((daysToComplete / 365) * 10) / 10} years`; + }; + + return ( + + + {content} + setShowForm(!showForm)} + variant="ghost" + size="sm" + color="gray.400" + _hover={{ color: "blue.400" }} + aria-label="Configure DCA" + icon={} + /> + + + + + + {/* From / To selection */} + + + From (Origin) + + + + + To (Destination) + + + + + {/* Basic settings */} + + Investment Step Size + + setConfig({ ...config, stepSize: e.target.value }) + } + /> + + + + + Total Investment Amount (Optional) + + + setConfig({ + ...config, + totalInvestmentAmount: e.target.value, + }) + } + /> + + + + Frequency + + + + {/* Advanced Settings Toggle */} + + + + + + + + + Maximum Purchase Amount (Optional) + + + setConfig({ + ...config, + maxPurchaseAmount: e.target.value, + }) + } + placeholder="No maximum" + /> + + + + + Price Threshold (Optional) + + + setConfig({ ...config, priceThreshold: e.target.value }) + } + placeholder="Only buy below this price" + /> + + + + setConfig({ + ...config, + pauseOnVolatility: e.target.checked, + }) + } + color="white" + sx={{ + "[data-checked]": { + backgroundColor: "white !important", + borderColor: "white !important", + }, + "& .chakra-checkbox__control": { + borderColor: "white", + }, + }} + > + Pause strategy during high volatility + + + + + + Estimated time to completion: {calculateTimeToCompletion()} + + + + + + + + ); +}; + +export default DCAMessage; diff --git a/submodules/frontend/components/Agents/Dexscreener/BoostedTokens/BoostedTokensMessage.module.css b/submodules/frontend/components/Agents/Dexscreener/BoostedTokens/BoostedTokensMessage.module.css new file mode 100644 index 00000000..bd204469 --- /dev/null +++ b/submodules/frontend/components/Agents/Dexscreener/BoostedTokens/BoostedTokensMessage.module.css @@ -0,0 +1,221 @@ +/* BoostedTokensMessage.module.css */ + +.container { + width: 100%; + max-width: 900px; + margin: 0 auto; + color: #e4e4e4; + font-size: 14px; +} + +.header { + margin-bottom: 16px; +} + +.titleSection { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; +} + +.titleIcon { + color: #ffd700; +} + +.title { + font-weight: 600; + font-size: 16px; +} + +.chainBadge { + background-color: rgba(255, 255, 255, 0.1); + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + color: #aaa; +} + +.subtitle { + color: #aaa; + font-size: 12px; +} + +.table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + margin-bottom: 20px; +} + +.headerRow { + height: 40px; +} + +.headerRow th { + text-align: left; + color: #888; + font-weight: 500; + padding: 8px; + font-size: 13px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.row { + height: 60px; + transition: background-color 0.2s; +} + +.row:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.row td { + padding: 8px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.rankCell { + width: 40px; + color: #aaa; + font-size: 13px; + text-align: center; +} + +.tokenCell { + width: 50px; +} + +.tokenImg { + width: 32px; + height: 32px; + border-radius: 50%; + object-fit: cover; +} + +.addressCell { + width: 140px; +} + +.addressWrapper { + display: flex; + align-items: center; + gap: 4px; +} + +.address { + font-family: monospace; + color: #e4e4e4; +} + +.chainId { + color: #888; + font-size: 12px; + margin-top: 4px; +} + +.copyButton { + background: transparent; + border: none; + color: #888; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 2px; +} + +.copyButton:hover { + color: #e4e4e4; +} + +.description { + color: #e4e4e4; + font-size: 13px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + line-height: 1.3; + max-width: 250px; +} + +.boostCell { + width: 120px; +} + +.boostAmount { + display: flex; + align-items: center; + gap: 4px; + color: #e4e4e4; + font-weight: 500; +} + +.boostIcon { + color: #4caf50; +} + +.totalBoost { + color: #888; + font-size: 12px; + margin-top: 4px; +} + +.linksCell { + width: 120px; +} + +.links { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.link { + color: #888; + transition: color 0.2s; + display: flex; +} + +.link:hover { + color: #e4e4e4; +} + +.noDataCell { + text-align: center; + padding: 24px; + color: #888; +} + +.showMoreButton { + width: 100%; + padding: 8px 16px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + color: #aaa; + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: all 0.2s; +} + +.showMoreButton:hover { + background: rgba(255, 255, 255, 0.1); +} + +@media (max-width: 768px) { + .table { + display: block; + overflow-x: auto; + } + + .description { + max-width: 150px; + } +} diff --git a/submodules/frontend/components/Agents/Dexscreener/BoostedTokens/BoostedTokensMessage.tsx b/submodules/frontend/components/Agents/Dexscreener/BoostedTokens/BoostedTokensMessage.tsx new file mode 100644 index 00000000..f728a2f5 --- /dev/null +++ b/submodules/frontend/components/Agents/Dexscreener/BoostedTokens/BoostedTokensMessage.tsx @@ -0,0 +1,192 @@ +import React, { useState } from "react"; +import { Copy, Award, TrendingUp } from "lucide-react"; +import { + FaTwitter, + FaTelegram, + FaGlobe, + FaLink, + FaChevronDown, + FaChevronUp, +} from "react-icons/fa"; +import { SiCoinmarketcap } from "react-icons/si"; +import styles from "./BoostedTokensMessage.module.css"; +import { BoostedTokensMessageProps } from "./BoostedTokensMessage.types"; +import { Text } from "@chakra-ui/react"; +import { Image } from "@chakra-ui/react"; + +const INITIAL_DISPLAY_COUNT = 5; + +export const BoostedTokensMessage: React.FC = ({ + metadata, +}) => { + const [showAll, setShowAll] = useState(false); + + const truncateAddress = (address: string) => { + if (!address) return ""; + return `${address.slice(0, 4)}...${address.slice(-4)}`; + }; + + const handleCopy = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + } catch (err) { + console.error("Failed to copy:", err); + } + }; + + const formatBoostAmount = (amount: number) => { + if (amount >= 1000000) { + return `${(amount / 1000000).toFixed(2)}M`; + } + if (amount >= 1000) { + return `${(amount / 1000).toFixed(1)}K`; + } + return amount.toFixed(2); + }; + + const getLinkIcon = (labelOrType = "") => { + const val = labelOrType.toLowerCase(); + if (val.includes("twitter")) return ; + if (val.includes("telegram")) return ; + if (val.includes("website")) return ; + if (val.includes("coinmarketcap") || val.includes("cmc")) + return ; + return ; + }; + + const { tokens = [] } = metadata || {}; + const displayTokens = showAll + ? tokens + : tokens.slice(0, INITIAL_DISPLAY_COUNT); + + // Calculate chain display name + const chainName = metadata?.chain_id + ? metadata.chain_id.charAt(0).toUpperCase() + metadata.chain_id.slice(1) + : "All Chains"; + + return ( +
+
+
+ + Boosted Tokens + {chainName} +
+ + Tokens with active promotional boosts on DexScreener + +
+ + + + + + + + + + + + + + {displayTokens.map((token: any, index: number) => ( + + + + + + + + + ))} + + {tokens.length === 0 && ( + + + + )} + +
AddressDescriptionBoost AmountLinks
#{index + 1} + {token.icon && ( + + + + )} + +
+ + {truncateAddress(token.tokenAddress)} + + +
+
{token.chainId}
+
+
+ {token.description || "-"} +
+
+
+ + {formatBoostAmount(token.amount)} +
+
+ Total: {formatBoostAmount(token.totalAmount)} +
+
+
+ + + + {token.links?.map((link: any, idx: number) => ( + + {getLinkIcon(link.label || link.type)} + + ))} +
+
+ No boosted tokens found + {metadata?.chain_id ? ` for ${metadata.chain_id}` : ""} +
+ + {tokens.length > INITIAL_DISPLAY_COUNT && ( + + )} +
+ ); +}; + +export default BoostedTokensMessage; diff --git a/submodules/frontend/components/Agents/Dexscreener/BoostedTokens/BoostedTokensMessage.types.ts b/submodules/frontend/components/Agents/Dexscreener/BoostedTokens/BoostedTokensMessage.types.ts new file mode 100644 index 00000000..6ad021c6 --- /dev/null +++ b/submodules/frontend/components/Agents/Dexscreener/BoostedTokens/BoostedTokensMessage.types.ts @@ -0,0 +1,35 @@ +// BoostedTokensMessage.types.ts + +interface TokenLink { + type: string; + label: string; + url: string; +} + +interface BoostedToken { + url: string; + chainId: string; + tokenAddress: string; + icon?: string | null; + header?: string | null; + description?: string | null; + links?: TokenLink[] | null; + amount: number; + totalAmount: number; +} + +interface BoostedTokensMetadata { + chain_id?: string | null; + tokens: BoostedToken[]; +} + +interface BoostedTokensMessageProps { + metadata: BoostedTokensMetadata; +} + +export type { + BoostedTokensMessageProps, + BoostedTokensMetadata, + BoostedToken, + TokenLink, +}; diff --git a/submodules/frontend/components/Agents/Dexscreener/DexPairs/DexPairsMessage.module.css b/submodules/frontend/components/Agents/Dexscreener/DexPairs/DexPairsMessage.module.css new file mode 100644 index 00000000..f5508a3a --- /dev/null +++ b/submodules/frontend/components/Agents/Dexscreener/DexPairs/DexPairsMessage.module.css @@ -0,0 +1,300 @@ +/* DexPairsMessage.module.css */ + +.container { + width: 100%; + max-width: 900px; + margin: 0 auto; + color: #e4e4e4; + font-size: 14px; +} + +.header { + margin-bottom: 16px; +} + +.title { + font-weight: 600; + font-size: 16px; + margin-bottom: 4px !important; +} + +.subtitle { + color: #aaa; + font-size: 12px; +} + +.pairList { + display: flex; + flex-direction: column; + gap: 16px; + margin-bottom: 16px; +} + +.pairCard { + background: rgba(255, 255, 255, 0.05); + border-radius: 12px; + padding: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); + transition: transform 0.2s, box-shadow 0.2s; +} + +.pairCard:hover { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2); +} + +.pairHeader { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 12px; +} + +.pairInfo { + display: flex; + flex-direction: column; + gap: 4px; +} + +.pairTokens { + font-size: 16px; + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; +} + +.baseToken { + color: #e4e4e4; +} + +.separator { + color: #888; +} + +.quoteToken { + color: #aaa; +} + +.pairDetails { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; +} + +.dex { + color: #888; +} + +.chainBadge { + background-color: rgba(255, 255, 255, 0.1); + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + color: #aaa; +} + +.pairLink { + color: #888; + margin-top: 4px; + transition: color 0.2s; +} + +.pairLink:hover { + color: #e4e4e4; +} + +.priceSection { + margin-bottom: 16px; + padding-bottom: 16px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.priceContainer { + display: flex; + align-items: center; + gap: 6px; +} + +.priceIcon { + color: #aaa; +} + +.price { + font-size: 20px; + font-weight: 500; +} + +.priceChange { + display: flex; + align-items: center; + gap: 2px; + font-size: 13px; + margin-left: 8px; +} + +.positive { + color: #4caf50; +} + +.negative { + color: #f44336; +} + +.statsGrid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-bottom: 16px; +} + +.statItem { + display: flex; + align-items: center; + gap: 6px; +} + +.statIcon { + color: #aaa; +} + +.statLabel { + color: #888; + font-size: 13px; +} + +.statValue { + color: #e4e4e4; + font-weight: 500; + margin-left: auto; +} + +.txStats { + grid-column: span 2; + display: flex; + justify-content: space-between; + align-items: center; + background: rgba(255, 255, 255, 0.03); + padding: 8px 12px; + border-radius: 8px; + margin-top: 4px; +} + +.txCount { + display: flex; + flex-direction: column; + gap: 2px; +} + +.txLabel { + color: #888; + font-size: 12px; +} + +.txValue { + font-weight: 500; +} + +.buysSells { + display: flex; + gap: 12px; +} + +.txBuys, +.txSells { + display: flex; + align-items: center; + gap: 4px; + font-size: 13px; +} + +.buyIcon { + color: #4caf50; + font-size: 10px; +} + +.sellIcon { + color: #f44336; + font-size: 10px; +} + +.linksSection { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.linksList { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.linkItem { + background: rgba(255, 255, 255, 0.05); + border-radius: 4px; + padding: 4px 8px; + display: flex; + align-items: center; + gap: 6px; + color: #aaa; + font-size: 12px; + transition: background-color 0.2s, color 0.2s; +} + +.linkItem:hover { + background: rgba(255, 255, 255, 0.1); + color: #e4e4e4; +} + +.linkLabel { + display: none; +} + +.noPairs { + text-align: center; + padding: 24px; + color: #888; + background: rgba(255, 255, 255, 0.02); + border-radius: 12px; +} + +.showMoreButton { + width: 100%; + padding: 8px 16px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + color: #aaa; + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: all 0.2s; +} + +.showMoreButton:hover { + background: rgba(255, 255, 255, 0.1); +} + +@media (max-width: 768px) { + .statsGrid { + grid-template-columns: 1fr; + gap: 8px; + } + + .txStats { + grid-column: span 1; + } + + .linkItem { + padding: 4px 6px; + } + + .linkLabel { + display: none; + } +} diff --git a/submodules/frontend/components/Agents/Dexscreener/DexPairs/DexPairsMessage.tsx b/submodules/frontend/components/Agents/Dexscreener/DexPairs/DexPairsMessage.tsx new file mode 100644 index 00000000..dff20baf --- /dev/null +++ b/submodules/frontend/components/Agents/Dexscreener/DexPairs/DexPairsMessage.tsx @@ -0,0 +1,249 @@ +import React, { useState } from "react"; +import { + ExternalLink, + DollarSign, + BarChart2, + ArrowUpRight, + ArrowDownRight, + Droplet, +} from "lucide-react"; +import { + FaTwitter, + FaTelegram, + FaGlobe, + FaLink, + FaChevronDown, + FaChevronUp, + FaCircle, +} from "react-icons/fa"; +import { SiCoinmarketcap, SiMedium, SiDiscord, SiGithub } from "react-icons/si"; +import styles from "./DexPairsMessage.module.css"; +import { DexPairsMessageProps } from "./DexPairsMessage.types"; +import { Text } from "@chakra-ui/react"; + +const INITIAL_DISPLAY_COUNT = 4; + +export const DexPairsMessage: React.FC = ({ + metadata, +}) => { + const [showAll, setShowAll] = useState(false); + + const formatPrice = (price: number | null | undefined) => { + if (price === null || price === undefined) return "N/A"; + + if (price < 0.00001) { + return price.toExponential(4); + } + + return price.toFixed(Math.min(6, getDecimalPlaces(price) + 2)); + }; + + const getDecimalPlaces = (num: number) => { + if (num === 0) return 0; + const match = num.toString().match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); + if (!match) return 0; + return Math.max( + 0, + (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0) + ); + }; + + const formatNumber = (value: number | string | undefined | null) => { + if (value === undefined || value === null) return "N/A"; + + const num = typeof value === "string" ? parseFloat(value) : value; + + if (isNaN(num)) return "N/A"; + + if (num >= 1_000_000_000) { + return `$${(num / 1_000_000_000).toFixed(2)}B`; + } + if (num >= 1_000_000) { + return `$${(num / 1_000_000).toFixed(2)}M`; + } + if (num >= 1_000) { + return `$${(num / 1_000).toFixed(1)}K`; + } + return `$${num.toFixed(2)}`; + }; + + const getSocialIcon = (type = "") => { + const lowerType = type.toLowerCase(); + if (lowerType.includes("twitter")) return ; + if (lowerType.includes("telegram")) return ; + if (lowerType.includes("website")) return ; + if (lowerType.includes("medium")) return ; + if (lowerType.includes("discord")) return ; + if (lowerType.includes("github")) return ; + if (lowerType.includes("coinmarketcap") || lowerType.includes("cmc")) + return ; + return ; + }; + + const { pairs = [] } = metadata || {}; + const displayPairs = showAll ? pairs : pairs.slice(0, INITIAL_DISPLAY_COUNT); + + return ( +
+
+ DexScreener Trading Pairs + + Found {pairs.length} popular DEX trading pairs + +
+ +
+ {displayPairs.map((pair, index) => ( +
+ + +
+
+ + + {formatPrice(pair.priceUsd)} + + {pair.priceChange?.h24 !== undefined && ( + = 0 + ? styles.positive + : styles.negative + }`} + > + {Number(pair.priceChange.h24) >= 0 ? ( + + ) : ( + + )} + {Math.abs(Number(pair.priceChange.h24)).toFixed(2)}% + + )} +
+
+ +
+
+ + 24h Volume: + + {formatNumber(pair.volume?.h24)} + +
+
+ + Liquidity: + + {formatNumber(pair.liquidity?.usd)} + +
+ {pair.txns?.h24 && ( +
+
+ 24h Txns: + + {(pair.txns.h24.buys || 0) + (pair.txns.h24.sells || 0)} + +
+
+
+ + {pair.txns.h24.buys || 0} +
+
+ + {pair.txns.h24.sells || 0} +
+
+
+ )} +
+ + {pair.baseToken?.links && pair.baseToken.links.length > 0 && ( + + )} +
+ ))} + + {pairs.length === 0 && ( +
+ No DEX pairs found matching your search. +
+ )} +
+ + {pairs.length > INITIAL_DISPLAY_COUNT && ( + + )} +
+ ); +}; + +export default DexPairsMessage; diff --git a/submodules/frontend/components/Agents/Dexscreener/DexPairs/DexPairsMessage.types.ts b/submodules/frontend/components/Agents/Dexscreener/DexPairs/DexPairsMessage.types.ts new file mode 100644 index 00000000..224aa30e --- /dev/null +++ b/submodules/frontend/components/Agents/Dexscreener/DexPairs/DexPairsMessage.types.ts @@ -0,0 +1,89 @@ +// DexPairsMessage.types.ts + +interface TokenLink { + type: string; + label: string; + url: string; +} + +interface TokenProfile { + url: string; + chainId: string; + tokenAddress: string; + icon?: string | null; + header?: string | null; + description?: string | null; + links?: TokenLink[] | null; + symbol?: string; +} + +interface Transaction { + buys: number; + sells: number; +} + +interface VolumeData { + h24?: number; + h6?: number; + h1?: number; + m5?: number; +} + +interface PriceChangeData { + h24?: string | number; + h6?: string | number; + h1?: string | number; + m5?: string | number; +} + +interface LiquidityData { + usd?: number | string; + base?: number | string; + quote?: number | string; +} + +interface DexPair { + chainId: string; + dexId: string; + url: string; + pairAddress: string; + baseToken: TokenProfile; + quoteToken: TokenProfile; + priceNative: number; + priceUsd?: number | null; + txns?: { + h24?: Transaction; + h6?: Transaction; + h1?: Transaction; + m5?: Transaction; + }; + volume?: VolumeData; + priceChange?: PriceChangeData; + liquidity?: LiquidityData; + fdv?: number | null; + pairCreatedAt?: number | null; + info?: { + websites?: any[]; + socials?: any[]; + }; +} + +interface DexPairsMetadata { + pairs: DexPair[]; +} + +interface DexPairsMessageProps { + metadata: DexPairsMetadata; +} + +export type { + DexPairsMessageProps, + DexPairsMetadata, + DexPair, + TokenProfile, + TokenLink, + Transaction, + VolumeData, + PriceChangeData, + LiquidityData, +}; diff --git a/submodules/frontend/components/Agents/Dexscreener/TopTokens/TopTokensMessage.module.css b/submodules/frontend/components/Agents/Dexscreener/TopTokens/TopTokensMessage.module.css new file mode 100644 index 00000000..000a6390 --- /dev/null +++ b/submodules/frontend/components/Agents/Dexscreener/TopTokens/TopTokensMessage.module.css @@ -0,0 +1,188 @@ +.container { + width: 100%; + overflow-x: auto; +} + +.table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + margin-top: 12px; +} + +.headerRow th { + text-align: left; + padding: 12px 16px; + color: #666; + font-weight: 500; + font-size: 12px; + border-bottom: 1px solid #1a1a1a; +} + +.rankCell { + width: 32px; + padding-right: 0 !important; + color: #666; +} + +.tokenCell { + width: 48px; + padding: 12px 8px !important; +} + +.tokenImg { + width: 32px; + height: 32px; + border-radius: 50%; + cursor: pointer; +} + +.addressCell { + width: 140px; + padding: 12px 16px; +} + +.addressWrapper { + display: flex; + align-items: center; + gap: 8px; +} + +.address { + font-family: monospace; + font-size: 12px; + color: #888; +} + +.copyButton { + color: #666; + background: none; + border: none; + padding: 0; + display: flex; + align-items: center; + cursor: pointer; +} + +.copyButton:hover { + color: #888; +} + +.description { + padding: 12px 16px; + color: #888; + font-size: 12px; + line-height: 1.4; + max-height: 5em; + overflow: hidden; + text-overflow: ellipsis; + white-space: normal; + display: block; +} + +.linksCell { + width: 120px; + padding: 12px 16px; +} + +.links { + display: flex; + align-items: center; + gap: 12px; +} + +.link { + color: #4d9eff; + display: flex; + align-items: center; +} + +.link:hover { + color: #66adff; +} + +.row { + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.row:last-child { + border-bottom: none; +} + +/* Add these new mobile styles */ +@media (max-width: 768px) { + .container { + overflow-x: hidden; + } + + .table { + display: block; + } + + .headerRow { + display: none; + } + + .row { + display: grid; + grid-template-columns: auto 1fr; + gap: 4px; + } + + .rankCell { + grid-column: 1; + grid-row: 1; + width: auto; + padding: 4px 12px !important; + } + + .tokenCell { + grid-column: 1; + grid-row: 2/5; + width: auto; + padding: 0 8px !important; + } + + .addressCell { + grid-column: 2; + grid-row: 1; + width: auto; + padding: 4px 8px; + } + + .description { + grid-column: 2; + grid-row: 2; + padding: 4px 8px; + max-height: none; + } + + .linksCell { + grid-column: 2; + grid-row: 3; + width: auto; + padding: 4px 8px; + } +} + +/* Add show more button styles */ +.showMoreButton { + width: 100%; + margin-top: 16px; + padding: 8px; + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 8px; + color: #888; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 12px; +} + +.showMoreButton:hover { + background: rgba(255, 255, 255, 0.15); + color: #aaa; +} diff --git a/submodules/frontend/components/Agents/Dexscreener/TopTokens/TopTokensMessage.tsx b/submodules/frontend/components/Agents/Dexscreener/TopTokens/TopTokensMessage.tsx new file mode 100644 index 00000000..5dfc3ca8 --- /dev/null +++ b/submodules/frontend/components/Agents/Dexscreener/TopTokens/TopTokensMessage.tsx @@ -0,0 +1,137 @@ +// TopTokensMessage.tsx +import React, { useState } from "react"; +import { Copy } from "lucide-react"; +import { + FaTwitter, + FaTelegram, + FaGlobe, + FaLink, + FaChevronDown, + FaChevronUp, +} from "react-icons/fa"; +import { SiCoinmarketcap } from "react-icons/si"; +import styles from "./TopTokensMessage.module.css"; +import { TopTokensMessageProps } from "./TopTokensMessage.types"; +import { Text } from "@chakra-ui/react"; +import { Image } from "@chakra-ui/react"; + +const INITIAL_DISPLAY_COUNT = 5; + +export const TopTokensMessage: React.FC = ({ + metadata, +}) => { + const [showAll, setShowAll] = useState(false); + + const truncateAddress = (address: string) => { + if (!address) return ""; + return `${address.slice(0, 4)}...${address.slice(-4)}`; + }; + + const handleCopy = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + } catch (err) { + console.error("Failed to copy:", err); + } + }; + + const getLinkIcon = (labelOrType = "") => { + const val = labelOrType.toLowerCase(); + if (val.includes("twitter")) return ; + if (val.includes("telegram")) return ; + if (val.includes("website")) return ; + if (val.includes("coinmarketcap") || val.includes("cmc")) + return ; + return ; + }; + + const { tokens = [] } = metadata || {}; + const displayTokens = showAll + ? tokens + : tokens.slice(0, INITIAL_DISPLAY_COUNT); + + return ( +
+ Here are the top tokens from Dexscreener! + + + + + + + + + + + + {displayTokens.map((token: any, index: number) => ( + + + + + + + + ))} + +
AddressDescriptionLinks
#{index + 1} + {token.icon && ( + + + + )} + +
+ + {truncateAddress(token.tokenAddress)} + + +
+
+
+ {token.description || "-"} +
+
+
+ {token.links?.map((link: any, idx: number) => ( + + {getLinkIcon(link.label || link.type)} + + ))} +
+
+ + {tokens.length > INITIAL_DISPLAY_COUNT && ( + + )} +
+ ); +}; diff --git a/submodules/frontend/components/Agents/Dexscreener/TopTokens/TopTokensMessage.types.ts b/submodules/frontend/components/Agents/Dexscreener/TopTokens/TopTokensMessage.types.ts new file mode 100644 index 00000000..a2b374d2 --- /dev/null +++ b/submodules/frontend/components/Agents/Dexscreener/TopTokens/TopTokensMessage.types.ts @@ -0,0 +1,24 @@ +interface Link { + label?: string; + type?: string; + url: string; +} + +interface Token { + url?: string; + tokenAddress: string; + icon?: string; + description?: string; + links?: Link[]; +} + +interface MetadataProps { + chain_id?: string; + tokens?: Token[]; +} + +interface TopTokensMessageProps { + metadata: any; +} + +export type { Link, Token, MetadataProps, TopTokensMessageProps }; diff --git a/submodules/frontend/components/Agents/Elfa/AccountSmartStats/AccountSmartStatsMessage.module.css b/submodules/frontend/components/Agents/Elfa/AccountSmartStats/AccountSmartStatsMessage.module.css new file mode 100644 index 00000000..9bebec94 --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/AccountSmartStats/AccountSmartStatsMessage.module.css @@ -0,0 +1,114 @@ +/* ElfaAccountSmartStatsMessage.module.css */ + +.container { + width: 100%; + max-width: 800px; + margin: 0 auto; + color: #e4e4e4; +} + +.header { + margin-bottom: 16px; +} + +.title { + font-size: 16px; + font-weight: 500; +} + +.statsGrid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + margin-bottom: 24px; +} + +.statCard { + background: rgba(255, 255, 255, 0.05); + border-radius: 12px; + padding: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + flex-direction: column; +} + +.statHeader { + display: flex; + align-items: center; + gap: 8px; + color: #aaa; + font-size: 14px; + margin-bottom: 12px; +} + +.statValue { + font-size: 24px; + font-weight: 600; + margin-bottom: 8px; +} + +.statDescription { + font-size: 12px; + color: #888; + line-height: 1.4; +} + +.insightSection { + background: rgba(255, 255, 255, 0.05); + border-radius: 12px; + padding: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.insightHeader { + font-size: 14px; + font-weight: 500; + margin-bottom: 16px; +} + +.scoreContainer { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 16px; +} + +.scoreBar { + flex: 1; + height: 8px; + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; + overflow: hidden; +} + +.scoreProgress { + height: 100%; + background: linear-gradient(90deg, #4f46e5, #6366f1); + border-radius: 4px; +} + +.scoreValue { + font-weight: 500; + font-size: 14px; +} + +.insightText { + font-size: 13px; + color: #aaa; + line-height: 1.5; +} + +@media (max-width: 640px) { + .statsGrid { + grid-template-columns: 1fr; + gap: 12px; + } + + .statCard { + padding: 12px; + } + + .insightSection { + padding: 12px; + } +} diff --git a/submodules/frontend/components/Agents/Elfa/AccountSmartStats/AccountSmartStatsMessage.tsx b/submodules/frontend/components/Agents/Elfa/AccountSmartStats/AccountSmartStatsMessage.tsx new file mode 100644 index 00000000..82157012 --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/AccountSmartStats/AccountSmartStatsMessage.tsx @@ -0,0 +1,104 @@ +import React from "react"; +import { Users, BarChart3, Percent } from "lucide-react"; +import styles from "./AccountSmartStatsMessage.module.css"; +import { ElfaAccountSmartStatsMessageProps } from "./AccountSmartStatsMessage.types"; +import { Text } from "@chakra-ui/react"; + +const formatNumber = (num: number): string => { + if (num >= 1000000) { + return `${(num / 1000000).toFixed(1)}M`; + } + if (num >= 1000) { + return `${(num / 1000).toFixed(1)}K`; + } + return num.toString(); +}; + +export const ElfaAccountSmartStatsMessage: React.FC< + ElfaAccountSmartStatsMessageProps +> = ({ metadata }) => { + if (!metadata.success || !metadata.data) { + return Failed to load account statistics.; + } + + const stats = metadata.data; + + // Calculate engagement quality score (simplified example) + const engagementQuality = Math.min( + 100, + Math.round( + stats.followerEngagementRatio * 100 + stats.averageEngagement / 10 + ) + ); + + return ( +
+
+ Account Smart Statistics +
+ +
+
+
+ + Smart Following +
+
+ {formatNumber(stats.smartFollowingCount)} +
+
+ Number of high-value accounts following this profile +
+
+ +
+
+ + Average Engagement +
+
+ {stats.averageEngagement.toFixed(2)} +
+
+ Average interactions per post +
+
+ +
+
+ + Follower Engagement Ratio +
+
+ {(stats.followerEngagementRatio * 100).toFixed(2)}% +
+
+ Percentage of followers who engage regularly +
+
+
+ +
+
Engagement Quality Score
+
+
+
+
+
{engagementQuality}/100
+
+
+ {engagementQuality >= 80 + ? "Excellent engagement quality with high follower interaction." + : engagementQuality >= 60 + ? "Good engagement quality with active follower base." + : engagementQuality >= 40 + ? "Moderate engagement quality with potential for improvement." + : "Low engagement quality. Consider improving content strategy."} +
+
+
+ ); +}; diff --git a/submodules/frontend/components/Agents/Elfa/AccountSmartStats/AccountSmartStatsMessage.types.ts b/submodules/frontend/components/Agents/Elfa/AccountSmartStats/AccountSmartStatsMessage.types.ts new file mode 100644 index 00000000..93f4798a --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/AccountSmartStats/AccountSmartStatsMessage.types.ts @@ -0,0 +1,22 @@ +// ElfaAccountSmartStatsMessage.types.ts + +interface AccountSmartStats { + followerEngagementRatio: number; + averageEngagement: number; + smartFollowingCount: number; +} + +interface AccountSmartStatsMetadata { + success: boolean; + data: AccountSmartStats; +} + +interface ElfaAccountSmartStatsMessageProps { + metadata: AccountSmartStatsMetadata; +} + +export type { + ElfaAccountSmartStatsMessageProps, + AccountSmartStats, + AccountSmartStatsMetadata, +}; diff --git a/submodules/frontend/components/Agents/Elfa/Mentions/MentionsMessage.module.css b/submodules/frontend/components/Agents/Elfa/Mentions/MentionsMessage.module.css new file mode 100644 index 00000000..a9637196 --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/Mentions/MentionsMessage.module.css @@ -0,0 +1,175 @@ +/* ElfaMentionsMessage.module.css */ + +.container { + width: 100%; + max-width: 800px; + margin: 0 auto; +} + +.mentionCard { + background: rgba(255, 255, 255, 0.05); + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.accountInfo { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.accountDetails { + display: flex; + align-items: center; + gap: 12px; +} + +.profileImage { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; +} + +.profilePlaceholder { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: rgba(255, 255, 255, 0.1); +} + +.accountName { + font-weight: 500; + color: #e4e4e4; + display: flex; + align-items: center; + gap: 4px; +} + +.verifiedBadge { + display: inline-flex; + align-items: center; + justify-content: center; + color: rgb(29, 155, 240); +} + +.username { + color: #888; + font-size: 12px; +} + +.followInfo { + color: #888; + font-size: 12px; +} + +.content { + color: #e4e4e4; + font-size: 14px; + line-height: 1.5; + margin-bottom: 12px; + white-space: pre-wrap; +} + +.metricsList { + display: flex; + gap: 16px; + flex-wrap: wrap; + margin-bottom: 12px; + padding-bottom: 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.metric { + display: flex; + align-items: center; + gap: 4px; + color: #888; + font-size: 13px; +} + +.footer { + display: flex; + justify-content: space-between; + align-items: center; +} + +.timestamp { + color: #666; + font-size: 12px; +} + +.viewOriginal { + display: flex; + align-items: center; + gap: 4px; + color: rgb(29, 155, 240); + font-size: 12px; + text-decoration: none; +} + +.viewOriginal:hover { + text-decoration: underline; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.title { + font-size: 16px; + font-weight: 500; + color: #e4e4e4; +} + +.total { + color: #888; + font-size: 12px; +} + +.showMoreButton { + width: 100%; + padding: 8px; + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 8px; + color: #888; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 12px; + margin-top: 16px; +} + +.showMoreButton:hover { + background: rgba(255, 255, 255, 0.15); + color: #aaa; +} + +@media (max-width: 640px) { + .mentionCard { + padding: 12px; + } + + .metricsList { + gap: 12px; + } + + .accountDetails { + gap: 8px; + } + + .profileImage, + .profilePlaceholder { + width: 32px; + height: 32px; + } +} diff --git a/submodules/frontend/components/Agents/Elfa/Mentions/MentionsMessage.tsx b/submodules/frontend/components/Agents/Elfa/Mentions/MentionsMessage.tsx new file mode 100644 index 00000000..c9935244 --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/Mentions/MentionsMessage.tsx @@ -0,0 +1,163 @@ +import React, { useState } from "react"; +import { + Eye, + MessageCircle, + Heart, + Repeat, + ChevronDown, + ChevronUp, + Bookmark, + ExternalLink, + CheckCircle, +} from "lucide-react"; +import Image from "next/image"; +import styles from "./MentionsMessage.module.css"; +import { ElfaMentionsMessageProps } from "./MentionsMessage.types"; +import { Text } from "@chakra-ui/react"; + +const INITIAL_DISPLAY_COUNT = 3; + +const formatNumber = (num?: number): string => { + if (!num) return "0"; + if (num >= 1000000) { + return `${(num / 1000000).toFixed(1)}M`; + } + if (num >= 1000) { + return `${(num / 1000).toFixed(1)}K`; + } + return num.toString(); +}; + +const formatDate = (dateString?: string): string => { + if (!dateString) return ""; + const date = new Date(dateString); + return date.toLocaleString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + hour: "numeric", + minute: "numeric", + hour12: true, + }); +}; + +export const ElfaMentionsMessage: React.FC = ({ + metadata, +}) => { + const [showAll, setShowAll] = useState(false); + + if (!metadata?.success || !metadata?.data) { + return Failed to load mentions data.; + } + + const mentions = metadata.data; + const displayMentions = showAll + ? mentions + : mentions.slice(0, INITIAL_DISPLAY_COUNT); + + return ( +
+
+ Latest Social Media Mentions + Total: {mentions.length} mentions +
+ + {displayMentions.map((mention) => ( +
+
+
+ {mention.account?.data?.profileImageUrl ? ( + {mention.account.username + ) : ( +
+ )} +
+
+ {mention.account?.data?.name || "Unknown"} + {mention.account?.isVerified && ( + + + + )} +
+
+ @{mention.account?.username || "unknown"} +
+
+
+
+ + {formatNumber(mention.account?.followerCount)} followers + +
+
+ +
{mention.content}
+ +
+
+ + {formatNumber(mention.viewCount)} +
+
+ + {formatNumber(mention.repostCount)} +
+
+ + {formatNumber(mention.replyCount)} +
+
+ + {formatNumber(mention.likeCount)} +
+
+ + {formatNumber(mention.bookmarkCount)} +
+
+ +
+
+ {formatDate(mention.mentionedAt)} +
+ {mention.originalUrl && ( + + View Original + + )} +
+
+ ))} + + {mentions.length > INITIAL_DISPLAY_COUNT && ( + + )} +
+ ); +}; diff --git a/submodules/frontend/components/Agents/Elfa/Mentions/MentionsMessage.types.ts b/submodules/frontend/components/Agents/Elfa/Mentions/MentionsMessage.types.ts new file mode 100644 index 00000000..d5dd1b1d --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/Mentions/MentionsMessage.types.ts @@ -0,0 +1,52 @@ +// ElfaMentionsMessage.types.ts + +interface AccountData { + profileBannerUrl?: string | null; + profileImageUrl?: string | null; + description?: string | null; + userSince?: string; + location?: string | null; + name?: string; +} + +interface Account { + id?: number; + username?: string; + data?: AccountData; + followerCount?: number; + followingCount?: number; + isVerified?: boolean; +} + +interface Mention { + id?: number; + type?: string; + content?: string; + originalUrl?: string | null; + data?: string | null; + likeCount?: number; + quoteCount?: number; + replyCount?: number; + repostCount?: number; + viewCount?: number; + mentionedAt?: string; + bookmarkCount?: number; + account?: Account; +} + +interface MentionsMetadata { + success?: boolean; + data?: Mention[]; +} + +interface ElfaMentionsMessageProps { + metadata?: MentionsMetadata; +} + +export type { + ElfaMentionsMessageProps, + Mention, + Account, + AccountData, + MentionsMetadata, +}; diff --git a/submodules/frontend/components/Agents/Elfa/TopMentions/TopMentionsMessage.module.css b/submodules/frontend/components/Agents/Elfa/TopMentions/TopMentionsMessage.module.css new file mode 100644 index 00000000..423d13d3 --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/TopMentions/TopMentionsMessage.module.css @@ -0,0 +1,92 @@ +/* ElfaTopMentionsMessage.module.css */ + +.container { + width: 100%; + max-width: 800px; + margin: 0 auto; +} + +.mentionCard { + background: rgba(255, 255, 255, 0.05); + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.content { + color: #e4e4e4; + font-size: 12px; + line-height: 1.5; + margin-bottom: 12px; + white-space: pre-wrap; +} + +.metricsList { + display: flex; + gap: 16px; + flex-wrap: wrap; + margin-bottom: 8px; +} + +.metric { + display: flex; + align-items: center; + gap: 4px; + color: #888; + font-size: 13px; +} + +.timestamp { + color: #666; + font-size: 12px; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.title { + font-size: 16px; + font-weight: 500; + color: #e4e4e4; +} + +.total { + color: #888; + font-size: 12px; +} + +.showMoreButton { + width: 100%; + padding: 8px; + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 8px; + color: #888; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 12px; + margin-top: 16px; +} + +.showMoreButton:hover { + background: rgba(255, 255, 255, 0.15); + color: #aaa; +} + +@media (max-width: 640px) { + .mentionCard { + padding: 12px; + } + + .metricsList { + gap: 12px; + } +} diff --git a/submodules/frontend/components/Agents/Elfa/TopMentions/TopMentionsMessage.tsx b/submodules/frontend/components/Agents/Elfa/TopMentions/TopMentionsMessage.tsx new file mode 100644 index 00000000..25f895c9 --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/TopMentions/TopMentionsMessage.tsx @@ -0,0 +1,112 @@ +// ElfaTopMentionsMessage.tsx +import React, { useState } from "react"; +import { + Eye, + MessageCircle, + Heart, + Repeat, + ChevronDown, + ChevronUp, +} from "lucide-react"; +import styles from "./TopMentionsMessage.module.css"; +import { + ElfaTopMentionsMessageProps, + Mention, +} from "./TopMentionsMessage.types"; +import { Text } from "@chakra-ui/react"; + +const INITIAL_DISPLAY_COUNT = 3; + +const formatNumber = (num: number): string => { + if (num >= 1000000) { + return `${(num / 1000000).toFixed(1)}M`; + } + if (num >= 1000) { + return `${(num / 1000).toFixed(1)}K`; + } + return num.toString(); +}; + +const formatDate = (dateString: string): string => { + const date = new Date(dateString); + return date.toLocaleString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + hour: "numeric", + minute: "numeric", + hour12: true, + }); +}; + +export const ElfaTopMentionsMessage: React.FC = ({ + metadata, +}) => { + const [showAll, setShowAll] = useState(false); + + if (!metadata.success || !metadata.data) { + return Failed to load mentions data.; + } + + const { data: mentions, total } = metadata.data; + const displayMentions = showAll + ? mentions + : mentions.slice(0, INITIAL_DISPLAY_COUNT); + + return ( +
+
+ Top Social Mentions + + Total: {total} mentions over the last 1D + +
+ + {displayMentions.map((mention: Mention) => ( +
+
{mention.content}
+ +
+
+ + {formatNumber(mention.metrics.view_count)} +
+
+ + {formatNumber(mention.metrics.repost_count)} +
+
+ + {formatNumber(mention.metrics.reply_count)} +
+
+ + {formatNumber(mention.metrics.like_count)} +
+
+ +
+ {formatDate(mention.mentioned_at)} +
+
+ ))} + + {mentions.length > INITIAL_DISPLAY_COUNT && ( + + )} +
+ ); +}; diff --git a/submodules/frontend/components/Agents/Elfa/TopMentions/TopMentionsMessage.types.ts b/submodules/frontend/components/Agents/Elfa/TopMentions/TopMentionsMessage.types.ts new file mode 100644 index 00000000..6fa214bf --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/TopMentions/TopMentionsMessage.types.ts @@ -0,0 +1,39 @@ +// ElfaTopMentionsMessage.types.ts + +interface Metrics { + view_count: number; + repost_count: number; + reply_count: number; + like_count: number; +} + +interface Mention { + id: number; + content: string; + mentioned_at: string; + metrics: Metrics; +} + +interface TopMentionsData { + pageSize: number; + page: number; + total: number; + data: Mention[]; +} + +interface TopMentionsMetadata { + success: boolean; + data: TopMentionsData; +} + +interface ElfaTopMentionsMessageProps { + metadata: TopMentionsMetadata; +} + +export type { + ElfaTopMentionsMessageProps, + Mention, + Metrics, + TopMentionsData, + TopMentionsMetadata, +}; diff --git a/submodules/frontend/components/Agents/Elfa/TrendingTokens/TrendingTokensMessage.module.css b/submodules/frontend/components/Agents/Elfa/TrendingTokens/TrendingTokensMessage.module.css new file mode 100644 index 00000000..1b00b0dd --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/TrendingTokens/TrendingTokensMessage.module.css @@ -0,0 +1,109 @@ +/* ElfaTrendingTokensMessage.module.css */ + +.container { + width: 100%; + overflow-x: auto; +} + +.table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + margin-top: 12px; +} + +.headerRow th { + text-align: left; + padding: 12px 16px; + color: #666; + font-weight: 500; + font-size: 12px; + border-bottom: 1px solid #1a1a1a; + white-space: nowrap; +} + +.headerRow th:first-child { + min-width: 150px; +} + +.headerRow th:nth-child(2) { + min-width: 200px; +} + +.headerRow th:last-child { + text-align: right; + min-width: 120px; +} + +.row { + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.row:last-child { + border-bottom: none; +} + +.row td { + padding: 12px 16px; + font-size: 12px; +} + +.tokenCell { + color: #e4e4e4; + font-weight: 500; + white-space: nowrap; +} + +.mentionsCell { + display: flex; + align-items: center; + gap: 8px; + color: #888; +} + +.arrow { + color: #666; + flex-shrink: 0; +} + +.changeCell { + text-align: right; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 4px; + white-space: nowrap; +} + +.changePositive { + color: #4caf50; +} + +.changeNegative { + color: #f44336; +} + +.changeNeutral { + color: #888; +} + +.showMoreButton { + width: 100%; + padding: 8px; + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 8px; + color: #888; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 12px; + margin-top: 16px; +} + +.showMoreButton:hover { + background: rgba(255, 255, 255, 0.15); + color: #aaa; +} diff --git a/submodules/frontend/components/Agents/Elfa/TrendingTokens/TrendingTokensMessage.tsx b/submodules/frontend/components/Agents/Elfa/TrendingTokens/TrendingTokensMessage.tsx new file mode 100644 index 00000000..71c24947 --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/TrendingTokens/TrendingTokensMessage.tsx @@ -0,0 +1,121 @@ +// ElfaTrendingTokensMessage.tsx +import React, { useState } from "react"; +import { + ChevronDown, + ChevronUp, + ArrowRight, + ArrowUpRight, + ArrowDownRight, + Minus, +} from "lucide-react"; +import styles from "./TrendingTokensMessage.module.css"; +import { + ElfaTrendingTokensMessageProps, + TokenData, +} from "./TrendingTokensMessage.types"; +import { Text } from "@chakra-ui/react"; + +const INITIAL_DISPLAY_COUNT = 10; + +const formatNumber = (num: number): string => { + if (num >= 1000000) { + return `${(num / 1000000).toFixed(1)}M`; + } + if (num >= 1000) { + return `${(num / 1000).toFixed(1)}K`; + } + return num.toString(); +}; + +const getChangeIndicator = (changePercent: number) => { + if (changePercent > 0) { + return { + icon: , + className: styles.changePositive, + prefix: "+", + }; + } + if (changePercent < 0) { + return { + icon: , + className: styles.changeNegative, + prefix: "", + }; + } + return { + icon: , + className: styles.changeNeutral, + prefix: "", + }; +}; + +export const ElfaTrendingTokensMessage: React.FC< + ElfaTrendingTokensMessageProps +> = ({ metadata }) => { + const [showAll, setShowAll] = useState(false); + + if (!metadata.success || !metadata.data) { + return Failed to load trending tokens data.; + } + + const { data: tokens, total } = metadata.data; + const displayTokens = showAll + ? tokens + : tokens.slice(0, INITIAL_DISPLAY_COUNT); + + return ( +
+ + + + + + + + + + {displayTokens.map((token: TokenData) => { + const change = getChangeIndicator(token.change_percent); + + return ( + + + + + + ); + })} + +
Token / TickerChange in # of Mentions% Change
${token.token} + {formatNumber(token.previous_count)} + + {formatNumber(token.current_count)} + +
+ {change.icon} + + {change.prefix} + {token.change_percent.toFixed(2)}% + +
+
+ + {tokens.length > INITIAL_DISPLAY_COUNT && ( + + )} +
+ ); +}; diff --git a/submodules/frontend/components/Agents/Elfa/TrendingTokens/TrendingTokensMessage.types.ts b/submodules/frontend/components/Agents/Elfa/TrendingTokens/TrendingTokensMessage.types.ts new file mode 100644 index 00000000..25555fee --- /dev/null +++ b/submodules/frontend/components/Agents/Elfa/TrendingTokens/TrendingTokensMessage.types.ts @@ -0,0 +1,31 @@ +// ElfaTrendingTokensMessage.types.ts + +interface TokenData { + token: string; + current_count: number; + previous_count: number; + change_percent: number; +} + +interface TrendingTokensData { + pageSize: number; + page: number; + total: number; + data: TokenData[]; +} + +interface TrendingTokensMetadata { + success: boolean; + data: TrendingTokensData; +} + +interface ElfaTrendingTokensMessageProps { + metadata: TrendingTokensMetadata; +} + +export type { + ElfaTrendingTokensMessageProps, + TokenData, + TrendingTokensData, + TrendingTokensMetadata, +}; diff --git a/submodules/moragents_dockers/frontend/components/Agents/Imagen/ImageDisplayMessage.module.css b/submodules/frontend/components/Agents/Imagen/ImageDisplayMessage.module.css old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/Agents/Imagen/ImageDisplayMessage.module.css rename to submodules/frontend/components/Agents/Imagen/ImageDisplayMessage.module.css diff --git a/submodules/moragents_dockers/frontend/components/Agents/Imagen/ImageDisplayMessage.tsx b/submodules/frontend/components/Agents/Imagen/ImageDisplayMessage.tsx old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/Agents/Imagen/ImageDisplayMessage.tsx rename to submodules/frontend/components/Agents/Imagen/ImageDisplayMessage.tsx diff --git a/submodules/moragents_dockers/frontend/components/Agents/MorClaims/ClaimForm.tsx b/submodules/frontend/components/Agents/MorClaims/CustomMessages/ClaimForm.tsx old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/Agents/MorClaims/ClaimForm.tsx rename to submodules/frontend/components/Agents/MorClaims/CustomMessages/ClaimForm.tsx diff --git a/submodules/moragents_dockers/frontend/components/Agents/MorClaims/ClaimMessage.tsx b/submodules/frontend/components/Agents/MorClaims/CustomMessages/ClaimMessage.tsx old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/Agents/MorClaims/ClaimMessage.tsx rename to submodules/frontend/components/Agents/MorClaims/CustomMessages/ClaimMessage.tsx diff --git a/submodules/frontend/components/Agents/MorClaims/hooks.ts b/submodules/frontend/components/Agents/MorClaims/hooks.ts new file mode 100644 index 00000000..774a8443 --- /dev/null +++ b/submodules/frontend/components/Agents/MorClaims/hooks.ts @@ -0,0 +1,37 @@ +import { Axios } from "axios"; +import { ChatMessage, ClaimTransactionPayload } from "@/services/types"; + +export const getClaimTxPayload = async ( + backendClient: Axios, + transactions: ClaimTransactionPayload[] +): Promise => { + const response = await backendClient.post("/claim/claim", { transactions }); + return response.data.transactions; +}; + +export const sendClaimStatus = async ( + backendClient: Axios, + chainId: number, + walletAddress: string, + claimStatus: string, + txHash?: string +): Promise => { + const responseBody = await backendClient.post("/claim/tx_status", { + chain_id: chainId, + wallet_address: walletAddress, + status: claimStatus, + tx_hash: txHash || "", + tx_type: "claim", + }); + + return { + role: responseBody.data.role, + content: responseBody.data.content, + agentName: responseBody.data.agentName, + error_message: responseBody.data.error_message, + metadata: responseBody.data.metadata, + requires_action: responseBody.data.requires_action, + action_type: responseBody.data.action_type, + timestamp: responseBody.data.timestamp, + } as ChatMessage; +}; diff --git a/submodules/frontend/components/Agents/Rugcheck/MostViewed/MostViewedTokensMessage.module.css b/submodules/frontend/components/Agents/Rugcheck/MostViewed/MostViewedTokensMessage.module.css new file mode 100644 index 00000000..1636f463 --- /dev/null +++ b/submodules/frontend/components/Agents/Rugcheck/MostViewed/MostViewedTokensMessage.module.css @@ -0,0 +1,181 @@ +/* ViewedTokensMessage.module.css */ + +.container { + width: 100%; + max-width: 800px; + margin: 0 auto; +} + +.header { + margin-bottom: 12px; +} + +.title { + font-size: 12px; + font-weight: 500; + color: #e4e4e4; + margin-bottom: 2px; +} + +.subtitle { + color: #888; + font-size: 12px; +} + +.tokenList { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 8px; +} + +.tokenCard { + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + padding: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.tokenHeader { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.rankBadge { + width: 24px; + height: 24px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.1); + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 500; + color: #aaa; +} + +.tokenInfo { + flex: 1; +} + +.tokenName { + font-size: 12px; + font-weight: 500; + color: #e4e4e4; + margin-bottom: 2px; +} + +.tokenSymbol { + font-size: 12px; + color: #888; +} + +.addressWrapper { + display: flex; + align-items: center; + gap: 4px; + background: rgba(0, 0, 0, 0.2); + padding: 2px 4px; + border-radius: 4px; +} + +.address { + font-family: monospace; + font-size: 12px; + color: #888; +} + +.copyButton { + color: #666; + background: none; + border: none; + padding: 2px; + display: flex; + align-items: center; + cursor: pointer; + border-radius: 4px; +} + +.copyButton:hover { + color: #888; + background: rgba(255, 255, 255, 0.1); +} + +.statsGrid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; +} + +.statItem { + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + background: rgba(255, 255, 255, 0.03); + border-radius: 6px; +} + +.statIcon { + color: #666; +} + +.statInfo { + flex: 1; +} + +.statValue { + font-size: 12px; + font-weight: 500; + color: #e4e4e4; + margin-bottom: 2px; +} + +.statLabel { + font-size: 12px; + color: #888; +} + +.showMoreButton { + width: 100%; + padding: 6px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 6px; + color: #888; + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + transition: all 0.2s; +} + +.showMoreButton:hover { + background: rgba(255, 255, 255, 0.08); + color: #aaa; +} + +@media (max-width: 640px) { + .tokenHeader { + flex-direction: column; + align-items: flex-start; + gap: 6px; + } + + .addressWrapper { + align-self: stretch; + } + + .statsGrid { + grid-template-columns: 1fr; + gap: 6px; + } + + .tokenCard { + padding: 8px; + } +} diff --git a/submodules/frontend/components/Agents/Rugcheck/MostViewed/MostViewedTokensMessage.tsx b/submodules/frontend/components/Agents/Rugcheck/MostViewed/MostViewedTokensMessage.tsx new file mode 100644 index 00000000..be1f800a --- /dev/null +++ b/submodules/frontend/components/Agents/Rugcheck/MostViewed/MostViewedTokensMessage.tsx @@ -0,0 +1,118 @@ +import React, { useState } from "react"; +import { Eye, Users, Copy, ChevronDown, ChevronUp } from "lucide-react"; +import { Text } from "@chakra-ui/react"; +import styles from "./MostViewedTokensMessage.module.css"; +import { ViewedTokensMessageProps } from "./MostViewedTokensMessage.types"; + +const INITIAL_DISPLAY_COUNT = 5; + +export const ViewedTokensMessage: React.FC = ({ + metadata, +}) => { + const [showAll, setShowAll] = useState(false); + + if (!metadata?.tokens || metadata.tokens.length === 0) { + return ( +
+ No token view data available +
+ ); + } + + const truncateAddress = (address: string) => { + if (!address) return ""; + return `${address.slice(0, 6)}...${address.slice(-6)}`; + }; + + const handleCopy = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + } catch (err) { + console.error("Failed to copy:", err); + } + }; + + const displayTokens = showAll + ? metadata.tokens + : metadata.tokens.slice(0, INITIAL_DISPLAY_COUNT); + + return ( +
+
+ Most Viewed Tokens (24h) + + Top {metadata.tokens.length} tokens by view count + +
+ +
+ {displayTokens.map((token, index) => ( +
+
+
#{index + 1}
+
+
+ {token.metadata.name || "Unknown Token"} +
+
+ {token.metadata.symbol || "???"} +
+
+
+ + {truncateAddress(token.mint)} + + +
+
+ +
+
+ +
+
+ {token.visits.toLocaleString()} +
+
Total Views
+
+
+ +
+ +
+
+ {token.user_visits.toLocaleString()} +
+
Unique Visitors
+
+
+
+
+ ))} +
+ + {metadata.tokens.length > INITIAL_DISPLAY_COUNT && ( + + )} +
+ ); +}; diff --git a/submodules/frontend/components/Agents/Rugcheck/MostViewed/MostViewedTokensMessage.types.ts b/submodules/frontend/components/Agents/Rugcheck/MostViewed/MostViewedTokensMessage.types.ts new file mode 100644 index 00000000..c0b4a606 --- /dev/null +++ b/submodules/frontend/components/Agents/Rugcheck/MostViewed/MostViewedTokensMessage.types.ts @@ -0,0 +1,29 @@ +// ViewedTokensMessage.types.ts + +interface TokenMetadata { + name?: string; + symbol?: string; + [key: string]: any; +} + +interface ViewedToken { + mint: string; + metadata: TokenMetadata; + visits: number; + user_visits: number; +} + +interface ViewedTokensMetadata { + tokens: ViewedToken[]; +} + +interface ViewedTokensMessageProps { + metadata: ViewedTokensMetadata; +} + +export type { + ViewedTokensMessageProps, + ViewedTokensMetadata, + ViewedToken, + TokenMetadata, +}; diff --git a/submodules/frontend/components/Agents/Rugcheck/MostVoted/MostVotedTokensMessage.module.css b/submodules/frontend/components/Agents/Rugcheck/MostVoted/MostVotedTokensMessage.module.css new file mode 100644 index 00000000..6025ab82 --- /dev/null +++ b/submodules/frontend/components/Agents/Rugcheck/MostVoted/MostVotedTokensMessage.module.css @@ -0,0 +1,200 @@ +/* VotedTokensMessage.module.css */ +.container { + width: 100%; + max-width: 800px; + margin: 0 auto; +} + +.header { + margin-bottom: 12px; +} + +.title { + font-size: 12px; + font-weight: 500; + color: #e4e4e4; + margin-bottom: 2px; +} + +.subtitle { + color: #888; + font-size: 12px; +} + +.tokenList { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 8px; +} + +.tokenCard { + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + padding: 12px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.tokenHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +.tokenInfo { + display: flex; + align-items: center; + gap: 8px; +} + +.rankBadge { + width: 24px; + height: 24px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.1); + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 500; + color: #aaa; +} + +.addressWrapper { + display: flex; + align-items: center; + gap: 4px; + background: rgba(0, 0, 0, 0.2); + padding: 2px 4px; + border-radius: 4px; +} + +.address { + font-family: monospace; + font-size: 12px; + color: #888; +} + +.copyButton { + color: #666; + background: none; + border: none; + padding: 2px; + display: flex; + align-items: center; + cursor: pointer; + border-radius: 4px; +} + +.copyButton:hover { + color: #888; + background: rgba(255, 255, 255, 0.1); +} + +.statsGroup { + display: flex; + align-items: center; + gap: 12px; +} + +.statItem { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + background: rgba(255, 255, 255, 0.03); + border-radius: 4px; +} + +.statIcon { + color: #666; +} + +.statValue { + font-size: 12px; + font-weight: 500; + color: #e4e4e4; +} + +.approvalSection { + padding: 8px; + background: rgba(255, 255, 255, 0.03); + border-radius: 6px; +} + +.approvalHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 4px; +} + +.approvalLabel { + font-size: 12px; + color: #888; +} + +.approvalValue { + font-size: 12px; + font-weight: 500; + color: #e4e4e4; +} + +.approvalBar { + height: 6px; + background: rgba(255, 255, 255, 0.1); + border-radius: 3px; + overflow: hidden; +} + +.approvalFill { + height: 100%; + transition: width 0.3s ease; +} + +.highApproval { + background: linear-gradient(90deg, #22c55e, #4ade80); +} + +.mediumApproval { + background: linear-gradient(90deg, #f59e0b, #fbbf24); +} + +.lowApproval { + background: linear-gradient(90deg, #ef4444, #f87171); +} + +.showMoreButton { + width: 100%; + padding: 6px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 6px; + color: #888; + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + transition: all 0.2s; +} + +.showMoreButton:hover { + background: rgba(255, 255, 255, 0.08); + color: #aaa; +} + +@media (max-width: 640px) { + .tokenHeader { + flex-direction: column; + align-items: flex-start; + gap: 6px; + } + + .statsGroup { + margin-top: 8px; + align-self: stretch; + } +} diff --git a/submodules/frontend/components/Agents/Rugcheck/MostVoted/MostVotedTokensMessage.tsx b/submodules/frontend/components/Agents/Rugcheck/MostVoted/MostVotedTokensMessage.tsx new file mode 100644 index 00000000..f62fed08 --- /dev/null +++ b/submodules/frontend/components/Agents/Rugcheck/MostVoted/MostVotedTokensMessage.tsx @@ -0,0 +1,133 @@ +import React, { useState } from "react"; +import { ThumbsUp, Vote, Copy, ChevronDown, ChevronUp } from "lucide-react"; +import { Text } from "@chakra-ui/react"; +import styles from "./MostVotedTokensMessage.module.css"; +import { VotedTokensMessageProps } from "./MostVotedTokensMessage.types"; + +const INITIAL_DISPLAY_COUNT = 5; + +export const VotedTokensMessage: React.FC = ({ + metadata, +}) => { + const [showAll, setShowAll] = useState(false); + + if (!metadata?.tokens || metadata.tokens.length === 0) { + return ( +
+ No token voting data available +
+ ); + } + + const truncateAddress = (address: string) => { + if (!address) return ""; + return `${address.slice(0, 6)}...${address.slice(-6)}`; + }; + + const handleCopy = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + } catch (err) { + console.error("Failed to copy:", err); + } + }; + + const getApprovalClass = (upvotes: number, total: number) => { + const ratio = upvotes / total; + if (ratio >= 0.8) return styles.highApproval; + if (ratio >= 0.5) return styles.mediumApproval; + return styles.lowApproval; + }; + + const displayTokens = showAll + ? metadata.tokens + : metadata.tokens.slice(0, INITIAL_DISPLAY_COUNT); + + return ( +
+
+ Most Voted Tokens (24h) + + Top {metadata.tokens.length} tokens by community votes + +
+ +
+ {displayTokens.map((token, index) => ( +
+
+
+
#{index + 1}
+
+ + {truncateAddress(token.mint)} + + +
+
+ +
+
+ +
+ {token.up_count.toLocaleString()} +
+
+ +
+ +
+ {token.vote_count.toLocaleString()} +
+
+
+
+ +
+
+
Community Approval
+
+ {((token.up_count / token.vote_count) * 100).toFixed(1)}% +
+
+
+
+
+
+
+ ))} +
+ + {metadata.tokens.length > INITIAL_DISPLAY_COUNT && ( + + )} +
+ ); +}; diff --git a/submodules/frontend/components/Agents/Rugcheck/MostVoted/MostVotedTokensMessage.types.ts b/submodules/frontend/components/Agents/Rugcheck/MostVoted/MostVotedTokensMessage.types.ts new file mode 100644 index 00000000..18bf7a52 --- /dev/null +++ b/submodules/frontend/components/Agents/Rugcheck/MostVoted/MostVotedTokensMessage.types.ts @@ -0,0 +1,17 @@ +// VotedTokensMessage.types.ts + +interface VotedToken { + mint: string; + up_count: number; + vote_count: number; +} + +interface VotedTokensMetadata { + tokens: VotedToken[]; +} + +interface VotedTokensMessageProps { + metadata: VotedTokensMetadata; +} + +export type { VotedTokensMessageProps, VotedTokensMetadata, VotedToken }; diff --git a/submodules/frontend/components/Agents/Rugcheck/Report/RugcheckReportMessage.module.css b/submodules/frontend/components/Agents/Rugcheck/Report/RugcheckReportMessage.module.css new file mode 100644 index 00000000..edf24464 --- /dev/null +++ b/submodules/frontend/components/Agents/Rugcheck/Report/RugcheckReportMessage.module.css @@ -0,0 +1,145 @@ +.container { + width: 100%; + max-width: 800px; + margin: 0 auto; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.title { + font-size: 20px; + font-weight: 500; + color: #e4e4e4; +} + +.scoreWrapper { + display: flex; + align-items: center; + gap: 8px; +} + +.score { + font-size: 18px; + font-weight: 600; +} + +.highScore { + color: #ef4444; +} + +.mediumScore { + color: #f59e0b; +} + +.lowScore { + color: #22c55e; +} + +.addressBar { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 24px; + background: rgba(255, 255, 255, 0.05); + padding: 8px 12px; + border-radius: 8px; +} + +.address { + font-family: monospace; + font-size: 12px; + color: #888; +} + +.copyButton { + color: #666; + background: none; + border: none; + padding: 4px; + display: flex; + align-items: center; + cursor: pointer; + border-radius: 4px; +} + +.copyButton:hover { + color: #888; + background: rgba(255, 255, 255, 0.1); +} + +.riskList { + display: flex; + flex-direction: column; + gap: 16px; +} + +.riskCard { + background: rgba(255, 255, 255, 0.05); + border-radius: 12px; + padding: 16px; + padding-top: 8px; + padding-bottom: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.dangerRisk { + background: rgba(239, 68, 68, 0.1); + border-color: rgba(239, 68, 68, 0.2); +} + +.warnRisk { + background: rgba(245, 158, 11, 0.1); + border-color: rgba(245, 158, 11, 0.2); +} + +.infoRisk { + background: rgba(59, 130, 246, 0.1); + border-color: rgba(59, 130, 246, 0.2); +} + +.riskHeader { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.riskName { + font-size: 16px; + font-weight: 500; + color: #e4e4e4; +} + +.riskValue { + font-size: 12px; + color: #888; +} + +.riskDescription { + color: #888; + font-size: 12px; + line-height: 1.5; + margin-bottom: 12px; +} + +.riskScore { + color: #666; + font-size: 12px; +} + +@media (max-width: 640px) { + .header { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + + .riskCard { + padding: 12px; + } +} diff --git a/submodules/frontend/components/Agents/Rugcheck/Report/RugcheckReportMessage.tsx b/submodules/frontend/components/Agents/Rugcheck/Report/RugcheckReportMessage.tsx new file mode 100644 index 00000000..ebc82eba --- /dev/null +++ b/submodules/frontend/components/Agents/Rugcheck/Report/RugcheckReportMessage.tsx @@ -0,0 +1,98 @@ +import React from "react"; +import { Copy } from "lucide-react"; +import { Text } from "@chakra-ui/react"; +import styles from "./RugcheckReportMessage.module.css"; +import { RugcheckReportMessageProps } from "./RugcheckReportMessage.types"; + +export const RugcheckReportMessage: React.FC = ({ + metadata, +}) => { + if (!metadata?.report) { + return ( +
+ + No security analysis report available + +
+ ); + } + + const { report, mint_address } = metadata; + + const truncateAddress = (address: string) => { + if (!address) return ""; + return `${address.slice(0, 6)}...${address.slice(-6)}`; + }; + + const handleCopy = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + } catch (err) { + console.error("Failed to copy:", err); + } + }; + + const getRiskClass = (level: string) => { + switch (level) { + case "danger": + return styles.dangerRisk; + case "warn": + return styles.warnRisk; + default: + return styles.infoRisk; + } + }; + + const getScoreClass = (score: number) => { + if (score > 10000) return styles.highScore; + if (score > 5000) return styles.mediumScore; + return styles.lowScore; + }; + + return ( +
+
+ Security Analysis Report +
+ + Score: {report?.score?.toLocaleString() || "0"} + +
+
+ +
+ {truncateAddress(mint_address)} + +
+ +
+ {report?.risks?.map((risk, index) => ( +
+
+ {risk.name} + {risk.value && ( + {risk.value} + )} +
+ + {risk.description} + +
+ Risk Score: {risk.score.toLocaleString()} +
+
+ ))} +
+
+ ); +}; diff --git a/submodules/frontend/components/Agents/Rugcheck/Report/RugcheckReportMessage.types.ts b/submodules/frontend/components/Agents/Rugcheck/Report/RugcheckReportMessage.types.ts new file mode 100644 index 00000000..9d652a51 --- /dev/null +++ b/submodules/frontend/components/Agents/Rugcheck/Report/RugcheckReportMessage.types.ts @@ -0,0 +1,29 @@ +// RugcheckReportMessage.types.ts + +interface Risk { + name: string; + value: string; + description: string; + score: number; + level: "danger" | "warn" | "info"; +} + +interface Report { + tokenProgram: string; + tokenType: string; + risks: Risk[]; + score: number; +} + +interface RugcheckMetadata { + report: Report; + mint_address: string; + token_name: string; + identifier: string; +} + +interface RugcheckReportMessageProps { + metadata: RugcheckMetadata; +} + +export type { RugcheckReportMessageProps, Risk, Report, RugcheckMetadata }; diff --git a/submodules/frontend/components/Agents/Swaps/Swap/SwapMessage.module.css b/submodules/frontend/components/Agents/Swaps/Swap/SwapMessage.module.css new file mode 100644 index 00000000..02ea1df4 --- /dev/null +++ b/submodules/frontend/components/Agents/Swaps/Swap/SwapMessage.module.css @@ -0,0 +1,218 @@ +/* OneInchSwapMessage.module.css */ +.container { + width: 100%; + max-width: 800px; + margin: 0 auto; +} + +.contentRow { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; +} + +.messageContent { + color: #e4e4e4; + font-size: 14px; + margin: 0; +} + +.toggleButton { + background: transparent; + border: none; + color: #888; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 4px; + border-radius: 4px; + transition: all 0.2s ease; +} + +.toggleButton:hover { + color: #59f886; + background: rgba(89, 248, 134, 0.1); +} + +.swapCard { + margin-top: 12px; + background: rgba(22, 24, 22, 0.7); + border-radius: 12px; + padding: 2px; + border: 1px solid rgba(103, 107, 104, 0.5); +} + +.swapPanel { + background: #111613; + border-radius: 10px; + padding: 16px; +} + +.swapGrid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 16px; + border-bottom: 1px solid rgba(204, 206, 205, 0.2); + padding-bottom: 16px; +} + +.swapColumn { + position: relative; +} + +.swapColumn:not(:last-child)::after { + content: ""; + position: absolute; + top: 10%; + right: -8px; + height: 80%; + width: 1px; + background-color: rgba(204, 206, 205, 0.2); +} + +.swapSection { + display: flex; + flex-direction: column; + gap: 8px; +} + +.sectionTitle { + color: #e4e4e4; + font-size: 14px; + font-weight: 500; +} + +.tokenAmount { + display: flex; + align-items: center; + gap: 4px; +} + +.amountValue { + color: #9a9c9b; + font-size: 16px; +} + +.tokenSymbol { + color: #e4e4e4; + font-size: 16px; +} + +.slippageHeader { + display: flex; + align-items: center; + gap: 4px; + margin-bottom: 4px; +} + +.infoButton { + background: transparent; + border: none; + color: #9a9c9b; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 2px; +} + +.infoButton:hover { + color: #e4e4e4; +} + +.inputGroup { + display: flex; + align-items: center; + height: 32px; + margin-bottom: 4px; +} + +.slippageInput { + width: 100%; + height: 100%; + background: transparent; + border: 1px solid #676b68; + border-radius: 4px 0 0 4px; + color: #e4e4e4; + padding: 0 8px; + font-size: 14px; +} + +.inputAddon { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 0 8px; + background: transparent; + border: 1px solid #676b68; + border-left: none; + border-radius: 0 4px 4px 0; + color: #9a9c9b; +} + +.poweredBy { + color: #676b68; + font-size: 10px; +} + +.actionButtons { + display: flex; + justify-content: flex-end; + gap: 16px; + margin-top: 16px; +} + +.cancelButton { + background: transparent; + border: none; + color: #e4e4e4; + font-size: 14px; + cursor: pointer; + padding: 8px 16px; + border-radius: 4px; +} + +.cancelButton:hover { + color: #59f886; +} + +.swapButton { + background: #59f886; + color: #0a120d; + border: none; + padding: 8px 24px; + border-radius: 4px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.swapButton:hover { + background: #4ae977; +} + +.swapButton:disabled { + background: #2d7c43; + color: #9a9c9b; + cursor: not-allowed; +} + +@media (max-width: 640px) { + .swapGrid { + grid-template-columns: 1fr; + gap: 12px; + } + + .swapColumn::after { + display: none; + } + + .swapColumn:not(:last-child) { + border-bottom: 1px solid rgba(204, 206, 205, 0.2); + padding-bottom: 12px; + } +} diff --git a/submodules/frontend/components/Agents/Swaps/Swap/SwapMessage.tsx b/submodules/frontend/components/Agents/Swaps/Swap/SwapMessage.tsx new file mode 100644 index 00000000..4da9fa43 --- /dev/null +++ b/submodules/frontend/components/Agents/Swaps/Swap/SwapMessage.tsx @@ -0,0 +1,143 @@ +// OneInchSwapMessage.jsx +import React, { useState } from "react"; +import { ArrowLeftRight, Info } from "lucide-react"; +import { useAccount } from "wagmi"; +import { useSwapTransaction, SwapTx } from "../hooks"; +import styles from "./SwapMessage.module.css"; +import { OneInchSwapMessageProps } from "./SwapMessage.types"; +import { StyledTooltip } from "@/components/Common/StyledTooltip"; + +const OneInchSwapMessage: React.FC = ({ + content, + metadata = { + src: "ETH", + dst: "USDC", + src_amount: 0, + dst_amount: 0, + src_address: "0x0000000000000000000000000000000000000000", + dst_address: "0x0000000000000000000000000000000000000000", + }, +}) => { + const { address } = useAccount(); + const { handleSwap, handleCancel, isLoading } = useSwapTransaction(); + const [slippage, setSlippage] = useState(0.1); + const [showForm, setShowForm] = useState(true); + + // Extract values from metadata + const src = metadata.src || "ETH"; + const dst = metadata.dst || "USDC"; + const srcAmount = metadata.src_amount || 0; + const dstAmount = metadata.dst_amount || 0; + const srcAddress = + metadata.src_address || "0x0000000000000000000000000000000000000000"; + const dstAddress = + metadata.dst_address || "0x0000000000000000000000000000000000000000"; + + return ( +
+
+

{content}

+ +
+ + {showForm && ( +
+
+
+
+
+ You Pay +
+ {srcAmount} + {src} +
+
+
+ +
+
+ You Receive +
+ + {dstAmount.toFixed(4)} + + {dst} +
+
+
+ +
+
+
+ Slippage + +
+
+ setSlippage(Number(e.target.value))} + className={styles.slippageInput} + /> +
%
+
+
Using 1inch
+
+
+
+ +
+ + + + +
+
+
+ )} +
+ ); +}; + +export default OneInchSwapMessage; diff --git a/submodules/frontend/components/Agents/Swaps/Swap/SwapMessage.types.ts b/submodules/frontend/components/Agents/Swaps/Swap/SwapMessage.types.ts new file mode 100644 index 00000000..7574d199 --- /dev/null +++ b/submodules/frontend/components/Agents/Swaps/Swap/SwapMessage.types.ts @@ -0,0 +1,17 @@ +// OneInchSwapMessage.types.ts + +export interface SwapMetadata { + src?: string; + dst?: string; + src_address?: string; + dst_address?: string; + src_amount?: number; + dst_amount?: number; +} + +export interface OneInchSwapMessageProps { + content: string | React.ReactNode; + metadata?: SwapMetadata; +} + +// Note: Main SwapTx type is now imported directly from useSwapTransaction.ts diff --git a/submodules/frontend/components/Agents/Swaps/hooks.ts b/submodules/frontend/components/Agents/Swaps/hooks.ts new file mode 100755 index 00000000..3952638b --- /dev/null +++ b/submodules/frontend/components/Agents/Swaps/hooks.ts @@ -0,0 +1,148 @@ +import { useState, useCallback } from "react"; +import { useAccount, useChainId, useSendTransaction } from "wagmi"; +import { sendSwapStatus } from "@/services/apiHooks"; +import { getHttpClient, SWAP_STATUS } from "@/services/constants"; + +export type SwapTx = { + dstAmount: string; + tx: { + data: string; + from: string; + gas: number; + gasPrice: string; + to: string; + value: string; + }; +}; + +export const useSwapTransaction = () => { + const [isLoading, setIsLoading] = useState(false); + const [txHash, setTxHash] = useState(""); + const { address } = useAccount(); + const chainId = useChainId(); + const { sendTransaction } = useSendTransaction(); + + const handleSwap = useCallback( + async (swapTx: SwapTx) => { + if (!address) { + console.error("Wallet not connected"); + return; + } + + setIsLoading(true); + setTxHash(""); + + try { + // Safely handle the value conversion to BigInt + let valueInWei: bigint; + + try { + // Check if the value is already in Wei format + if (swapTx.tx.value.includes("e")) { + // Handle scientific notation + const valueNumber = Number(swapTx.tx.value); + valueInWei = BigInt(Math.floor(valueNumber * 1e18)); + } else if (Number(swapTx.tx.value) < 1) { + // Small values need to be converted to Wei + valueInWei = BigInt(Math.floor(Number(swapTx.tx.value) * 1e18)); + } else { + // Try to parse the value directly + valueInWei = BigInt(swapTx.tx.value); + } + } catch (e) { + console.error("Error converting value to BigInt:", e); + // Fallback to 0 if conversion fails + valueInWei = BigInt(0); + } + + // Make sure tx.to is a valid address + const toAddress = + swapTx.tx.to && swapTx.tx.to !== "0x" + ? (swapTx.tx.to as `0x${string}`) + : ("0x0000000000000000000000000000000000000000" as `0x${string}`); + + // Make sure tx.data is a valid hex string + const txData = + swapTx.tx.data && swapTx.tx.data !== "" + ? (swapTx.tx.data as `0x${string}`) + : ("0x" as `0x${string}`); + + sendTransaction( + { + account: address, + data: txData, + to: toAddress, + value: valueInWei, + }, + { + onSuccess: async (hash) => { + setTxHash(hash); + try { + await sendSwapStatus( + getHttpClient(), + chainId, + address.toLowerCase(), + SWAP_STATUS.INIT, + hash, + 0 + ); + } catch (error) { + console.error("Error sending swap status:", error); + } + }, + onError: async (error) => { + console.error(`Error sending transaction: ${error}`); + try { + await sendSwapStatus( + getHttpClient(), + chainId, + address.toLowerCase(), + SWAP_STATUS.FAIL, + "", + 0 + ); + } catch (statusError) { + console.error("Error sending failure status:", statusError); + } + setIsLoading(false); + }, + onSettled: () => setIsLoading(false), + } + ); + } catch (error) { + setIsLoading(false); + console.error("Swap failed:", error); + } + }, + [address, chainId, sendTransaction] + ); + + const handleCancel = useCallback( + async (fromAction: number) => { + if (!address) return; + + try { + await sendSwapStatus( + getHttpClient(), + chainId, + address.toLowerCase(), + SWAP_STATUS.CANCELLED, + "", + fromAction + ); + } catch (error) { + console.error(`Failed to cancel swap: ${error}`); + } + }, + [address, chainId] + ); + + return { + handleSwap, + handleCancel, + isLoading, + txHash, + }; +}; + +export default useSwapTransaction; diff --git a/submodules/moragents_dockers/frontend/components/Agents/Tweet/TweetMessage.module.css b/submodules/frontend/components/Agents/Tweet/CustomMessages/TweetMessage.module.css old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/Agents/Tweet/TweetMessage.module.css rename to submodules/frontend/components/Agents/Tweet/CustomMessages/TweetMessage.module.css diff --git a/submodules/moragents_dockers/frontend/components/Agents/Tweet/TweetMessage.tsx b/submodules/frontend/components/Agents/Tweet/CustomMessages/TweetMessage.tsx old mode 100644 new mode 100755 similarity index 96% rename from submodules/moragents_dockers/frontend/components/Agents/Tweet/TweetMessage.tsx rename to submodules/frontend/components/Agents/Tweet/CustomMessages/TweetMessage.tsx index 0cb0a1ad..335ee174 --- a/submodules/moragents_dockers/frontend/components/Agents/Tweet/TweetMessage.tsx +++ b/submodules/frontend/components/Agents/Tweet/CustomMessages/TweetMessage.tsx @@ -13,13 +13,8 @@ import { Text, Box, } from "@chakra-ui/react"; -import { - FaPaperPlane, - FaCheckCircle, - FaTimesCircle, - FaSync, -} from "react-icons/fa"; -import { postTweet, regenerateTweet } from "@/services/apiHooks"; +import { FaPaperPlane, FaCheckCircle, FaTimesCircle } from "react-icons/fa"; +import { postTweet, regenerateTweet } from "@/components/Agents/Tweet/hooks"; import { getHttpClient } from "@/services/constants"; import styles from "./TweetMessage.module.css"; @@ -130,7 +125,7 @@ export const Tweet: FC = ({ initialContent }) => { charactersLeft < 0 || !tweetContent || tweetContent.length === 0 } > - Tweet + Post on X diff --git a/submodules/frontend/components/Agents/Tweet/hooks.ts b/submodules/frontend/components/Agents/Tweet/hooks.ts new file mode 100644 index 00000000..cfc8807b --- /dev/null +++ b/submodules/frontend/components/Agents/Tweet/hooks.ts @@ -0,0 +1,56 @@ +import { Axios } from "axios"; +import { decryptData, downloadFromIrys } from "@/services/LitProtocol/utils"; + +export const postTweet = async ( + backendClient: Axios, + content: string +): Promise => { + const irysUrl = localStorage.getItem("twitter_irys_url"); + + if (!irysUrl) { + throw new Error( + "X API credentials not found. Please set them in the settings." + ); + } + + try { + const irysId = irysUrl.split("/").pop() || ""; + const [ciphertext, dataToEncryptHash, accessControlConditions] = + await downloadFromIrys(irysId); + + if (!ciphertext || !dataToEncryptHash || !accessControlConditions) { + throw new Error("Missing required data from Irys"); + } + + const decrypted = await decryptData( + ciphertext, + dataToEncryptHash, + accessControlConditions + ); + + const credentials = JSON.parse(decrypted); + + await backendClient.post("/tweet/post", { + post_content: content, + api_key: credentials.apiKey, + api_secret: credentials.apiSecret, + access_token: credentials.accessToken, + access_token_secret: credentials.accessTokenSecret, + }); + } catch (error) { + console.error("Error posting tweet:", error); + throw error; + } +}; + +export const regenerateTweet = async ( + backendClient: Axios +): Promise => { + try { + const response = await backendClient.post("/tweet/regenerate"); + return response.data; + } catch (error) { + console.error("Error regenerating tweet:", error); + throw error; + } +}; diff --git a/submodules/moragents_dockers/frontend/components/Avatar/index.tsx b/submodules/frontend/components/Avatar/index.tsx old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/Avatar/index.tsx rename to submodules/frontend/components/Avatar/index.tsx diff --git a/submodules/frontend/components/CDPWallets/Button.tsx b/submodules/frontend/components/CDPWallets/Button.tsx new file mode 100644 index 00000000..a86b0aee --- /dev/null +++ b/submodules/frontend/components/CDPWallets/Button.tsx @@ -0,0 +1,32 @@ +import React, { useState } from "react"; +import { Flex, Text } from "@chakra-ui/react"; +import { Wallet } from "lucide-react"; +import { CDPWalletsModal } from "./Modal"; +import styles from "./CDPWallets.module.css"; + +export const CDPWalletsButton: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); + + return ( + <> + setIsOpen(true)} + className={styles.menuButton} + opacity={0.5} + pointerEvents="none" + > + + + Coinbase Developer Wallets + + + + setIsOpen(false)} /> + + ); +}; diff --git a/submodules/frontend/components/CDPWallets/CDPWallets.module.css b/submodules/frontend/components/CDPWallets/CDPWallets.module.css new file mode 100644 index 00000000..92a7f02a --- /dev/null +++ b/submodules/frontend/components/CDPWallets/CDPWallets.module.css @@ -0,0 +1,234 @@ +.menuButton { + transition: background-color 0.2s ease; + border-radius: 6px; + padding: 8px 12px; +} + +.menuButton:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.icon { + color: rgba(255, 255, 255, 0.8); +} + +/* Wallet Item */ +.walletItem { + padding: 16px; + border-radius: 8px; + background-color: rgba(255, 255, 255, 0.02); + border: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + justify-content: space-between; + align-items: flex-start; + transition: all 0.2s ease; +} + +.walletItem:hover { + background-color: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.2); +} + +.walletName { + color: white; + font-size: 12px; + font-weight: 500; +} + +.networkId { + color: rgba(255, 255, 255, 0.6); + font-size: 12px; +} + +.address { + color: rgba(255, 255, 255, 0.4); + font-size: 12px; + font-family: monospace; +} + +/* Action Buttons */ +.actions { + opacity: 0; + transition: opacity 0.2s ease; + gap: 4px; +} + +.walletItem:hover .actions { + opacity: 1; +} + +.actionButton { + background: transparent !important; + color: rgba(255, 255, 255, 0.6) !important; + padding: 6px !important; + height: auto !important; + min-width: 0 !important; + border-radius: 6px !important; + transition: all 0.2s ease !important; +} + +.copyButton:hover { + background-color: rgba(66, 153, 225, 0.1) !important; + color: #4299e1 !important; +} + +.starButton:hover { + background-color: rgba(236, 201, 75, 0.1) !important; + color: #ecc94b !important; +} + +.starButton.active { + color: #ecc94b !important; +} + +.downloadButton:hover { + background-color: rgba(72, 187, 120, 0.1) !important; + color: #48bb78 !important; +} + +.deleteButton:hover { + background-color: rgba(245, 101, 101, 0.1) !important; + color: #f56565 !important; +} + +/* Empty State */ +.emptyState { + text-align: center; + color: rgba(255, 255, 255, 0.4); + font-size: 12px; + padding: 24px; +} + +/* Form Controls */ +.label { + color: white !important; + font-size: 14px !important; + font-weight: 500 !important; + margin-bottom: 8px !important; +} + +.input, +.select { + background-color: rgba(255, 255, 255, 0.05) !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + color: white !important; + font-size: 14px !important; + border-radius: 8px !important; + height: 40px !important; +} + +.input:hover, +.select:hover { + border-color: rgba(255, 255, 255, 0.2) !important; +} + +.input:focus, +.select:focus { + border-color: rgba(255, 255, 255, 0.3) !important; + box-shadow: none !important; +} + +.input::placeholder { + color: rgba(255, 255, 255, 0.4) !important; +} + +.select option { + background-color: #080808; + color: white; +} + +.fileUploadButton { + width: 100% !important; + height: auto !important; + background-color: rgba(255, 255, 255, 0.05) !important; + border: 1px dashed rgba(255, 255, 255, 0.2) !important; + color: rgba(255, 255, 255, 0.8) !important; + padding: 24px !important; + border-radius: 8px !important; + transition: all 0.2s ease !important; +} + +.fileUploadButton:hover { + background-color: rgba(255, 255, 255, 0.08) !important; + border-color: rgba(255, 255, 255, 0.3) !important; +} + +.submitButton { + width: 100% !important; + background-color: rgba(255, 255, 255, 0.1) !important; + color: white !important; + font-size: 14px !important; + height: 40px !important; + border-radius: 8px !important; + transition: all 0.2s ease !important; +} + +.submitButton:hover { + background-color: rgba(255, 255, 255, 0.15) !important; +} + +.submitButton:active { + background-color: rgba(255, 255, 255, 0.2) !important; +} + +/* Delete Dialog */ +.deleteDialog { + background: #080808 !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + border-radius: 12px !important; + max-width: 400px !important; + margin: 16px !important; +} + +.deleteDialogHeader { + color: white !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; + padding: 16px !important; + font-size: 16px !important; + font-weight: 500 !important; +} + +.deleteDialogBody { + padding: 16px !important; +} + +.deleteConfirmText { + color: rgba(255, 255, 255, 0.8) !important; + font-size: 14px !important; + margin-bottom: 16px !important; +} + +.deleteConfirmInput { + background-color: rgba(255, 255, 255, 0.05) !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + color: white !important; + border-radius: 8px !important; +} + +.deleteDialogFooter { + border-top: 1px solid rgba(255, 255, 255, 0.1) !important; + padding: 16px !important; +} + +.cancelButton { + background-color: rgba(255, 255, 255, 0.05) !important; + color: white !important; + border-radius: 8px !important; + font-size: 14px !important; +} + +.cancelButton:hover { + background-color: rgba(255, 255, 255, 0.1) !important; +} + +.confirmDeleteButton { + background-color: rgba(245, 101, 101, 0.1) !important; + color: #f56565 !important; + margin-left: 8px !important; + border-radius: 8px !important; + font-size: 14px !important; +} + +.confirmDeleteButton:hover { + background-color: rgba(245, 101, 101, 0.2) !important; +} diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/CreateWalletForm.tsx b/submodules/frontend/components/CDPWallets/CreateWalletForm.tsx old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/CDPWallets/CreateWalletForm.tsx rename to submodules/frontend/components/CDPWallets/CreateWalletForm.tsx diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/DeleteWalletDialog.tsx b/submodules/frontend/components/CDPWallets/DeleteWalletDialog.tsx old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/CDPWallets/DeleteWalletDialog.tsx rename to submodules/frontend/components/CDPWallets/DeleteWalletDialog.tsx diff --git a/submodules/frontend/components/CDPWallets/Modal.tsx b/submodules/frontend/components/CDPWallets/Modal.tsx new file mode 100644 index 00000000..bb4dbf1a --- /dev/null +++ b/submodules/frontend/components/CDPWallets/Modal.tsx @@ -0,0 +1,532 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + Button, + VStack, + HStack, + Text, + Box, + useToast, + Input, + Select, + FormControl, + FormLabel, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + AlertDialog, + AlertDialogBody, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogContent, + AlertDialogOverlay, +} from "@chakra-ui/react"; +import { Copy, Download, Star, Trash2 } from "lucide-react"; +import styles from "./CDPWallets.module.css"; + +const NETWORKS = [ + "base-mainnet", + "base-sepolia", + "base-goerli", + "ethereum-mainnet", + "ethereum-goerli", + "ethereum-sepolia", +]; + +interface CDPWalletsModalProps { + isOpen: boolean; + onClose: () => void; +} + +interface Wallet { + wallet_id: string; + network_id: string; + address: string; +} + +export const CDPWalletsModal: React.FC = ({ + isOpen, + onClose, +}) => { + const [wallets, setWallets] = useState([]); + const [activeWallet, setActiveWallet] = useState(null); + const [newWalletName, setNewWalletName] = useState(""); + const [selectedNetwork, setSelectedNetwork] = useState(NETWORKS[0]); + const [walletFile, setWalletFile] = useState(null); + const [walletToDelete, setWalletToDelete] = useState(""); + const [confirmWalletId, setConfirmWalletId] = useState(""); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const toast = useToast(); + const cancelRef = React.useRef(null); + + const fetchWallets = useCallback(async () => { + try { + const [walletsResponse, activeWalletResponse] = await Promise.all([ + fetch("/api/wallets/list"), + fetch("/api/wallets/active"), + ]); + + const walletsData = await walletsResponse.json(); + const activeData = await activeWalletResponse.json(); + + setWallets(walletsData.wallets || []); + setActiveWallet(activeData.active_wallet_id); + } catch (error) { + console.error("Failed to fetch wallets:", error); + } + }, []); + + useEffect(() => { + if (isOpen) { + fetchWallets(); + } + }, [isOpen, fetchWallets]); + + const handleCopyAddress = (address: string) => { + navigator.clipboard.writeText(address); + toast({ + title: "Address copied", + status: "success", + duration: 2000, + position: "top-right", + }); + }; + + const handleSetActiveWallet = async (walletId: string) => { + try { + const response = await fetch("/api/wallets/active", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ wallet_id: walletId }), + }); + + if (response.ok) { + setActiveWallet(walletId); + toast({ + title: "Active wallet set", + status: "success", + duration: 2000, + position: "top-right", + }); + } + } catch (error) { + console.error("Error setting active wallet:", error); + } + }; + + const handleCreateWallet = async () => { + if (!newWalletName.trim()) { + toast({ + title: "Please enter a wallet name", + status: "warning", + duration: 2000, + position: "top-right", + }); + return; + } + + try { + const response = await fetch("/api/wallets/create", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + wallet_id: newWalletName, + network_id: selectedNetwork, + set_active: true, + }), + }); + + if (response.ok) { + toast({ + title: "Wallet created", + status: "success", + duration: 2000, + position: "top-right", + }); + setNewWalletName(""); + fetchWallets(); + } + } catch (error) { + console.error("Error creating wallet:", error); + } + }; + + const handleRestoreWallet = async () => { + if (!walletFile) { + toast({ + title: "Please select a wallet file", + status: "warning", + duration: 2000, + position: "top-right", + }); + return; + } + + try { + const fileContent = await walletFile.text(); + const walletData = JSON.parse(fileContent); + + const response = await fetch("/api/wallets/restore", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + wallet_id: walletData.wallet_id, + wallet_data: walletData, + set_active: true, + }), + }); + + if (response.ok) { + toast({ + title: "Wallet restored", + status: "success", + duration: 2000, + position: "top-right", + }); + setWalletFile(null); + fetchWallets(); + } + } catch (error) { + console.error("Error restoring wallet:", error); + } + }; + + const handleDownloadWallet = async (walletId: string) => { + try { + const response = await fetch(`/api/wallets/export/${walletId}`); + if (response.ok) { + const data = await response.json(); + const blob = new Blob([JSON.stringify(data.data, null, 2)], { + type: "application/json", + }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `${walletId}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + + toast({ + title: "Wallet exported", + status: "success", + duration: 2000, + position: "top-right", + }); + } + } catch (error) { + console.error("Error exporting wallet:", error); + } + }; + + const handleDeleteWallet = async () => { + if (confirmWalletId !== walletToDelete) { + toast({ + title: "Wallet ID doesn't match", + status: "error", + duration: 2000, + position: "top-right", + }); + return; + } + + try { + const response = await fetch(`/api/wallets/${walletToDelete}`, { + method: "DELETE", + }); + + if (response.ok) { + toast({ + title: "Wallet deleted", + status: "success", + duration: 2000, + position: "top-right", + }); + setIsDeleteOpen(false); + setConfirmWalletId(""); + setWalletToDelete(""); + fetchWallets(); + } + } catch (error) { + console.error("Error deleting wallet:", error); + } + }; + + return ( + <> + + + + + CDP Wallet Management + + + + + + + + + Wallets + + + Create + + + Restore + + + + + + + {wallets.map((wallet) => ( + + + + {wallet.wallet_id} + + + {wallet.network_id} + + + {wallet.address} + + + + + + + + + + ))} + {wallets.length === 0 && ( + + No wallets created yet + + )} + + + + + + + Wallet Name + + setNewWalletName(e.target.value)} + placeholder="Enter wallet name" + className={styles.input} + /> + + + Network + + + + + + + + + + Upload Wallet File + + + + + + + + + + + + + { + setIsDeleteOpen(false); + setConfirmWalletId(""); + setWalletToDelete(""); + }} + > + + + + Delete Wallet + + + + + To confirm deletion, please type the wallet ID:{" "} + + {walletToDelete} + + + setConfirmWalletId(e.target.value)} + placeholder="Enter wallet ID to confirm" + className={styles.deleteConfirmInput} + /> + + + + + + + + + + + ); +}; diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/RestoreWalletForm.tsx b/submodules/frontend/components/CDPWallets/RestoreWalletForm.tsx old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/CDPWallets/RestoreWalletForm.tsx rename to submodules/frontend/components/CDPWallets/RestoreWalletForm.tsx diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/WalletItem.tsx b/submodules/frontend/components/CDPWallets/WalletItem.tsx old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/CDPWallets/WalletItem.tsx rename to submodules/frontend/components/CDPWallets/WalletItem.tsx diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/index.module.css b/submodules/frontend/components/CDPWallets/index.module.css old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/CDPWallets/index.module.css rename to submodules/frontend/components/CDPWallets/index.module.css diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/index.tsx b/submodules/frontend/components/CDPWallets/index.tsx old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/CDPWallets/index.tsx rename to submodules/frontend/components/CDPWallets/index.tsx diff --git a/submodules/moragents_dockers/frontend/components/CDPWallets/useWallets.tsx b/submodules/frontend/components/CDPWallets/useWallets.tsx old mode 100644 new mode 100755 similarity index 94% rename from submodules/moragents_dockers/frontend/components/CDPWallets/useWallets.tsx rename to submodules/frontend/components/CDPWallets/useWallets.tsx index a32a33c4..f2daf7dd --- a/submodules/moragents_dockers/frontend/components/CDPWallets/useWallets.tsx +++ b/submodules/frontend/components/CDPWallets/useWallets.tsx @@ -30,8 +30,8 @@ export const useWallets = (): UseWalletsReturn => { const fetchWallets = useCallback(async () => { try { const [walletsResponse, activeWalletResponse] = await Promise.all([ - fetch("http://localhost:8080/wallets/list"), - fetch("http://localhost:8080/wallets/active"), + fetch("http://localhost:8888/wallets/list"), + fetch("http://localhost:8888/wallets/active"), ]); const walletsData = await walletsResponse.json(); @@ -60,7 +60,7 @@ export const useWallets = (): UseWalletsReturn => { const handleSetActiveWallet = async (walletId: string) => { try { - const response = await fetch("http://localhost:8080/wallets/active", { + const response = await fetch("http://localhost:8888/wallets/active", { method: "POST", headers: { "Content-Type": "application/json", @@ -105,7 +105,7 @@ export const useWallets = (): UseWalletsReturn => { } try { - const response = await fetch("http://localhost:8080/wallets/create", { + const response = await fetch("http://localhost:8888/wallets/create", { method: "POST", headers: { "Content-Type": "application/json", @@ -144,7 +144,7 @@ export const useWallets = (): UseWalletsReturn => { const fileContent = await walletFile.text(); const walletData = JSON.parse(fileContent); - const response = await fetch("http://localhost:8080/wallets/restore", { + const response = await fetch("http://localhost:8888/wallets/restore", { method: "POST", headers: { "Content-Type": "application/json", @@ -181,7 +181,7 @@ export const useWallets = (): UseWalletsReturn => { const handleDownloadWallet = async (walletId: string) => { try { const response = await fetch( - `http://localhost:8080/wallets/export/${walletId}` + `http://localhost:8888/wallets/export/${walletId}` ); if (response.ok) { @@ -237,7 +237,7 @@ export const useWallets = (): UseWalletsReturn => { try { const response = await fetch( - `http://localhost:8080/wallets/${walletId}`, + `http://localhost:8888/wallets/${walletId}`, { method: "DELETE", } diff --git a/submodules/frontend/components/Chat/hooks.ts b/submodules/frontend/components/Chat/hooks.ts new file mode 100755 index 00000000..ea448b28 --- /dev/null +++ b/submodules/frontend/components/Chat/hooks.ts @@ -0,0 +1,129 @@ +import { useCallback, useState } from "react"; +import { useAccount, useChainId, useSendTransaction } from "wagmi"; +// import { sendClaimStatus, sendSwapStatus } from "@/services/apiHooks"; +// import { getHttpClient } from "@/services/constants"; +// import { SwapTxPayloadType, ClaimTransactionPayload } from "@/services/types"; +// import { SWAP_STATUS, CLAIM_STATUS } from "@/services/constants"; + +export const useChat = (onBackendError: () => void) => { + const [txHash, setTxHash] = useState(""); + const [approveTxHash, setApproveTxHash] = useState(""); + const [showSpinner, setShowSpinner] = useState(false); + + const { address } = useAccount(); + const chainId = useChainId(); + const { sendTransaction } = useSendTransaction(); + + // const handleSwapStatus = useCallback( + // async (status: string, hash: string, isApprove: number) => { + // try { + // const response = await sendSwapStatus( + // getHttpClient(), + // chainId, + // address?.toLowerCase() || "0x", + // status, + // hash, + // isApprove + // ); + // if (isApprove) { + // setApproveTxHash(""); + // } else { + // setTxHash(""); + // } + // setShowSpinner(false); + // return response; + // } catch (error) { + // console.log(`Error sending status: ${error}`); + // onBackendError(); + // setShowSpinner(false); + // throw error; + // } + // }, + // [chainId, address, onBackendError] + // ); + + // const handleClaimStatus = useCallback( + // async (status: string, hash: string) => { + // try { + // const response = await sendClaimStatus( + // getHttpClient(), + // chainId, + // address?.toLowerCase() || "0x", + // status, + // hash + // ); + // setTxHash(""); + // setShowSpinner(false); + // return response; + // } catch (error) { + // console.log(`Error sending claim status: ${error}`); + // onBackendError(); + // setShowSpinner(false); + // throw error; + // } + // }, + // [chainId, address, onBackendError] + // ); + + // const handleSwapSubmit = useCallback( + // (swapTx: SwapTxPayloadType) => { + // setTxHash(""); + // sendTransaction( + // { + // account: address, + // data: (swapTx?.tx.data || "0x") as `0x${string}`, + // to: (swapTx?.tx.to || "0x") as `0x${string}`, + // value: BigInt(swapTx?.tx.value || "0"), + // }, + // { + // onSuccess: (hash) => { + // setTxHash(hash); + // handleSwapStatus(SWAP_STATUS.INIT, hash, 0); + // }, + // onError: (error) => { + // console.log(`Error sending transaction: ${error}`); + // handleSwapStatus(SWAP_STATUS.FAIL, "", 0); + // }, + // } + // ); + // }, + // [address, handleSwapStatus, sendTransaction] + // ); + + // const handleClaimSubmit = useCallback( + // (claimTx: ClaimTransactionPayload) => { + // setTxHash(""); + // sendTransaction( + // { + // account: address, + // data: claimTx.data as `0x${string}`, + // to: claimTx.to as `0x${string}`, + // value: BigInt(claimTx.value), + // chainId: parseInt(claimTx.chainId), + // }, + // { + // onSuccess: (hash) => { + // setTxHash(hash); + // handleClaimStatus(CLAIM_STATUS.INIT, hash); + // }, + // onError: (error) => { + // console.log(`Error sending transaction: ${error}`); + // handleClaimStatus(CLAIM_STATUS.FAIL, ""); + // }, + // } + // ); + // }, + // [address, handleClaimStatus, sendTransaction] + // ); + + return { + txHash, + approveTxHash, + showSpinner, + setShowSpinner, + // handleSwapStatus, + // handleClaimStatus, + // handleSwapSubmit, + // handleClaimSubmit, + }; +}; diff --git a/submodules/frontend/components/Chat/index.module.css b/submodules/frontend/components/Chat/index.module.css new file mode 100644 index 00000000..acd83253 --- /dev/null +++ b/submodules/frontend/components/Chat/index.module.css @@ -0,0 +1,12 @@ +/* Update Chat component styles */ +.chatContainer { + width: 100%; + display: flex; + flex-direction: column; +} + +@supports (padding-bottom: env(safe-area-inset-bottom)) { + .chatContainer { + padding-bottom: env(safe-area-inset-bottom); + } +} diff --git a/submodules/frontend/components/Chat/index.tsx b/submodules/frontend/components/Chat/index.tsx new file mode 100755 index 00000000..ab0ed90f --- /dev/null +++ b/submodules/frontend/components/Chat/index.tsx @@ -0,0 +1,56 @@ +import React, { FC, useState } from "react"; +import { Box, useBreakpointValue } from "@chakra-ui/react"; +import { MessageList } from "@/components/MessageList"; +import { ChatInput } from "@/components/ChatInput"; +import { useChatContext } from "@/contexts/chat/useChatContext"; +import styles from "./index.module.css"; + +export const Chat: FC<{ isSidebarOpen?: boolean }> = ({ + isSidebarOpen = false, +}) => { + const { state, sendMessage } = useChatContext(); + const { messages, currentConversationId, isLoading } = state; + const [localLoading, setLocalLoading] = useState(false); + + const currentMessages = messages[currentConversationId] || []; + const isMobile = useBreakpointValue({ base: true, md: false }); + const showLoading = isLoading || localLoading; + + const handleSubmit = async (message: string, file: File | null) => { + try { + setLocalLoading(true); + await sendMessage(message, file); + setTimeout(() => setLocalLoading(false), 200); + } catch (error) { + console.error("Error sending message:", error); + setLocalLoading(false); + } + }; + + return ( + + + + {/* Give ChatInput a higher z-index than sidebar to ensure it's always accessible */} + + + + ); +}; diff --git a/submodules/moragents_dockers/frontend/components/Chat/types.ts b/submodules/frontend/components/Chat/types.ts old mode 100644 new mode 100755 similarity index 100% rename from submodules/moragents_dockers/frontend/components/Chat/types.ts rename to submodules/frontend/components/Chat/types.ts diff --git a/submodules/frontend/components/ChatInput/Commands.module.css b/submodules/frontend/components/ChatInput/Commands.module.css new file mode 100644 index 00000000..22215c93 --- /dev/null +++ b/submodules/frontend/components/ChatInput/Commands.module.css @@ -0,0 +1,85 @@ +.commandsContainer { + background-color: #080808; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); + max-height: 200px; + overflow-y: auto; + z-index: 99999; + padding: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + position: fixed; + bottom: 55px; + left: 50%; + transform: translateX(-50%); + width: 100%; + max-width: 800px; + pointer-events: auto; +} + +@media (max-width: 768px) { + .commandsContainer { + max-height: 200px; + width: calc(100% - 1rem); + margin: 0 0.5rem; + } +} + +.commandsContainer::-webkit-scrollbar { + width: 6px; +} + +.commandsContainer::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.02); + border-radius: 4px; +} + +.commandsContainer::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; +} + +.commandsContainer::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.15); +} + +.commandItem { + padding: 8px 12px; + background-color: transparent; + cursor: pointer; + transition: background-color 0.2s ease; + border-radius: 8px; + margin-bottom: 2px; +} + +.commandItem:last-child { + margin-bottom: 0; +} + +.commandItem:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.commandItem.selected { + background-color: rgba(255, 255, 255, 0.08); +} + +.commandText { + color: #59f886; + font-size: 14px; + margin-bottom: 2px; + transition: color 0.2s ease; +} + +.commandItem:hover .commandText { + color: #6aff97; +} + +.description { + color: rgba(255, 255, 255, 0.6); + font-size: 12px; + transition: color 0.2s ease; +} + +.commandItem:hover .description { + color: rgba(255, 255, 255, 0.8); +} diff --git a/submodules/frontend/components/ChatInput/Commands.tsx b/submodules/frontend/components/ChatInput/Commands.tsx new file mode 100644 index 00000000..9acfab32 --- /dev/null +++ b/submodules/frontend/components/ChatInput/Commands.tsx @@ -0,0 +1,66 @@ +import React, { FC, useRef, useEffect } from "react"; +import { Box, VStack, Text, useMediaQuery } from "@chakra-ui/react"; +import styles from "./Commands.module.css"; + +export type Command = { + command: string; + description: string; + name: string; +}; + +type CommandsProps = { + commands: Command[]; + selectedIndex: number; + onSelect: (command: Command) => void; + isSidebarOpen: boolean; +}; + +export const Commands: FC = ({ + commands, + selectedIndex, + onSelect, + isSidebarOpen, +}) => { + const [isMobile] = useMediaQuery("(max-width: 768px)"); + const commandsRef = useRef(null); + + useEffect(() => { + if (commandsRef.current) { + const selectedElement = commandsRef.current.querySelector( + `[data-index="${selectedIndex}"]` + ); + if (selectedElement) { + selectedElement.scrollIntoView({ + block: "nearest", + behavior: "smooth", + }); + } + } + }, [selectedIndex]); + + return ( + + + {commands.map((cmd, index) => ( + onSelect(cmd)} + role="button" + tabIndex={0} + > + + /{cmd.command} + + + {cmd.name} - {cmd.description} + + + ))} + + + ); +}; diff --git a/submodules/frontend/components/ChatInput/CommandsPortal.tsx b/submodules/frontend/components/ChatInput/CommandsPortal.tsx new file mode 100644 index 00000000..f5e8a7c9 --- /dev/null +++ b/submodules/frontend/components/ChatInput/CommandsPortal.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { createPortal } from "react-dom"; +import { Commands, Command } from "./Commands"; + +type CommandsPortalProps = { + commands: Command[]; + selectedIndex: number; + onSelect: (command: Command) => void; + isSidebarOpen: boolean; +}; + +export const CommandsPortal: React.FC = (props) => { + React.useEffect(() => { + const portalId = "commands-portal"; + if (!document.getElementById(portalId)) { + const div = document.createElement("div"); + div.id = portalId; + document.body.appendChild(div); + } + }, []); + + return createPortal( + , + document.getElementById("commands-portal") || document.body + ); +}; diff --git a/submodules/frontend/components/ChatInput/PrefilledOptions.constants.tsx b/submodules/frontend/components/ChatInput/PrefilledOptions.constants.tsx new file mode 100755 index 00000000..bd9848a6 --- /dev/null +++ b/submodules/frontend/components/ChatInput/PrefilledOptions.constants.tsx @@ -0,0 +1,219 @@ +import { + Sparkles, + FileText, + DollarSign, + Send, + Search, + Newspaper, + Trophy, + LineChart, + Flame, + Globe2, + ArrowLeftRight, + BarChart2, + Shield, + Gift, + Database, +} from "lucide-react"; +import { AgentType } from "@/services/types"; +import { IconSocial } from "@tabler/icons-react"; + +// Icon color mapping +const ICON_COLORS = { + [AgentType.IMAGEN]: "#8B5CF6", // Purple + [AgentType.RAG]: "#8B5CF6", // Purple + [AgentType.CRYPTO_DATA]: "#3B82F6", // Blue + [AgentType.TOKEN_SWAP]: "#10B981", // Green + [AgentType.TWEET_SIZZLER]: "#F59E0B", // Amber + [AgentType.REALTIME_SEARCH]: "#EC4899", // Pink + [AgentType.DEXSCREENER]: "#6366F1", // Indigo + [AgentType.MOR_CLAIMS]: "#14B8A6", // Teal + [AgentType.MOR_REWARDS]: "#F472B6", // Pink + [AgentType.CRYPTO_NEWS]: "#2DD4BF", // Teal + [AgentType.BASE_AGENT]: "#818CF8", // Indigo + [AgentType.DCA_AGENT]: "#4ADE80", // Green + [AgentType.RUGCHECK]: "#EF4444", // Red + [AgentType.DEFAULT]: "#6B7280", // Gray + [AgentType.ELFA]: "#6366F1", // Indigo + [AgentType.CODEX]: "#3B82F6", // Blue +}; + +export const prefilledOptionsMap = { + [AgentType.DEFAULT]: { + title: "Default Agent", + icon: , + examples: [ + { text: "Who is Elon Musk?", agent: "default" }, + { text: "What Morpheus agents are currently active?", agent: "default" }, + ], + }, + [AgentType.IMAGEN]: { + title: "Generate Images", + icon: , + examples: [ + { text: "Generate an image of Donald Trump", agent: "imagen" }, + { + text: "Create a cyberpunk style portrait of Elon Musk", + agent: "imagen", + }, + ], + }, + [AgentType.RAG]: { + title: "Document Analysis", + icon: , + examples: [ + { text: "Summarize the uploaded document", agent: "rag" }, + { + text: "What are the key points in this uploaded document?", + agent: "rag", + }, + ], + }, + [AgentType.CRYPTO_DATA]: { + title: "Crypto Market Data", + icon: , + examples: [ + { text: "What's the price of ETH?", agent: "crypto_data" }, + { text: "What's the FDV of USDC?", agent: "crypto_data" }, + ], + }, + [AgentType.TOKEN_SWAP]: { + title: "Token Swaps", + icon: ( + + ), + examples: [ + { text: "Swap 0.2 ETH for USDC", agent: "token_swap" }, + { text: "Exchange my BTC for ETH", agent: "token_swap" }, + ], + }, + [AgentType.TWEET_SIZZLER]: { + title: "Tweet Generator", + icon: , + examples: [ + { text: "Create a viral tweet about Web3", agent: "tweet_sizzler" }, + { + text: "Create a spicy crypto market tweet about Gary Gensler", + agent: "tweet_sizzler", + }, + ], + }, + // [AgentType.DCA_AGENT]: { + // title: "DCA Strategy Planning", + // icon: , + // examples: [ + // { text: "DCA into ETH weekly", agent: "dca_agent" }, + // { + // text: "Help me create a monthly BTC buying strategy", + // agent: "dca_agent", + // }, + // ], + // }, + [AgentType.BASE_AGENT]: { + title: "Base Transactions", + icon: , + examples: [ + { text: "Send USDC on Base", agent: "base_agent" }, + { text: "Swap USDC for ETH on Base", agent: "base_agent" }, + ], + }, + [AgentType.MOR_CLAIMS]: { + title: "MOR Claims Manager", + icon: , + examples: [ + { text: "Claim my MOR rewards", agent: "mor_claims" }, + { text: "Help me claim my pending MOR tokens", agent: "mor_claims" }, + ], + }, + [AgentType.MOR_REWARDS]: { + title: "MOR Rewards Tracking", + icon: , + examples: [ + { text: "Show my MOR rewards balance", agent: "mor_rewards" }, + { text: "Calculate my pending MOR rewards", agent: "mor_rewards" }, + ], + }, + // [AgentType.REALTIME_SEARCH]: { + // title: "Real-Time Search", + // icon: , + // examples: [ + // { + // text: "Search the web for latest news about Ethereum", + // agent: "realtime_search", + // }, + // { + // text: "What did Donald Trump say about Bitcoin?", + // agent: "realtime_search", + // }, + // ], + // }, + [AgentType.CRYPTO_NEWS]: { + title: "Crypto News Analysis", + icon: , + examples: [ + { + text: "Analyze recent crypto market news for ETH", + agent: "crypto_news", + }, + { text: "What's the latest news impact on BTC?", agent: "crypto_news" }, + ], + }, + [AgentType.DEXSCREENER]: { + title: "DexScreener", + icon: , + examples: [ + { + text: "Scan Dexscreener for the most active tokens on solana", + agent: "dexscreener", + }, + { + text: "Show me DEX activity for ETH as reported by Dexscreener", + agent: "dexscreener", + }, + ], + }, + [AgentType.ELFA]: { + title: "Elfa Social Search", + icon: , + examples: [ + { + text: "Search for mentions of MOR on social media", + agent: "elfa", + }, + { + text: "What are the top trending tokens on social media?", + agent: "elfa", + }, + ], + }, + [AgentType.RUGCHECK]: { + title: "Solana Token Safety", + icon: , + examples: [ + { text: "Check token safety for SAMO", agent: "rugcheck" }, + { text: "Show me the most viewed tokens on rugcheck", agent: "rugcheck" }, + ], + }, + [AgentType.CODEX]: { + title: "Codex Agent", + icon: , + examples: [ + { + text: "What are the top trending tokens on Ethereum?", + agent: "codex", + }, + { + text: "Who are the top holders of $TRUMP on Solana?", + agent: "codex", + }, + ], + }, +}; + +export const OPTION_GROUPS = { + Data: [AgentType.CRYPTO_DATA, AgentType.DEXSCREENER, AgentType.CODEX], + // Trade: [AgentType.TOKEN_SWAP, AgentType.BASE_AGENT], + Social: [AgentType.TWEET_SIZZLER, AgentType.ELFA], + Morpheus: [AgentType.MOR_REWARDS], + Analysis: [AgentType.RAG, AgentType.CRYPTO_NEWS, AgentType.RUGCHECK], +}; diff --git a/submodules/frontend/components/ChatInput/PrefilledOptions.module.css b/submodules/frontend/components/ChatInput/PrefilledOptions.module.css new file mode 100755 index 00000000..0bdff9c6 --- /dev/null +++ b/submodules/frontend/components/ChatInput/PrefilledOptions.module.css @@ -0,0 +1,172 @@ +.prefilledContainer { + position: relative; + width: 100%; + background-color: transparent; + transition: padding 0.3s ease; + z-index: 100; /* Add a z-index lower than the sidebar */ +} + +.prefilledInner { + width: 100%; + margin: 0 auto; + display: flex; + padding: 0.2rem; + flex-direction: column; + background-color: #000000; + border-radius: 12px 12px 0 0; + transition: transform 0.3s ease, opacity 0.3s ease; + position: relative; /* Ensure the component has its own stacking context */ +} + +/* Pills Container */ +.pillsContainer { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; +} + +@media (min-width: 768px) { + .prefilledContainer { + padding-left: 2% !important; + padding-right: 2% !important; + } +} + +.pillButton { + background: transparent; + border: 1px solid rgba(255, 255, 255, 0.2); + color: rgba(255, 255, 255, 0.8); + padding: 4px 12px; + border-radius: 20px; + font-size: 12px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; + position: relative; + overflow: hidden; +} + +.pillButton:hover { + background: rgba(255, 255, 255, 0.1); +} + +.pillButton.selected { + background: rgb(64, 65, 79); + border-color: transparent; + color: white; +} + +/* Examples Panel */ +.examplesPanel { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; + margin-top: 1rem; + padding: 0 1rem; + width: 100%; + opacity: 0; + + /* Modify transition to be more specific */ + transition: opacity 0.3s ease; + + /* Add these to prevent blur during animation */ + backface-visibility: hidden; + -webkit-font-smoothing: antialiased; + will-change: opacity; +} + +@media (min-width: 768px) { + .examplesPanel { + grid-template-columns: repeat( + auto-fit, + minmax(200px, 1fr) + ); /* Auto-fit grid for desktop */ + } +} + +.examplesPanel.visible { + opacity: 1; + transform: translateY(0); +} + +.exampleGroup { + display: flex; + flex-direction: column; + gap: 0.5rem; + opacity: 0; + transform: translateY(10px); + animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.exampleHeader { + display: flex; + align-items: center; + gap: 0.5rem; + color: white; +} + +.exampleIcon svg { + width: 20px; + height: 20px; + transition: transform 0.2s ease; +} + +.exampleTitle { + font-size: 12px; + font-weight: 500; + color: white; +} + +.exampleButtons { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.exampleButton { + /* Previous styles remain the same */ + text-align: left; + background: transparent; + border: 1px solid rgba(255, 255, 255, 0.2); + color: rgba(255, 255, 255, 0.8); + padding: 8px 12px; + border-radius: 8px; + font-size: 12px; + cursor: pointer; + line-height: 1.4; + position: relative; + overflow: hidden; + + /* Modified transition */ + transition: background 0.2s cubic-bezier(0.4, 0, 0.2, 1); + + /* Add these properties to fix text blur */ + backface-visibility: hidden; + -webkit-font-smoothing: antialiased; + transform: translateZ(0); + will-change: transform, background; +} + +.exampleButton:hover { + background: rgba(255, 255, 255, 0.1); + /* Remove transform on hover to prevent blur */ + transform: translateZ(0); +} + +@keyframes slideIn { + 0% { + opacity: 0; + transform: translateX(5px); + } + 100% { + opacity: 1; + transform: translateX(0); + } +} + +/* Support for iOS safe areas */ +@supports (padding-bottom: env(safe-area-inset-bottom)) { + .prefilledContainer { + padding-bottom: env(safe-area-inset-bottom); + } +} diff --git a/submodules/frontend/components/ChatInput/PrefilledOptions.tsx b/submodules/frontend/components/ChatInput/PrefilledOptions.tsx new file mode 100755 index 00000000..3c7d30f2 --- /dev/null +++ b/submodules/frontend/components/ChatInput/PrefilledOptions.tsx @@ -0,0 +1,130 @@ +import React, { useState, useEffect } from "react"; +import { + prefilledOptionsMap, + OPTION_GROUPS, +} from "./PrefilledOptions.constants"; +import styles from "./PrefilledOptions.module.css"; + +interface PrefilledOptionsProps { + onSelect: (text: string) => void; + isSidebarOpen: boolean; + onExpandChange?: (isExpanded: boolean, selectedGroup: string | null) => void; +} + +const PrefilledOptions: React.FC = ({ + onSelect, + isSidebarOpen = true, + onExpandChange, +}) => { + const [selectedGroup, setSelectedGroup] = useState(null); + const [isMobile, setIsMobile] = useState(false); + const [isExamplesPanelVisible, setIsExamplesPanelVisible] = useState(false); + + useEffect(() => { + const checkMobile = () => setIsMobile(window.innerWidth < 768); + checkMobile(); + window.addEventListener("resize", checkMobile); + return () => window.removeEventListener("resize", checkMobile); + }, []); + + // Notify parent component about expansion state + useEffect(() => { + if (onExpandChange) { + onExpandChange(isExamplesPanelVisible, selectedGroup); + } + }, [selectedGroup, isExamplesPanelVisible, onExpandChange]); + + // This effect ensures panel visibility follows selected group state + useEffect(() => { + if (selectedGroup) { + setIsExamplesPanelVisible(true); + } else { + setIsExamplesPanelVisible(false); + } + }, [selectedGroup]); + + const handleGroupClick = (group: string) => { + if (selectedGroup === group) { + setSelectedGroup(null); + } else { + setSelectedGroup(group); + } + }; + + // Calculate container style with adjusted padding to avoid sidebar overlap + const containerStyle = isMobile + ? { width: "100%" } + : { + paddingLeft: isSidebarOpen ? "380px" : "100px", // Adjusted to be wider than the sidebar (360px) + paddingRight: "20%", + position: "relative" as const, + zIndex: 100, // Ensure it's above content but below sidebar + }; + + const renderExamples = () => { + if (!selectedGroup) return null; + + return ( +
+ {OPTION_GROUPS[selectedGroup as keyof typeof OPTION_GROUPS].map( + (agentType) => { + const option = + prefilledOptionsMap[ + agentType as keyof typeof prefilledOptionsMap + ]; + if (!option) return null; + + return ( +
+
+ {option.icon} + {option.title} +
+
+ {option.examples.map( + (example: { text: string }, index: number) => ( + + ) + )} +
+
+ ); + } + )} +
+ ); + }; + + return ( +
+
+
+ {Object.keys(OPTION_GROUPS).map((group) => ( + + ))} +
+ {renderExamples()} +
+
+ ); +}; + +export default PrefilledOptions; diff --git a/submodules/frontend/components/ChatInput/index.module.css b/submodules/frontend/components/ChatInput/index.module.css new file mode 100755 index 00000000..3fab0e85 --- /dev/null +++ b/submodules/frontend/components/ChatInput/index.module.css @@ -0,0 +1,150 @@ +.container { + position: relative; + width: 100%; + background-color: transparent; + margin-bottom: 1.5rem; +} + +.flexContainer { + margin-top: 0.5rem; + padding-left: 0.25rem; + padding-right: 0.25rem; + background-color: transparent; + width: 100%; +} + +.inputGroup { + margin-bottom: 0.2rem; + border-radius: 8px; + background-color: #333; + width: 100%; + max-width: 100%; +} + +@media (min-width: 640px) { + .inputGroup { + max-width: 800px; + margin-left: auto; + margin-right: auto; + } +} + +.fileAddon { + background-color: transparent !important; + border: none !important; + color: #59f886 !important; + cursor: pointer; + padding: 8px !important; + display: flex; + align-items: center; +} + +.fileAddon button { + width: 32px !important; + height: 32px !important; + min-width: 32px !important; + padding: 0 !important; +} + +.hiddenInput { + display: none; +} + +.disabledIcon { + color: #6c6c6c !important; +} + +.messageInput { + border: none !important; + color: white; + width: 100%; + font-size: 16px !important; /* Ensure this is at least 16px to prevent zoom */ + margin-top: 0.15rem; + -webkit-text-size-adjust: 100%; /* Prevent iOS from adjusting text size */ + /* Add the following to prevent zoom */ + touch-action: manipulation; +} +@media (min-width: 640px) { + .messageInput { + font-size: 14px; + } +} + +.messageInput:focus { + border-color: transparent !important; + box-shadow: none !important; +} + +.rightAddon { + background-color: transparent !important; + border: none !important; +} + +.sendButton { + background-color: transparent !important; + color: #59f886 !important; +} + +@media (max-width: 768px) { + .sendButton { + padding: 0.25rem; + } +} + +.sendButton:hover { + background-color: transparent !important; +} + +/* New styles for file preview */ +.filePreview { + margin: 0 auto; + padding: 0.5rem; + background-color: #333; + border-radius: 8px; + max-width: 800px; + margin-bottom: 0.5rem; +} + +.fileInfo { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem; +} + +.fileIcon { + font-size: 1.5rem; + margin-right: 0.75rem; +} + +.fileName { + font-size: 14px; + font-weight: 500; + color: white; + margin-bottom: 0.25rem; + max-width: 250px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.fileType { + font-size: 12px; + color: #a0a0a0; +} + +.removeFile { + color: white; + background: transparent !important; +} + +.removeFile:hover { + background: rgba(255, 255, 255, 0.1) !important; +} + +.uploadingIndicator { + padding: 0.5rem; + text-align: center; + color: #a0a0a0; + font-size: 12px; +} diff --git a/submodules/frontend/components/ChatInput/index.tsx b/submodules/frontend/components/ChatInput/index.tsx new file mode 100755 index 00000000..29521d54 --- /dev/null +++ b/submodules/frontend/components/ChatInput/index.tsx @@ -0,0 +1,220 @@ +import React, { FC, useState, useEffect, useRef } from "react"; +import { + Textarea, + InputGroup, + InputLeftAddon, + InputRightAddon, + IconButton, + useMediaQuery, +} from "@chakra-ui/react"; +import { AttachmentIcon } from "@chakra-ui/icons"; +import { SendIcon } from "../CustomIcon/SendIcon"; +import { Command } from "./Commands"; +import { CommandsPortal } from "./CommandsPortal"; +import styles from "./index.module.css"; +import API_BASE_URL from "../../config"; + +type ChatInputProps = { + onSubmit: (message: string, file: File | null) => Promise; + disabled: boolean; + isSidebarOpen: boolean; +}; + +export const ChatInput: FC = ({ + onSubmit, + disabled, + isSidebarOpen, +}) => { + const [message, setMessage] = useState(""); + const [file, setFile] = useState(null); + const [commands, setCommands] = useState([]); + const [showCommands, setShowCommands] = useState(false); + const [selectedCommandIndex, setSelectedCommandIndex] = useState(0); + const [isMobile] = useMediaQuery("(max-width: 768px)"); + const [isSubmitting, setIsSubmitting] = useState(false); + + const inputGroupRef = useRef(null); + const inputRef = useRef(null); + + // Add this useEffect to prevent focus zoom on mobile + useEffect(() => { + // Add meta viewport tag to prevent zoom + const viewportMeta = document.createElement("meta"); + viewportMeta.name = "viewport"; + viewportMeta.content = + "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"; + + // Check if there's already a viewport meta tag + const existingMeta = document.querySelector('meta[name="viewport"]'); + + if (existingMeta) { + // Update existing meta tag + existingMeta.setAttribute( + "content", + "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" + ); + } else { + // Add new meta tag + document.head.appendChild(viewportMeta); + } + + // Optional cleanup + return () => { + if (!existingMeta && viewportMeta.parentNode) { + document.head.removeChild(viewportMeta); + } + }; + }, []); + + // Fetch commands + useEffect(() => { + fetch(`${API_BASE_URL}/agents/commands`) + .then((res) => res.json()) + .then((data) => setCommands(data.commands)) + .catch((error) => console.error("Error fetching commands:", error)); + }, []); + + // Filter commands based on input + const filteredCommands = message.startsWith("/") + ? commands.filter((cmd) => + cmd.command.toLowerCase().includes(message.slice(1).toLowerCase()) + ) + : []; + + // Show/hide commands dropdown based on input + useEffect(() => { + setShowCommands(message.startsWith("/") && filteredCommands.length > 0); + setSelectedCommandIndex(0); + }, [message, filteredCommands.length]); + + const handleCommandSelect = (command: Command) => { + setMessage(`/${command.command} `); + setShowCommands(false); + inputRef.current?.focus(); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (showCommands) { + switch (e.key) { + case "ArrowDown": + e.preventDefault(); + setSelectedCommandIndex((prev) => + Math.min(prev + 1, filteredCommands.length - 1) + ); + break; + case "ArrowUp": + e.preventDefault(); + setSelectedCommandIndex((prev) => Math.max(prev - 1, 0)); + break; + case "Tab": + case "Enter": + e.preventDefault(); + handleCommandSelect(filteredCommands[selectedCommandIndex]); + break; + case "Escape": + setShowCommands(false); + break; + } + } else if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSubmit(); + } + }; + + const agentSupportsFileUploads = !isMobile; + + const handleSubmit = async () => { + if ((!message && !file) || isSubmitting || disabled) return; + + try { + setIsSubmitting(true); + const messageToSend = message; + const fileToSend = file; + + // Clear input immediately to improve UX + setMessage(""); + setFile(null); + + // Then submit the message + await onSubmit(messageToSend, fileToSend); + } catch (error) { + console.error("Error submitting message:", error); + } finally { + setIsSubmitting(false); + } + }; + + return ( + <> + {showCommands && ( + + )} + +
+ + {agentSupportsFileUploads && ( + + setFile(e.target.files?.[0] || null)} + disabled={isSubmitting || disabled} + /> + + } + className={isSubmitting || disabled ? styles.disabledIcon : ""} + disabled={isSubmitting || disabled} + onClick={() => + document + .querySelector('input[type="file"]') + ?.dispatchEvent(new MouseEvent("click")) + } + /> + + )} +