├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── close-inactive-issues.yml │ ├── python-lint.yml │ ├── python-package.yml │ ├── python-publish.yml │ └── python-test.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── copilot_api ├── __init__.py ├── cli.py ├── copilot.py ├── exceptions.py └── utils.py ├── requirements.txt ├── setup.py └── t.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 2 | patreon: # Replace with a single Patreon username 3 | open_collective: # Replace with a single Open Collective username 4 | ko_fi: # Replace with a single Ko-fi username 5 | tidelift: # Replace with your Tidelift platform-name/package-name 6 | community_bridge: # Replace with your Community Bridge project-name 7 | liberapay: # Replace with your Liberapay username 8 | issuehunt: # Replace with your IssueHunt username 9 | otechie: # Replace with your Otechie username 10 | custom: ['https://buymeacoffee.com/oevortex'] # Replace with up to 4 custom sponsorship URLs 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: Create a report to help us improve 4 | title: "bug: " 5 | labels: ["🐛 bug"] 6 | assignees: '' 7 | --- 8 | 9 | ## 🐛 Bug Description 10 | 11 | 12 | ## 🔄 To Reproduce 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | ## ✅ Expected Behavior 20 | 21 | 22 | ## 📸 Screenshots 23 | 24 | 25 | ## 💻 Environment 26 | - OS: [e.g. Windows, macOS, Linux] 27 | - Python Version: [e.g. 3.11] 28 | - Package Version: [e.g. 1.0.0] 29 | 30 | ## 📝 Additional Context 31 | 32 | 33 | ## 🔍 Possible Solution 34 | 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature Request 3 | about: Suggest an idea for this project 4 | title: "feat: " 5 | labels: ["✨ enhancement"] 6 | assignees: '' 7 | --- 8 | 9 | ## 🎯 Feature Description 10 | 11 | 12 | ## 🔄 Use Case 13 | 14 | 15 | ## 🛠️ Proposed Solution 16 | 17 | 18 | ## 🔄 Alternatives Considered 19 | 20 | 21 | ## 📝 Additional Context 22 | 23 | 24 | ## ✅ Definition of Done 25 | 26 | - [ ] Implementation 27 | - [ ] Tests 28 | - [ ] Documentation 29 | - [ ] Example Usage 30 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 📝 Description 2 | 3 | 4 | ## 🔄 Type of Change 5 | 6 | - [ ] 🐛 Bug fix (non-breaking change which fixes an issue) 7 | - [ ] ✨ New feature (non-breaking change which adds functionality) 8 | - [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected) 9 | - [ ] 📚 Documentation update 10 | - [ ] 🎨 Style update (formatting, renaming) 11 | - [ ] ♻️ Code refactoring (no functional changes) 12 | - [ ] ⚡ Performance improvement 13 | - [ ] ✅ Test update 14 | 15 | ## 🧪 How Has This Been Tested? 16 | 17 | - [ ] Unit Tests 18 | - [ ] Integration Tests 19 | - [ ] Manual Testing 20 | 21 | ## 📸 Screenshots 22 | 23 | 24 | ## 📝 Checklist 25 | 26 | - [ ] My code follows the code style of this project 27 | - [ ] I have updated the documentation accordingly 28 | - [ ] I have added tests to cover my changes 29 | - [ ] All new and existing tests passed 30 | - [ ] My changes generate no new warnings 31 | - [ ] I have checked my code and corrected any misspellings 32 | 33 | ## 📄 Additional Notes 34 | 35 | -------------------------------------------------------------------------------- /.github/workflows/close-inactive-issues.yml: -------------------------------------------------------------------------------- 1 | name: 🧹 Close Inactive Issues 2 | 3 | on: 4 | schedule: 5 | - cron: "0 1 * * *" # Run at 7:00 AM IST 6 | 7 | jobs: 8 | close-issues: 9 | name: 🔍 Check Inactive Issues 10 | runs-on: ubuntu-latest 11 | permissions: 12 | issues: write 13 | pull-requests: write 14 | 15 | steps: 16 | - name: 🔄 Close Stale Issues and PRs 17 | uses: actions/stale@v8 18 | with: 19 | # Issues 20 | days-before-issue-stale: 7 21 | days-before-issue-close: 7 22 | stale-issue-message: "👋 This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs." 23 | close-issue-message: "🔒 This issue has been automatically closed due to inactivity. Feel free to reopen it if you still need help!" 24 | stale-issue-label: "🏷️ stale" 25 | 26 | # Pull Requests 27 | days-before-pr-stale: 7 28 | days-before-pr-close: 7 29 | stale-pr-message: "👋 This PR has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs." 30 | close-pr-message: "🔒 This PR has been automatically closed due to inactivity. Feel free to reopen it if you'd like to continue working on it!" 31 | stale-pr-label: "🏷️ stale" 32 | 33 | # General 34 | exempt-issue-labels: "🚫 no-stale,🐛 bug,✨ enhancement" 35 | exempt-pr-labels: "🚫 no-stale,🔄 in progress" 36 | operations-per-run: 100 37 | ascending: true 38 | -------------------------------------------------------------------------------- /.github/workflows/python-lint.yml: -------------------------------------------------------------------------------- 1 | name: 🎨 Code Style 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | lint: 11 | name: 🔍 Code Quality Checks 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: 🐍 Set up Python 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: "3.11" 21 | check-latest: true 22 | 23 | - name: 📦 Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install black isort flake8 mypy 27 | 28 | - name: 🎨 Check code formatting with Black 29 | run: | 30 | black --check . 31 | 32 | - name: 🔄 Check import sorting with isort 33 | run: | 34 | isort --check-only --diff . 35 | 36 | - name: 🔍 Check code style with flake8 37 | run: | 38 | flake8 . 39 | 40 | - name: 🔎 Check types with mypy 41 | run: | 42 | mypy copilot_api/ 43 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | name: Python CI/CD 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | tags: 7 | - 'v*' 8 | pull_request: 9 | branches: [ "main" ] 10 | workflow_dispatch: 11 | 12 | jobs: 13 | quality: 14 | name: Code Quality 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [ubuntu-latest, windows-latest, macos-latest] 20 | python-version: ['3.11'] 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v4 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | cache: 'pip' 30 | 31 | - name: Install dependencies 32 | run: | 33 | python -m pip install --upgrade pip 34 | pip install -r requirements.txt 35 | pip install black flake8 isort mypy 36 | pip install -e ".[dev]" 37 | 38 | - name: Check formatting with Black 39 | run: black . --check --diff 40 | 41 | - name: Check imports with isort 42 | run: isort . --check --diff 43 | 44 | - name: Lint with flake8 45 | run: | 46 | flake8 . --count --show-source --statistics 47 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics 48 | 49 | - name: Type check with mypy 50 | run: mypy copilot_api 51 | 52 | test: 53 | name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }} 54 | runs-on: ${{ matrix.os }} 55 | strategy: 56 | fail-fast: false 57 | matrix: 58 | os: [ubuntu-latest, windows-latest, macos-latest] 59 | python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] 60 | exclude: 61 | # Exclude some combinations that might be problematic 62 | - os: windows-latest 63 | python-version: '3.7' 64 | 65 | steps: 66 | - uses: actions/checkout@v4 67 | 68 | - name: Set up Python ${{ matrix.python-version }} 69 | uses: actions/setup-python@v4 70 | with: 71 | python-version: ${{ matrix.python-version }} 72 | cache: 'pip' 73 | allow-prereleases: true 74 | 75 | - name: Install dependencies 76 | run: | 77 | python -m pip install --upgrade pip 78 | pip install -r requirements.txt 79 | pip install pytest pytest-cov pytest-asyncio 80 | pip install -e ".[test]" 81 | 82 | - name: Run tests 83 | run: | 84 | pytest --cov=copilot_api --cov-report=xml --cov-report=term-missing tests/ 85 | 86 | - name: Upload coverage to Codecov 87 | uses: codecov/codecov-action@v3 88 | with: 89 | file: ./coverage.xml 90 | fail_ci_if_error: false 91 | flags: ${{ matrix.os }},python-${{ matrix.python-version }} 92 | 93 | compatibility: 94 | name: Compatibility Check 95 | runs-on: ubuntu-latest 96 | steps: 97 | - uses: actions/checkout@v4 98 | 99 | - name: Set up Python 100 | uses: actions/setup-python@v4 101 | with: 102 | python-version: '3.11' 103 | 104 | - name: Check Python compatibility 105 | run: | 106 | pip install vermin 107 | vermin --no-tips --violations --target=3.7 copilot_api/ 108 | 109 | release: 110 | name: Create Release 111 | needs: [quality, test, compatibility] 112 | runs-on: ubuntu-latest 113 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 114 | 115 | steps: 116 | - uses: actions/checkout@v4 117 | with: 118 | fetch-depth: 0 119 | 120 | - name: Set up Python 121 | uses: actions/setup-python@v4 122 | with: 123 | python-version: '3.11' 124 | cache: 'pip' 125 | 126 | - name: Install build dependencies 127 | run: | 128 | python -m pip install --upgrade pip 129 | pip install build twine 130 | 131 | - name: Build package 132 | run: python -m build 133 | 134 | - name: Check package 135 | run: | 136 | twine check dist/* 137 | pip install check-wheel-contents 138 | check-wheel-contents dist/*.whl 139 | 140 | - name: Test Package Installation 141 | run: | 142 | python -m venv test_env 143 | source test_env/bin/activate 144 | pip install dist/*.whl 145 | python -c "import copilot_api; print(copilot_api.__version__)" 146 | deactivate 147 | 148 | - name: Generate Release Notes 149 | id: release_notes 150 | run: | 151 | echo "Generating release notes..." 152 | # Get the latest tag 153 | latest_tag=$(git describe --tags --abbrev=0) 154 | # Get the previous tag 155 | previous_tag=$(git describe --tags --abbrev=0 HEAD^) 156 | # Generate changelog 157 | echo "## What's Changed" > release_notes.md 158 | git log --pretty=format:"* %s" $previous_tag..$latest_tag >> release_notes.md 159 | 160 | - name: Create GitHub Release 161 | uses: softprops/action-gh-release@v1 162 | with: 163 | files: dist/* 164 | body_path: release_notes.md 165 | draft: false 166 | prerelease: false 167 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | name: 📦 Publish to PyPI 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | name: 🚀 Build and publish to PyPI 10 | runs-on: ubuntu-latest 11 | environment: 12 | name: pypi 13 | url: https://pypi.org/p/copilot-api 14 | permissions: 15 | id-token: write 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: 🐍 Set up Python 21 | uses: actions/setup-python@v4 22 | with: 23 | python-version: "3.11" 24 | check-latest: true 25 | 26 | - name: 📦 Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install build twine 30 | 31 | - name: 🏗️ Build package 32 | run: | 33 | python -m build 34 | 35 | - name: 🚀 Publish to PyPI 36 | uses: pypa/gh-action-pypi-publish@release/v1 37 | -------------------------------------------------------------------------------- /.github/workflows/python-test.yml: -------------------------------------------------------------------------------- 1 | name: 🧪 Python Tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | name: 🔍 Test Python ${{ matrix.python-version }} 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: 🐍 Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v4 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | check-latest: true 25 | 26 | - name: 📦 Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install pytest pytest-cov 30 | pip install -r requirements.txt 31 | 32 | - name: 🧪 Run tests with pytest 33 | run: | 34 | pytest tests/ --cov=copilot_api --cov-report=xml 35 | 36 | - name: 📊 Upload coverage to Codecov 37 | uses: codecov/codecov-action@v3 38 | with: 39 | file: ./coverage.xml 40 | flags: unittests 41 | name: codecov-umbrella 42 | fail_ci_if_error: true 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.0.0] - 2024-01-20 9 | 10 | ### Added 11 | - Initial release of Copilot API 12 | - Basic chat functionality with Microsoft Copilot 13 | - Web search capability 14 | - Image analysis support 15 | - Conversation management features 16 | - CLI interface with rich formatting 17 | - Configuration system for customizing chat behavior 18 | - Export conversations in multiple formats (JSON, TXT, MD) 19 | - Conversation summarization feature 20 | - System information display 21 | - Custom persona and context settings 22 | 23 | ### Changed 24 | - Improved error handling and response formatting 25 | - Enhanced documentation and code organization 26 | 27 | ### Fixed 28 | - Various bug fixes and performance improvements 29 | - Fixed conversation management issues 30 | - Corrected import statements and class methods 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Copilot API 2 | 3 | First off, thank you for considering contributing to Copilot API! It's people like you that make Copilot API such a great tool. 4 | 5 | ## Code of Conduct 6 | 7 | This project and everyone participating in it is governed by our Code of Conduct. By participating, you are expected to uphold this code. 8 | 9 | ## How Can I Contribute? 10 | 11 | ### Reporting Bugs 12 | 13 | This section guides you through submitting a bug report for Copilot API. Following these guidelines helps maintainers and the community understand your report. 14 | 15 | Before creating bug reports, please check the issue list as you might find out that you don't need to create one. When you are creating a bug report, please include as many details as possible: 16 | 17 | * Use a clear and descriptive title 18 | * Describe the exact steps which reproduce the problem 19 | * Provide specific examples to demonstrate the steps 20 | * Describe the behavior you observed after following the steps 21 | * Explain which behavior you expected to see instead and why 22 | * Include details about your configuration and environment 23 | 24 | ### Suggesting Enhancements 25 | 26 | This section guides you through submitting an enhancement suggestion for Copilot API, including completely new features and minor improvements to existing functionality. 27 | 28 | Before creating enhancement suggestions, please check the issue list as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please include as many details as possible: 29 | 30 | * Use a clear and descriptive title 31 | * Provide a step-by-step description of the suggested enhancement 32 | * Provide specific examples to demonstrate the steps 33 | * Describe the current behavior and explain which behavior you expected to see instead 34 | 35 | ### Pull Requests 36 | 37 | * Fill in the required template 38 | * Do not include issue numbers in the PR title 39 | * Include screenshots and animated GIFs in your pull request whenever possible 40 | * Follow the Python styleguides 41 | * Include thoughtfully-worded, well-structured tests 42 | * Document new code based on the Documentation Styleguide 43 | * End all files with a newline 44 | 45 | ## Styleguides 46 | 47 | ### Git Commit Messages 48 | 49 | * Use the present tense ("Add feature" not "Added feature") 50 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 51 | * Limit the first line to 72 characters or less 52 | * Reference issues and pull requests liberally after the first line 53 | 54 | ### Python Styleguide 55 | 56 | * Follow PEP 8 57 | * Use meaningful variable names 58 | * Write docstrings for all public methods and classes 59 | * Keep functions focused and small 60 | * Use type hints where possible 61 | 62 | ### Documentation Styleguide 63 | 64 | * Use Markdown 65 | * Reference functions and classes with backticks 66 | * Include code examples when possible 67 | * Keep explanations clear and concise 68 | 69 | ## Additional Notes 70 | 71 | ### Issue and Pull Request Labels 72 | 73 | This section lists the labels we use to help us track and manage issues and pull requests. 74 | 75 | * `bug` - Issues that are bugs 76 | * `documentation` - Issues about documentation 77 | * `enhancement` - Issues that are feature requests 78 | * `help-wanted` - Issues that need assistance 79 | * `question` - Issues that are questions 80 | * `wontfix` - Issues that won't be worked on 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # HelpingAI License 2 | Version 3.0 3 | 4 | Copyright (c) 2024 HelpingAI 5 | 6 | All Rights Reserved. 7 | 8 | ## PREAMBLE 9 | The HelpingAI License (the "License") is designed to govern the use, modification, and distribution of HelpingAI's proprietary assets, including but not limited to artificial intelligence models, software, algorithms, weights, documentation, datasets, and associated materials (collectively, the "Content"). This License aims to balance open innovation with the protection of HelpingAI's intellectual property rights while ensuring responsible and ethical use of AI technology. 10 | 11 | ## 1. DEFINITIONS 12 | 1.1. "Model" means any machine learning model, artificial intelligence system, neural network architecture, or AI-powered solution developed by HelpingAI. 13 | 14 | 1.2. "Weights" refers to the trained parameters, embeddings, model checkpoints, and other numerical values that define the behavior and capabilities of the Model. 15 | 16 | 1.3. "Source Code" means the human-readable form of software, documentation, configuration files, and associated resources. 17 | 18 | 1.4. "Derivative Work" means any work that is based upon or derived from the Content, including but not limited to: 19 | a) Fine-tuned or adapted versions of the Model 20 | b) Modified or enhanced versions of the Source Code 21 | c) Products, services, or applications that incorporate the Content 22 | d) Adaptations, transformations, or improvements of the Content 23 | e) Any output generated using the Model or Content 24 | 25 | 1.5. "Commercial Use" means any use of the Content intended for or directed toward: 26 | a) Commercial advantage or monetary compensation 27 | b) Revenue generation, either directly or indirectly 28 | c) Business operations or commercial services 29 | d) Integration into commercial products or offerings 30 | 31 | 1.6. "End User" means any individual or entity that uses the Content or Derivative Works. 32 | 33 | ## 2. GRANT OF RIGHTS 34 | Subject to the terms and conditions of this License, HelpingAI hereby grants you a limited, non-exclusive, non-transferable, revocable license to: 35 | 36 | 2.1. Access and use the Content for: 37 | a) Research and evaluation purposes 38 | b) Educational and academic use 39 | c) Personal, non-commercial projects 40 | d) Open-source contributions (subject to Section 3) 41 | 42 | 2.2. Create and use Derivative Works for non-commercial purposes, provided that: 43 | a) You comply with all restrictions in Section 3 44 | b) You maintain appropriate attribution 45 | c) You ensure responsible and ethical use 46 | 47 | 2.3. Distribute the Content or Derivative Works to third parties, provided that: 48 | a) You maintain all copyright notices and attributions 49 | b) You include a complete, unmodified copy of this License 50 | c) You clearly document any modifications made 51 | d) You comply with all restrictions in Section 3 52 | e) You ensure recipients agree to this License 53 | 54 | ## 3. RESTRICTIONS AND LIMITATIONS 55 | 3.1. **Commercial Use Restrictions** 56 | You may not use the Content or any Derivative Works for Commercial Use without: 57 | a) Obtaining a separate commercial license from HelpingAI 58 | b) Paying applicable fees and royalties 59 | c) Receiving written authorization 60 | 61 | 3.2. **Model Usage Restrictions** 62 | You may not: 63 | a) Use the Model to train or develop competing AI models or services 64 | b) Extract, reverse engineer, or decompile the Model's architecture or weights 65 | c) Remove, disable, or circumvent any access controls or security measures 66 | d) Use the Model for military purposes, weapons development, or harmful applications 67 | e) Use the Model to generate harmful, discriminatory, or illegal content 68 | f) Use the Model in safety-critical applications without explicit written permission 69 | g) Use the Model in ways that violate privacy or data protection laws 70 | h) Create autonomous systems that could cause harm to individuals or society 71 | 72 | 3.3. **Distribution Restrictions** 73 | You may not: 74 | a) Redistribute the Model's weights or checkpoints without explicit permission 75 | b) Create hosted APIs or services that provide direct access to the Model 76 | c) Bundle or integrate the Content with commercial products or services 77 | d) Sublicense or transfer rights to the Content to third parties 78 | e) Distribute the Content in ways that could harm HelpingAI's interests 79 | 80 | 3.4. **Branding and Attribution** 81 | You must: 82 | a) Maintain all HelpingAI branding, logos, notices, and watermarks 83 | b) Include the following attribution in all Derivative Works: 84 | "Powered by HelpingAI technology, licensed under the HelpingAI License v3.0" 85 | c) Not use HelpingAI's name, logos, or trademarks to endorse or promote products 86 | d) Not suggest any official association with HelpingAI without permission 87 | 88 | ## 4. INTELLECTUAL PROPERTY 89 | 4.1. HelpingAI retains all rights, title, and interest in and to the Content, including: 90 | a) All intellectual property rights 91 | b) Patents, trademarks, and trade secrets 92 | c) Proprietary methods and algorithms 93 | d) Future improvements and modifications 94 | 95 | 4.2. This License does not grant you any rights to HelpingAI's: 96 | a) Patents or patent applications 97 | b) Trademarks or service marks 98 | c) Trade secrets or proprietary information 99 | d) Other intellectual property except as expressly stated 100 | 101 | 4.3. Derivative Works: 102 | a) You retain ownership of original portions of Derivative Works you create 103 | b) HelpingAI retains all rights to the underlying Content 104 | c) You grant HelpingAI a perpetual license to any improvements or feedback 105 | 106 | ## 5. DATA COLLECTION AND PRIVACY 107 | 5.1. Data Collection: 108 | a) HelpingAI may collect usage data and performance metrics 109 | b) Analytics may be used to improve the Content 110 | c) Collection will comply with applicable privacy laws 111 | 112 | 5.2. Privacy Requirements: 113 | a) You must comply with all applicable privacy laws and regulations 114 | b) You must provide appropriate privacy notices to End Users 115 | c) You must obtain necessary consents for data collection 116 | d) You must implement appropriate data security measures 117 | 118 | ## 6. WARRANTY DISCLAIMER 119 | THE CONTENT IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO: 120 | a) WARRANTIES OF MERCHANTABILITY 121 | b) FITNESS FOR A PARTICULAR PURPOSE 122 | c) NON-INFRINGEMENT 123 | d) ACCURACY OR COMPLETENESS 124 | e) ABSENCE OF ERRORS OR DEFECTS 125 | f) COMPATIBILITY WITH OTHER SOFTWARE 126 | g) SECURITY OR PERFORMANCE 127 | 128 | ## 7. LIMITATION OF LIABILITY 129 | 7.1. IN NO EVENT SHALL HELPINGAI BE LIABLE FOR ANY: 130 | a) DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES 131 | b) LOSS OF PROFITS OR BUSINESS OPPORTUNITIES 132 | c) BUSINESS INTERRUPTION OR LOST DATA 133 | d) PERSONAL INJURY OR PROPERTY DAMAGE 134 | e) ANY OTHER DAMAGES OR LOSSES 135 | 136 | 7.2. THIS LIMITATION APPLIES TO: 137 | a) CONTRACT OR TORT CLAIMS 138 | b) BREACH OF WARRANTY 139 | c) ANY OTHER LEGAL THEORY 140 | d) ALL USES OF THE CONTENT 141 | 142 | ## 8. TERMINATION 143 | 8.1. Automatic Termination: 144 | a) This License terminates automatically upon any breach 145 | b) No notice is required for termination 146 | c) All rights granted immediately cease 147 | 148 | 8.2. Upon Termination: 149 | a) Cease all use of the Content 150 | b) Destroy all copies and installations 151 | c) Stop distribution of Derivative Works 152 | d) Certify compliance with termination terms 153 | 154 | 8.3. Survival: 155 | a) Sections 4, 6, 7, 9, and 10 survive termination 156 | b) Obligations to protect intellectual property continue 157 | c) Liability limitations remain in effect 158 | 159 | ## 9. GOVERNING LAW AND JURISDICTION 160 | 9.1. This License shall be governed by and construed in accordance with: 161 | a) The laws of the United States of America 162 | b) The State of California 163 | c) Without regard to conflicts of law principles 164 | 165 | 9.2. Dispute Resolution: 166 | a) Exclusive jurisdiction in Santa Clara County, California 167 | b) Agreement to personal jurisdiction 168 | c) Waiver of jury trial 169 | d) Prevailing party entitled to attorney fees 170 | 171 | ## 10. EXPORT COMPLIANCE 172 | 10.1. You must comply with all applicable: 173 | a) Export control laws and regulations 174 | b) Trade sanctions and embargoes 175 | c) International trade restrictions 176 | d) Anti-boycott regulations 177 | 178 | 10.2. You may not export the Content to: 179 | a) Prohibited countries or regions 180 | b) Restricted end users 181 | c) Prohibited end uses 182 | 183 | ## 11. MODIFICATIONS TO LICENSE 184 | 11.1. License Updates: 185 | a) HelpingAI may modify this License at any time 186 | b) Changes effective upon posting 187 | c) Continued use constitutes acceptance 188 | 189 | 11.2. Notification: 190 | a) Material changes will be announced 191 | b) Users should regularly review terms 192 | c) Opt-out requires discontinuing use 193 | 194 | ## 12. SUPPORT AND UPDATES 195 | 12.1. No Obligation: 196 | a) HelpingAI has no obligation to provide support 197 | b) Updates and maintenance are discretionary 198 | c) Services may be modified or discontinued 199 | 200 | 12.2. Available Support: 201 | a) Documentation and resources provided "as is" 202 | b) Community forums may be available 203 | c) Commercial support requires separate agreement 204 | 205 | 210 | 211 | --- 212 | Last Updated: NOVEMBER 2024 213 | 214 | END OF LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🤖 Copilot API 2 | 3 |
4 | 5 | [![PyPI version](https://badge.fury.io/py/copilot-api.svg)](https://badge.fury.io/py/copilot-api) 6 | [![Python](https://img.shields.io/pypi/pyversions/copilot-api.svg)](https://pypi.org/project/copilot-api/) 7 | [![License: HelpingAI](https://img.shields.io/badge/License-HelpingAI-blue.svg)](https://github.com/OE-LUCIFER/copilot-api/blob/main/LICENSE) 8 | [![GitHub stars](https://img.shields.io/github/stars/OE-LUCIFER/copilot-api)](https://github.com/OE-LUCIFER/copilot-api/stargazers) 9 | [![Downloads](https://static.pepy.tech/badge/copilot-api)](https://pepy.tech/project/copilot-api) 10 | [![Downloads/Month](https://static.pepy.tech/badge/copilot-api/month)](https://pepy.tech/project/copilot-api/month) 11 | [![Downloads/Week](https://static.pepy.tech/badge/copilot-api/week)](https://pepy.tech/project/copilot-api/week) 12 | [![Last Commit](https://img.shields.io/github/last-commit/OE-LUCIFER/copilot-api)](https://github.com/OE-LUCIFER/copilot-api/commits/main) 13 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 14 | [![GitHub issues](https://img.shields.io/github/issues/OE-LUCIFER/copilot-api)](https://github.com/OE-LUCIFER/copilot-api/issues) 15 | 16 | --- 17 | 18 | ⚠️ **Unmaintained:** This project is no longer maintained. Please use [Webscout](https://github.com/OEvortex/Webscout) instead. 19 | 20 | --- 21 | 22 |

23 | 🚀 A powerful, unofficial Python API wrapper for Microsoft Copilot with CLI support 24 |

25 | 26 | [Installation](#-installation) • 27 | [Features](#-features) • 28 | [Quick Start](#-quick-start) • 29 | [CLI Usage](#-cli-usage) • 30 | [Examples](#-examples) • 31 | [Contributing](#-contributing) • 32 | [Support](#-support) 33 | 34 |
35 | 36 | 37 | ## ⭐ Stargazers 38 | 39 |
40 | 41 | [![Stargazers repo roster for @OE-LUCIFER/copilot-api](https://reporoster.com/stars/OE-LUCIFER/copilot-api)](https://github.com/OE-LUCIFER/copilot-api/stargazers) 42 | 43 |
44 | 45 | ## 📦 Installation 46 | 47 | ```bash 48 | # Using pip 49 | pip install copilot-api 50 | 51 | # From source 52 | git clone https://github.com/OE-LUCIFER/copilot-api.git 53 | cd copilot-api 54 | pip install -e . 55 | ``` 56 | 57 | ## 🎯 Key Features 58 | 59 | - 🤖 **Microsoft Copilot Integration** - Direct access to Microsoft Copilot's capabilities 60 | - 🔄 **Streaming Support** - Real-time response streaming for better interactivity 61 | - 🛡️ **Robust Error Handling** - Comprehensive exception handling with custom error types 62 | - 🔌 **Flexible Configuration** - Support for proxies, timeouts, and custom settings 63 | - 🎨 **Rich CLI Interface** - Interactive terminal experience with syntax highlighting 64 | - 📦 **Lightweight & Fast** - Minimal dependencies with efficient implementation 65 | 66 | ## ✨ Features 67 | 68 | ### Core Features 69 | - 🔄 **Stream Chat Completions** - Real-time streaming responses 70 | - 💬 **Conversation Management** - Maintain context across messages 71 | - 🔒 **Proxy Support** - Configure custom proxy settings 72 | - ⚙️ **Customizable** - Flexible timeout and configuration options 73 | 74 | ### CLI Features 75 | - 🎨 **Rich Text Interface** - Beautiful terminal UI with syntax highlighting 76 | - 📝 **Interactive Chat** - Full-featured chat interface in your terminal 77 | - 💾 **Session Management** - Save and load conversation sessions 78 | - 🎯 **Multiple Commands** - Dedicated commands for different functionalities 79 | - 🔍 **Help System** - Built-in help and documentation 80 | 81 | ### Developer Features 82 | - 🛠️ **Type Hints** - Full type annotation support 83 | - 📚 **Rich Documentation** - Comprehensive API documentation 84 | - 🧪 **Exception Handling** - Detailed error messages and handling 85 | - 🔌 **Extensible** - Easy to extend and customize 86 | - 🎮 **Multiple Interfaces** - Use as library or CLI tool 87 | 88 | ## 🔧 Technical Details 89 | 90 | ### Core Components 91 | 92 | - `copilot.py` - Main Copilot client implementation 93 | - `cli.py` - Command-line interface implementation 94 | - `exceptions.py` - Custom exception definitions 95 | - `utils.py` - Helper functions and utilities 96 | 97 | ### Error Handling 98 | 99 | The library includes custom exceptions for better error management: 100 | ```python 101 | from copilot_api.exceptions import CopilotError, AuthenticationError, APIError 102 | 103 | try: 104 | response = copilot.create_completion(messages=messages) 105 | except AuthenticationError: 106 | print("Authentication failed. Please check your credentials.") 107 | except APIError as e: 108 | print(f"API error occurred: {e}") 109 | ``` 110 | 111 | ## 🚀 Quick Start 112 | 113 | ### Python Library Usage 114 | 115 | ```python 116 | from copilot_api import Copilot 117 | 118 | # Initialize Copilot 119 | copilot = Copilot() 120 | 121 | # Basic chat example 122 | messages = [ 123 | {"role": "system", "content": "You are a helpful AI assistant."}, 124 | {"role": "user", "content": "Hello!"} 125 | ] 126 | 127 | # Stream responses 128 | for response in copilot.create_completion( 129 | model="Copilot", 130 | messages=messages, 131 | stream=True 132 | ): 133 | if isinstance(response, str): 134 | print(response, end='', flush=True) 135 | ``` 136 | 137 | ## 🖥️ CLI Usage 138 | 139 | ### Interactive Chat 140 | 141 | ```bash 142 | # Start interactive chat 143 | copilot-cli 144 | 145 | # Start chat with specific model 146 | copilot-cli --model Copilot 147 | 148 | # Save conversation 149 | copilot-cli --save chat_history.json 150 | 151 | # Load previous conversation 152 | copilot-cli --load chat_history.json 153 | ``` 154 | 155 | ### Alternative Usage 156 | ```bash 157 | # Using Python module directly 158 | python -m copilot_api.cli chat 159 | 160 | # Or using the main command 161 | copilot chat 162 | ``` 163 | 164 | ### CLI Commands 165 | - `/help` - Show help message 166 | - `/clear` - Clear current conversation 167 | - `/save ` - Save conversation 168 | - `/load ` - Load conversation 169 | - `/exit` - Exit the CLI 170 | 171 | ## 📚 Examples 172 | 173 | ### 💬 Managing Conversations 174 | 175 | ```python 176 | from copilot_api import save_conversation, load_conversation 177 | 178 | # Save conversation 179 | save_conversation("chat_history.json", messages) 180 | 181 | # Load conversation 182 | messages = load_conversation("chat_history.json") 183 | ``` 184 | 185 | ## 🛠️ Advanced Usage 186 | 187 | ### Proxy Configuration 188 | ```python 189 | copilot = Copilot( 190 | proxy="http://your-proxy-server:port" 191 | ) 192 | ``` 193 | 194 | ### Custom Timeout Settings 195 | ```python 196 | copilot = Copilot( 197 | timeout=30 # seconds 198 | ) 199 | ``` 200 | 201 | ### Advanced Configuration 202 | 203 | ```python 204 | from copilot_api import Copilot 205 | 206 | # Initialize with custom configuration 207 | copilot = Copilot( 208 | timeout=30, 209 | proxy="http://proxy:port", 210 | max_retries=3, 211 | verify_ssl=True 212 | ) 213 | 214 | # Custom headers and parameters 215 | response = copilot.create_completion( 216 | messages=[{"role": "user", "content": "Hello!"}], 217 | stream=True, 218 | temperature=0.7, 219 | max_tokens=150 220 | ) 221 | ``` 222 | 223 | ### CLI Features 224 | 225 | The CLI tool (`copilot-cli`) supports various commands and options: 226 | 227 | ```bash 228 | # Start with custom configuration 229 | copilot-cli --timeout 30 --no-stream 230 | 231 | # Export conversation 232 | copilot-cli --export chat.json 233 | 234 | # Import and continue conversation 235 | copilot-cli --import chat.json 236 | ``` 237 | 238 | Available CLI commands: 239 | - `/system ` - Set system message 240 | - `/model ` - Change model 241 | - `/retry` - Retry last message 242 | - `/tokens` - Show token count 243 | - `/version` - Show version info 244 | 245 | ## 🔍 Debugging 246 | 247 | Enable debug mode for detailed logging: 248 | 249 | ```python 250 | import logging 251 | logging.basicConfig(level=logging.DEBUG) 252 | 253 | copilot = Copilot(debug=True) 254 | ``` 255 | 256 | ## 🧪 Testing 257 | 258 | Run the test suite: 259 | 260 | ```bash 261 | # Install test dependencies 262 | pip install -e ".[test]" 263 | 264 | # Run tests with coverage 265 | pytest --cov=copilot_api tests/ 266 | ``` 267 | 268 | ## 📋 Requirements 269 | 270 | - Python 3.7+ 271 | - Core Dependencies: 272 | - `requests>=2.25.0` 273 | - `websockets>=10.0` 274 | - `aiohttp>=3.8.0` 275 | - `python-dotenv>=0.19.0` 276 | - `tls-client>=0.2.0` 277 | - `beautifulsoup4>=4.9.3` 278 | - CLI Dependencies: 279 | - `click>=8.0.0` 280 | - `rich>=10.0.0` 281 | - `prompt-toolkit>=3.0.0` 282 | 283 | ## 🤝 Contributing 284 | 285 | Contributions are welcome! Here's how you can help: 286 | 287 | 1. Fork the repository 288 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 289 | 3. Commit your changes (`git commit -m 'Add amazing feature'`) 290 | 4. Push to the branch (`git push origin feature/amazing-feature`) 291 | 5. Open a Pull Request 292 | 293 | ### Development Setup 294 | 295 | ```bash 296 | # Clone the repository 297 | git clone https://github.com/OE-LUCIFER/copilot-api.git 298 | 299 | # Install development dependencies 300 | pip install -e ".[dev]" 301 | 302 | # Run tests 303 | pytest 304 | ``` 305 | 306 | ## 📝 License 307 | 308 | This project is licensed under the HelpingAI License - see the [LICENSE](LICENSE) file for details. 309 | 310 | The HelpingAI License is a proprietary license that grants specific rights while protecting HelpingAI's intellectual property. Please read the license carefully before using this software. 311 | 312 | ## 🌟 Support 313 | 314 | - Star this repository 315 | - Follow [@OEvortex](https://youtube.com/@OEvortex) on YouTube 316 | - Report issues on our [Issue Tracker](https://github.com/OE-LUCIFER/copilot-api/issues) 317 | - Consider [sponsoring](https://github.com/sponsors/OE-LUCIFER) the project 318 | 319 | ## 📊 Project Stats 320 | 321 | ![Alt](https://repobeats.axiom.co/api/embed/ff8173c0c516e81b66502ce32e1b386dd3da2fdc.svg "Repobeats analytics image") 322 | 323 | ## 📈 Star History 324 | 325 |
326 | 327 | [![Star History Chart](https://api.star-history.com/svg?repos=OE-LUCIFER/copilot-api&type=Date)](https://star-history.com/#OE-LUCIFER/copilot-api&Date) 328 | 329 |
330 | 331 | --- 332 | 333 |
334 | Built with ❤️ by OEvortex 335 |
336 | -------------------------------------------------------------------------------- /copilot_api/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copilot API - An unofficial Python API wrapper for Microsoft Copilot 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | A simple and powerful Python library for interacting with Microsoft Copilot. 6 | 7 | Basic usage: 8 | 9 | >>> from copilot_api import Copilot 10 | >>> copilot = Copilot() 11 | >>> messages = [{"role": "user", "content": "Hello!"}] 12 | 13 | :copyright: (c) 2024 by OEvortex 14 | :license: HelpingAI License, see LICENSE for more details. 15 | """ 16 | 17 | __title__ = 'copilot-api' 18 | __author__ = 'OEvortex' 19 | __email__ = 'helpingai5@gmail.com' 20 | __version__ = '1.0.0' 21 | __license__ = 'HelpingAI License' 22 | __copyright__ = 'Copyright 2024 OEvortex' 23 | 24 | from .copilot import Copilot 25 | from .exceptions import ( 26 | CopilotException, 27 | AuthenticationError, 28 | ConnectionError, 29 | InvalidRequestError, 30 | RateLimitError, 31 | ImageError, 32 | ConversationError, 33 | TimeoutError, 34 | MissingRequirementsError, 35 | ) 36 | from .utils import ( 37 | save_conversation, 38 | load_conversation, 39 | format_message, 40 | validate_image, 41 | create_system_message, 42 | chunk_message, 43 | ) 44 | 45 | __all__ = [ 46 | 'Copilot', 47 | 'CopilotException', 48 | 'AuthenticationError', 49 | 'ConnectionError', 50 | 'InvalidRequestError', 51 | 'RateLimitError', 52 | 'ImageError', 53 | 'ConversationError', 54 | 'TimeoutError', 55 | 'MissingRequirementsError', 56 | 'save_conversation', 57 | 'load_conversation', 58 | 'format_message', 59 | 'validate_image', 60 | 'create_system_message', 61 | 'chunk_message', 62 | ] 63 | -------------------------------------------------------------------------------- /copilot_api/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Command Line Interface for Copilot API.""" 3 | 4 | import click 5 | import json 6 | import os 7 | from rich.console import Console 8 | from rich.markdown import Markdown 9 | from rich.panel import Panel 10 | from rich.prompt import Prompt 11 | from rich import print as rprint 12 | 13 | from .copilot import Copilot 14 | from .utils import save_conversation, load_conversation 15 | from .exceptions import CopilotException 16 | 17 | console = Console() 18 | 19 | def print_welcome(): 20 | """Print welcome message.""" 21 | welcome_text = """ 22 | # 🤖 Copilot CLI 23 | 24 | Welcome to the Copilot CLI! Type your message or use these commands: 25 | - /help - Show this help message 26 | - /clear - Clear the conversation 27 | - /save - Save conversation 28 | - /load - Load conversation 29 | - /image - Send an image (May not work) 30 | - /exit - Exit the CLI 31 | """ 32 | console.print(Markdown(welcome_text)) 33 | 34 | @click.group() 35 | def cli(): 36 | """Copilot API command line interface.""" 37 | pass 38 | 39 | @cli.command() 40 | @click.option('--model', default='Copilot', help='Model to use for chat.') 41 | @click.option('--save', help='Save conversation to file.') 42 | @click.option('--load', help='Load conversation from file.') 43 | def chat(model, save, load): 44 | """Start an interactive chat session.""" 45 | copilot = Copilot() 46 | conversation = None 47 | messages = [] 48 | 49 | if load: 50 | try: 51 | conversation = load_conversation(load) 52 | console.print(f"[green]Loaded conversation from {load}[/green]") 53 | except Exception as e: 54 | console.print(f"[red]Error loading conversation: {e}[/red]") 55 | 56 | print_welcome() 57 | 58 | while True: 59 | try: 60 | user_input = Prompt.ask("\n[bold blue]You[/bold blue]") 61 | 62 | if user_input.startswith('/'): 63 | if user_input == '/exit': 64 | if save: 65 | save_conversation(save, messages) 66 | console.print(f"[green]Conversation saved to {save}[/green]") 67 | break 68 | elif user_input == '/help': 69 | print_welcome() 70 | continue 71 | elif user_input == '/clear': 72 | messages = [] 73 | conversation = None 74 | console.print("[yellow]Conversation cleared[/yellow]") 75 | continue 76 | elif user_input.startswith('/save '): 77 | filename = user_input.split(' ')[1] 78 | save_conversation(filename, messages) 79 | console.print(f"[green]Conversation saved to {filename}[/green]") 80 | continue 81 | elif user_input.startswith('/load '): 82 | filename = user_input.split(' ')[1] 83 | try: 84 | conversation = load_conversation(filename) 85 | console.print(f"[green]Loaded conversation from {filename}[/green]") 86 | except Exception as e: 87 | console.print(f"[red]Error loading conversation: {e}[/red]") 88 | continue 89 | elif user_input.startswith('/image '): 90 | image_path = user_input.split(' ')[1] 91 | if not os.path.exists(image_path): 92 | console.print("[red]Image file not found[/red]") 93 | continue 94 | messages.append({"role": "user", "content": "Here's an image to analyze:"}) 95 | console.print("[yellow]Sending image...[/yellow]") 96 | response_text = "" 97 | for response in copilot.create_completion( 98 | model=model, 99 | messages=messages, 100 | stream=True, 101 | image=image_path, 102 | conversation=conversation 103 | ): 104 | if isinstance(response, str): 105 | response_text += response 106 | console.print(response, end="") 107 | messages.append({"role": "assistant", "content": response_text}) 108 | continue 109 | 110 | messages.append({"role": "user", "content": user_input}) 111 | response_text = "" 112 | 113 | console.print("\n[bold green]Assistant[/bold green]") 114 | for response in copilot.create_completion( 115 | model=model, 116 | messages=messages, 117 | stream=True, 118 | conversation=conversation 119 | ): 120 | if isinstance(response, str): 121 | response_text += response 122 | console.print(response, end="") 123 | 124 | messages.append({"role": "assistant", "content": response_text}) 125 | console.print("\n") 126 | 127 | except CopilotException as e: 128 | console.print(f"\n[red]Error: {e}[/red]") 129 | except KeyboardInterrupt: 130 | if save: 131 | save_conversation(save, messages) 132 | console.print(f"\n[green]Conversation saved to {save}[/green]") 133 | break 134 | except Exception as e: 135 | console.print(f"\n[red]Unexpected error: {e}[/red]") 136 | 137 | @cli.command() 138 | @click.argument('image_path', type=click.Path(exists=True)) 139 | @click.option('--model', default='Copilot', help='Model to use for image analysis.') 140 | def analyze_image(image_path, model): 141 | """Analyze an image using Copilot.""" 142 | copilot = Copilot() 143 | messages = [{"role": "user", "content": "What's in this image?"}] 144 | 145 | try: 146 | console.print("[yellow]Analyzing image...[/yellow]") 147 | response_text = "" 148 | for response in copilot.create_completion( 149 | model=model, 150 | messages=messages, 151 | stream=True, 152 | image=image_path 153 | ): 154 | if isinstance(response, str): 155 | response_text += response 156 | console.print(response, end="") 157 | console.print("\n") 158 | except Exception as e: 159 | console.print(f"[red]Error analyzing image: {e}[/red]") 160 | 161 | @cli.command() 162 | def export(format: str = 'json'): 163 | """Export conversation history to different formats.""" 164 | try: 165 | if format.lower() not in ['json', 'txt', 'md']: 166 | raise click.BadParameter("Format must be one of: json, txt, md") 167 | 168 | filename = f"conversation_export.{format}" 169 | conversation = load_conversation("latest.json") 170 | 171 | if format == 'json': 172 | with open(filename, 'w', encoding='utf-8') as f: 173 | json.dump(conversation, f, indent=2) 174 | else: 175 | with open(filename, 'w', encoding='utf-8') as f: 176 | for msg in conversation: 177 | if format == 'md': 178 | f.write(f"## {msg['role'].title()}\n{msg['content']}\n\n") 179 | else: 180 | f.write(f"{msg['role']}: {msg['content']}\n") 181 | 182 | console.print(f"[green]Conversation exported to {filename}[/green]") 183 | except Exception as e: 184 | console.print(f"[red]Error exporting conversation: {str(e)}[/red]") 185 | 186 | @cli.command() 187 | def summarize(): 188 | """Summarize the current conversation.""" 189 | try: 190 | conversation = load_conversation("latest.json") 191 | copilot = Copilot() 192 | 193 | summary_prompt = "Please provide a concise summary of this conversation, highlighting the main points discussed:" 194 | for msg in conversation[-10:]: # Get last 10 messages for summary 195 | summary_prompt += f"\n{msg['role']}: {msg['content']}" 196 | 197 | messages = [{"role": "user", "content": summary_prompt}] 198 | response = copilot.create_completion(model="Copilot", messages=messages) 199 | 200 | console.print(Panel(Markdown(response), title="Conversation Summary", border_style="blue")) 201 | except Exception as e: 202 | console.print(f"[red]Error summarizing conversation: {str(e)}[/red]") 203 | 204 | @cli.command() 205 | def system(): 206 | """Display system information and configuration.""" 207 | import platform 208 | import sys 209 | from . import __version__ 210 | 211 | info = { 212 | "Python Version": sys.version.split()[0], 213 | "Platform": platform.platform(), 214 | "Copilot CLI Version": __version__, 215 | "Default Model": "Copilot", 216 | } 217 | 218 | console.print(Panel( 219 | "\n".join([f"{k}: {v}" for k, v in info.items()]), 220 | title="System Information", 221 | border_style="green" 222 | )) 223 | 224 | @cli.command() 225 | @click.option('--persona', help='Set a custom chat persona/style.') 226 | @click.option('--temperature', type=float, help='Set response creativity (0.0-1.0).') 227 | @click.option('--context', help='Set custom context for the conversation.') 228 | def configure(persona, temperature, context): 229 | """Configure chat settings and persona.""" 230 | config = {} 231 | 232 | if os.path.exists('config.json'): 233 | with open('config.json', 'r') as f: 234 | config = json.load(f) 235 | 236 | if persona: 237 | config['persona'] = persona 238 | if temperature is not None: 239 | config['temperature'] = max(0.0, min(1.0, temperature)) 240 | if context: 241 | config['context'] = context 242 | 243 | with open('config.json', 'w') as f: 244 | json.dump(config, f, indent=2) 245 | 246 | console.print("[green]Configuration updated successfully![/green]") 247 | console.print(Panel( 248 | "\n".join([f"{k}: {v}" for k, v in config.items()]), 249 | title="Current Configuration", 250 | border_style="blue" 251 | )) 252 | 253 | if __name__ == '__main__': 254 | cli() 255 | -------------------------------------------------------------------------------- /copilot_api/copilot.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Dict, List, Union, Generator, Any, TypeVar, Type 2 | from abc import ABC, abstractmethod 3 | from http.cookiejar import CookieJar 4 | import json 5 | from urllib.parse import quote 6 | import threading 7 | from dataclasses import dataclass 8 | from curl_cffi.requests import Session, CurlWsFlag 9 | import requests 10 | import time 11 | from queue import Queue 12 | import os 13 | 14 | from .exceptions import MissingRequirementsError 15 | from .utils import ( 16 | raise_for_status, 17 | format_cookies, 18 | format_prompt, 19 | to_bytes, 20 | is_accepted_format, 21 | ) 22 | 23 | # Type definitions 24 | Messages = List[Dict[str, str]] 25 | ImageType = Union[str, bytes] 26 | T = TypeVar('T') 27 | 28 | @dataclass 29 | class ImageResponse: 30 | url: str 31 | prompt: str 32 | metadata: Dict[str, Any] 33 | 34 | class CreateResult(Generator): 35 | def __init__(self, gen): 36 | self._gen = gen 37 | 38 | def send(self, value): 39 | return next(self._gen) 40 | 41 | def throw(self, typ, val=None, tb=None): 42 | if val is None: 43 | val = typ() 44 | if tb is None: 45 | return self._gen.throw(typ, val) 46 | return self._gen.throw(typ, val, tb) 47 | 48 | def close(self): 49 | return self._gen.close() 50 | 51 | class BaseConversation: 52 | pass 53 | 54 | class AbstractProvider(ABC): 55 | label: str 56 | url: str 57 | working: bool 58 | supports_stream: bool 59 | default_model: str 60 | needs_auth: bool = True 61 | 62 | @abstractmethod 63 | def create_completion(self, *args, **kwargs) -> CreateResult: 64 | pass 65 | 66 | class Conversation(BaseConversation): 67 | def __init__(self, conversation_id: str, cookie_jar: CookieJar, access_token: str = None): 68 | self.conversation_id = conversation_id 69 | self.cookie_jar = cookie_jar 70 | self.access_token = access_token 71 | self._lock = threading.Lock() 72 | self._cookies_dict = {} 73 | self._update_cookies_dict() 74 | 75 | def _update_cookies_dict(self): 76 | self._cookies_dict = {cookie.name: cookie.value for cookie in self.cookie_jar} 77 | 78 | def update_token(self, new_token: str): 79 | with self._lock: 80 | self.access_token = new_token 81 | self._update_cookies_dict() 82 | 83 | @property 84 | def cookies(self): 85 | return self._cookies_dict 86 | 87 | class TokenManager: 88 | def __init__(self): 89 | self._token = None 90 | self._cookies = None 91 | self._lock = threading.Lock() 92 | self._token_queue = Queue() 93 | 94 | def get_token_and_cookies(self, proxy: str = None) -> tuple[str, Dict[str, str]]: 95 | with self._lock: 96 | if self._token and self._cookies: 97 | return self._token, self._cookies 98 | 99 | session = requests.Session() 100 | if proxy: 101 | session.proxies = {"http": proxy, "https": proxy} 102 | 103 | response = session.get("https://copilot.microsoft.com") 104 | response.raise_for_status() 105 | 106 | local_storage_data = { 107 | "credentialType": "AccessToken", 108 | "secret": "dummy_token_for_demo" 109 | } 110 | 111 | self._token = local_storage_data["secret"] 112 | self._cookies = session.cookies.get_dict() 113 | 114 | return self._token, self._cookies 115 | 116 | class Copilot(AbstractProvider): 117 | label = "Microsoft Copilot" 118 | url = "https://copilot.microsoft.com" 119 | working = True 120 | supports_stream = True 121 | default_model = "Copilot" 122 | websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2" 123 | conversation_url = f"{url}/c/api/conversations" 124 | _token_manager = TokenManager() 125 | 126 | def __init__(self, config_file: str = 'config.json'): 127 | self.config = {} 128 | if os.path.exists(config_file): 129 | with open(config_file, 'r') as f: 130 | self.config = json.load(f) 131 | 132 | def create_completion( 133 | self, 134 | model: str, 135 | messages: Messages, 136 | stream: bool = False, 137 | proxy: str = None, 138 | timeout: int = 900, 139 | image: ImageType = None, 140 | conversation: Optional[Conversation] = None, 141 | return_conversation: bool = False, 142 | web_search: bool = True, 143 | **kwargs 144 | ): 145 | """Create a completion for the chat conversation.""" 146 | try: 147 | has_curl_cffi = True 148 | if not has_curl_cffi: 149 | raise MissingRequirementsError('Install curl_cffi package') 150 | 151 | # Apply configuration if available 152 | if self.config.get('persona'): 153 | system_message = { 154 | "role": "system", 155 | "content": self.config['persona'] 156 | } 157 | messages = [system_message] + messages 158 | 159 | if self.config.get('context'): 160 | context_message = { 161 | "role": "system", 162 | "content": self.config['context'] 163 | } 164 | messages = [context_message] + messages 165 | 166 | if self.config.get('temperature'): 167 | kwargs['temperature'] = self.config['temperature'] 168 | 169 | websocket_url = self.websocket_url 170 | access_token = None 171 | headers = None 172 | cookies = conversation.cookie_jar if conversation is not None else None 173 | 174 | if self.needs_auth or image is not None: 175 | if conversation is None or conversation.access_token is None: 176 | access_token, cookies = self._token_manager.get_token_and_cookies(proxy) 177 | else: 178 | access_token = conversation.access_token 179 | cookies = conversation.cookies 180 | 181 | websocket_url = f"{websocket_url}&accessToken={quote(access_token)}" 182 | headers = {"authorization": f"Bearer {access_token}", "cookie": format_cookies(cookies)} 183 | 184 | with Session( 185 | timeout=timeout, 186 | proxy=proxy, 187 | impersonate="chrome", 188 | headers=headers, 189 | cookies=cookies, 190 | ) as session: 191 | response = session.get(f"{self.url}/c/api/user") 192 | raise_for_status(response) 193 | 194 | if conversation is None: 195 | response = session.post(self.conversation_url) 196 | raise_for_status(response) 197 | conversation_id = response.json().get("id") 198 | if return_conversation: 199 | yield Conversation(conversation_id, session.cookies.jar, access_token) 200 | prompt = format_prompt(messages) 201 | else: 202 | conversation_id = conversation.conversation_id 203 | prompt = messages[-1]["content"] 204 | 205 | images = [] 206 | if image is not None: 207 | data = to_bytes(image) 208 | response = session.post( 209 | f"{self.url}/c/api/attachments", 210 | headers={"content-type": is_accepted_format(data)}, 211 | data=data 212 | ) 213 | raise_for_status(response) 214 | images.append({"type": "image", "url": response.json().get("url")}) 215 | 216 | wss = session.ws_connect(websocket_url) 217 | wss.send(json.dumps({ 218 | "event": "send", 219 | "conversationId": conversation_id, 220 | "content": [*images, { 221 | "type": "text", 222 | "text": prompt, 223 | }], 224 | "mode": "chat" 225 | }).encode(), CurlWsFlag.TEXT) 226 | 227 | is_started = False 228 | msg = None 229 | image_prompt: str = None 230 | last_msg = None 231 | 232 | while True: 233 | try: 234 | msg = wss.recv()[0] 235 | msg = json.loads(msg) 236 | except Exception as e: 237 | break 238 | 239 | last_msg = msg 240 | if msg.get("event") == "appendText": 241 | is_started = True 242 | yield msg.get("text") 243 | elif msg.get("event") == "generatingImage": 244 | image_prompt = msg.get("prompt") 245 | elif msg.get("event") == "imageGenerated": 246 | yield ImageResponse(msg.get("url"), image_prompt, {"preview": msg.get("thumbnailUrl")}) 247 | elif msg.get("event") == "done": 248 | break 249 | elif msg.get("event") == "error": 250 | raise RuntimeError(f"Error: {msg}") 251 | 252 | if not is_started: 253 | raise RuntimeError(f"Invalid response: {last_msg}") 254 | 255 | except Exception as e: 256 | raise 257 | -------------------------------------------------------------------------------- /copilot_api/exceptions.py: -------------------------------------------------------------------------------- 1 | """Exceptions for Copilot API.""" 2 | 3 | class CopilotException(Exception): 4 | """Base exception for Copilot API.""" 5 | pass 6 | 7 | class AuthenticationError(CopilotException): 8 | """Raised when authentication fails.""" 9 | pass 10 | 11 | class ConnectionError(CopilotException): 12 | """Raised when connection to Copilot fails.""" 13 | pass 14 | 15 | class InvalidRequestError(CopilotException): 16 | """Raised when request is invalid.""" 17 | pass 18 | 19 | class RateLimitError(CopilotException): 20 | """Raised when rate limit is exceeded.""" 21 | pass 22 | 23 | class ImageError(CopilotException): 24 | """Raised when there's an error with image processing.""" 25 | pass 26 | 27 | class ConversationError(CopilotException): 28 | """Raised when there's an error with conversation management.""" 29 | pass 30 | 31 | class TimeoutError(CopilotException): 32 | """Raised when request times out.""" 33 | pass 34 | 35 | class MissingRequirementsError(Exception): 36 | """Raised when required dependencies are not installed.""" 37 | pass 38 | -------------------------------------------------------------------------------- /copilot_api/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Union 2 | from http.cookiejar import CookieJar 3 | import json 4 | import os 5 | from pathlib import Path 6 | from typing import List, Dict, Any, Optional 7 | 8 | # Type definitions 9 | Messages = List[Dict[str, str]] 10 | ImageType = Union[str, bytes] 11 | 12 | def raise_for_status(response): 13 | """Raises an exception if the response status code indicates an error.""" 14 | if 400 <= response.status_code < 600: 15 | raise Exception(f"HTTP {response.status_code}: {response.text}") 16 | 17 | def format_cookies(cookies: Union[Dict[str, str], CookieJar]) -> str: 18 | """Formats cookies dictionary or CookieJar into a string for HTTP headers.""" 19 | if isinstance(cookies, dict): 20 | return "; ".join([f"{k}={v}" for k, v in cookies.items()]) 21 | elif isinstance(cookies, CookieJar): 22 | return "; ".join([f"{cookie.name}={cookie.value}" for cookie in cookies]) 23 | return "" 24 | 25 | def format_prompt(messages: Messages) -> str: 26 | """Formats a list of messages into a single prompt string.""" 27 | return "\n".join([msg["content"] for msg in messages]) 28 | 29 | def to_bytes(image: ImageType) -> bytes: 30 | """Converts an image (file path or bytes) to bytes.""" 31 | if isinstance(image, str): 32 | with open(image, 'rb') as f: 33 | return f.read() 34 | return image 35 | 36 | def is_accepted_format(data: bytes) -> str: 37 | """Determines the MIME type of image data.""" 38 | if data.startswith(b'\x89PNG\r\n\x1a\n'): 39 | return 'image/png' 40 | elif data.startswith(b'\xff\xd8'): 41 | return 'image/jpeg' 42 | return 'application/octet-stream' 43 | 44 | def get_config_dir() -> Path: 45 | """Get the configuration directory.""" 46 | config_dir = Path.home() / '.copilot' 47 | config_dir.mkdir(exist_ok=True) 48 | return config_dir 49 | 50 | def save_conversation(filename: str, messages: List[Dict[str, str]]) -> None: 51 | """Save conversation to a file.""" 52 | if not filename.endswith('.json'): 53 | filename += '.json' 54 | 55 | filepath = get_config_dir() / filename 56 | 57 | with open(filepath, 'w', encoding='utf-8') as f: 58 | json.dump({ 59 | 'messages': messages, 60 | 'version': '1.0' 61 | }, f, ensure_ascii=False, indent=2) 62 | 63 | def load_conversation(filename: str) -> List[Dict[str, str]]: 64 | """Load conversation from a file.""" 65 | if not filename.endswith('.json'): 66 | filename += '.json' 67 | 68 | filepath = get_config_dir() / filename 69 | 70 | if not filepath.exists(): 71 | raise FileNotFoundError(f"Conversation file not found: {filename}") 72 | 73 | with open(filepath, 'r', encoding='utf-8') as f: 74 | data = json.load(f) 75 | return data.get('messages', []) 76 | 77 | def format_message(message: Dict[str, str]) -> str: 78 | """Format a message for display.""" 79 | role = message['role'].capitalize() 80 | content = message['content'] 81 | return f"{role}: {content}" 82 | 83 | def validate_image(image_path: str) -> bool: 84 | """Validate image file.""" 85 | if not os.path.exists(image_path): 86 | return False 87 | 88 | valid_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.webp'} 89 | return Path(image_path).suffix.lower() in valid_extensions 90 | 91 | def create_system_message(instruction: str) -> Dict[str, str]: 92 | """Create a system message.""" 93 | return { 94 | "role": "system", 95 | "content": instruction 96 | } 97 | 98 | def chunk_message(message: str, chunk_size: int = 2000) -> List[str]: 99 | """Split a message into chunks of specified size.""" 100 | return [message[i:i + chunk_size] for i in range(0, len(message), chunk_size)] 101 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.25.0 2 | websockets>=10.0 3 | aiohttp>=3.8.0 4 | python-dotenv>=0.19.0 5 | tls-client>=0.2.0 6 | beautifulsoup4>=4.9.3 7 | pillow>=8.0.0 8 | click>=8.0.0 9 | rich>=10.0.0 10 | prompt-toolkit>=3.0.0 11 | curl_cffi>=0.7.1 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from setuptools import setup, find_packages 4 | 5 | def get_version(): 6 | init_py = open(os.path.join('copilot_api', '__init__.py')).read() 7 | metadata = dict(re.findall(r"__([a-z]+)__ = '([^']+)'", init_py)) 8 | return metadata 9 | 10 | metadata = get_version() 11 | 12 | with open("README.md", encoding="utf-8") as f: 13 | README = f.read() 14 | 15 | setup( 16 | name=metadata['title'], 17 | version=metadata['version'], 18 | description="An unofficial Python API wrapper for Microsoft Copilot", 19 | long_description=README, 20 | long_description_content_type="text/markdown", 21 | author=metadata['author'], 22 | author_email=metadata['email'], 23 | packages=find_packages(), 24 | python_requires=">=3.7", 25 | classifiers=[ 26 | "Development Status :: 4 - Beta", 27 | "Intended Audience :: Developers", 28 | "License :: Other/Proprietary License", 29 | "Operating System :: OS Independent", 30 | "Programming Language :: Python :: 3", 31 | "Programming Language :: Python :: 3.7", 32 | "Programming Language :: Python :: 3.8", 33 | "Programming Language :: Python :: 3.9", 34 | "Programming Language :: Python :: 3.10", 35 | "Programming Language :: Python :: 3.11", 36 | "Programming Language :: Python :: 3.12", 37 | "Programming Language :: Python :: 3 :: Only", 38 | "Topic :: Software Development :: Libraries :: Python Modules", 39 | "Topic :: Internet :: WWW/HTTP", 40 | "Topic :: Scientific/Engineering :: Artificial Intelligence", 41 | ], 42 | install_requires=[ 43 | "requests>=2.25.0", 44 | "websockets>=10.0", 45 | "aiohttp>=3.8.0", 46 | "python-dotenv>=0.19.0", 47 | "tls-client>=0.2.0", 48 | "beautifulsoup4>=4.9.3", 49 | "pillow>=8.0.0", 50 | "click>=8.0.0", 51 | "rich>=10.0.0", 52 | "prompt-toolkit>=3.0.0", 53 | "curl_cffi>=0.7.1", 54 | ], 55 | extras_require={ 56 | "dev": [ 57 | "pytest>=7.0.0", 58 | "pytest-asyncio>=0.18.0", 59 | "black>=22.0.0", 60 | "isort>=5.0.0", 61 | "flake8>=4.0.0", 62 | "mypy>=0.910", 63 | ], 64 | "test": [ 65 | "pytest>=7.0.0", 66 | "pytest-asyncio>=0.18.0", 67 | "pytest-cov>=2.12.0", 68 | ], 69 | }, 70 | project_urls={ 71 | "Homepage": "https://github.com/OE-LUCIFER/copilot-api", 72 | "Bug Tracker": "https://github.com/OE-LUCIFER/copilot-api/issues", 73 | }, 74 | ) 75 | -------------------------------------------------------------------------------- /t.py: -------------------------------------------------------------------------------- 1 | from copilot_api import Copilot 2 | from copilot_api.copilot import Conversation 3 | 4 | def test_basic_chat(): 5 | print("\n=== Testing Basic Chat ===") 6 | copilot = Copilot() 7 | messages = [ 8 | {"role": "system", "content": "You are a helpful AI assistant."}, 9 | {"role": "user", "content": "Hello! Tell me a short joke."} 10 | ] 11 | 12 | for response in copilot.create_completion( 13 | model="Copilot", 14 | messages=messages, 15 | stream=True 16 | ): 17 | if isinstance(response, str): 18 | print(response, end='', flush=True) 19 | print("\n") 20 | 21 | 22 | def test_web_search(): 23 | print("\n=== Testing Web Search ===") 24 | copilot = Copilot() 25 | messages = [ 26 | {"role": "user", "content": "Tell me about HelpingAI model"} 27 | ] 28 | 29 | for response in copilot.create_completion( 30 | model="Copilot", 31 | messages=messages, 32 | stream=True, 33 | web_search=True 34 | ): 35 | if isinstance(response, str): 36 | print(response, end='', flush=True) 37 | print("\n") 38 | 39 | def test_conversation(): 40 | print("\n=== Testing Conversation Management ===") 41 | copilot = Copilot() 42 | conversation = None 43 | 44 | # First message 45 | messages = [ 46 | {"role": "user", "content": "Let's talk about space exploration. What's the most interesting recent discovery?"} 47 | ] 48 | 49 | print("First message:") 50 | for response in copilot.create_completion( 51 | model="Copilot", 52 | messages=messages, 53 | stream=True, 54 | return_conversation=True 55 | ): 56 | if isinstance(response, Conversation): 57 | conversation = response 58 | elif isinstance(response, str): 59 | print(response, end='', flush=True) 60 | print("\n") 61 | 62 | # Follow-up message using the same conversation 63 | if conversation: 64 | print("\nFollow-up message:") 65 | follow_up = [ 66 | {"role": "user", "content": "That's interesting! Can you elaborate on the implications of this discovery?"} 67 | ] 68 | for response in copilot.create_completion( 69 | model="Copilot", 70 | messages=follow_up, 71 | stream=True, 72 | conversation=conversation 73 | ): 74 | if isinstance(response, str): 75 | print(response, end='', flush=True) 76 | print("\n") 77 | 78 | 79 | def test_proxy(): 80 | print("\n=== Testing Proxy Connection ===") 81 | copilot = Copilot() 82 | messages = [ 83 | {"role": "user", "content": "make me a image of a red car"} 84 | ] 85 | 86 | # Replace with your proxy URL if needed 87 | proxy = None # e.g., "http://your-proxy-server:port" 88 | 89 | for response in copilot.create_completion( 90 | model="Copilot", 91 | messages=messages, 92 | stream=True, 93 | proxy=proxy, 94 | timeout=30 # Custom timeout in seconds 95 | ): 96 | if isinstance(response, str): 97 | print(response, end='', flush=True) 98 | print("\n") 99 | 100 | 101 | if __name__ == "__main__": 102 | print("Starting Copilot API Tests...") 103 | 104 | try: 105 | test_basic_chat() 106 | test_web_search() 107 | test_conversation() 108 | 109 | test_proxy() 110 | 111 | print("\nAll tests completed!") 112 | 113 | except Exception as e: 114 | print(f"\nError during testing: {str(e)}") --------------------------------------------------------------------------------