├── .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 | [](https://badge.fury.io/py/copilot-api)
6 | [](https://pypi.org/project/copilot-api/)
7 | [](https://github.com/OE-LUCIFER/copilot-api/blob/main/LICENSE)
8 | [](https://github.com/OE-LUCIFER/copilot-api/stargazers)
9 | [](https://pepy.tech/project/copilot-api)
10 | [](https://pepy.tech/project/copilot-api/month)
11 | [](https://pepy.tech/project/copilot-api/week)
12 | [](https://github.com/OE-LUCIFER/copilot-api/commits/main)
13 | [](https://github.com/psf/black)
14 | [](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 | [](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 | 
322 |
323 | ## 📈 Star History
324 |
325 |
326 |
327 | [](https://star-history.com/#OE-LUCIFER/copilot-api&Date)
328 |
329 |
330 |
331 | ---
332 |
333 |
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)}")
--------------------------------------------------------------------------------