├── .github ├── FUNDING.yml └── workflows │ └── docker.yaml ├── .gitignore ├── .vscode └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── README_CN.md ├── README_JA.md ├── claude_to_chatgpt ├── __init__.py ├── adapter.py ├── app.py ├── logger.py ├── models.py └── util.py ├── cloudflare-worker.js ├── docker-compose.yaml ├── poetry.lock ├── pyproject.toml ├── tests └── __init__.py └── wrangler.toml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [jtsang4]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | push_to_registry: 10 | name: Push Docker image to Docker Hub 11 | runs-on: ubuntu-latest 12 | steps: 13 | - 14 | name: Check out the repo 15 | uses: actions/checkout@v3 16 | - 17 | name: Log in to Docker Hub 18 | uses: docker/login-action@v2 19 | with: 20 | username: ${{ secrets.DOCKER_USERNAME }} 21 | password: ${{ secrets.DOCKER_PASSWORD }} 22 | 23 | - 24 | name: Extract metadata (tags, labels) for Docker 25 | id: meta 26 | uses: docker/metadata-action@v4 27 | with: 28 | images: wtzeng/claude-to-chatgpt 29 | tags: | 30 | type=raw,value=latest 31 | type=ref,event=tag 32 | 33 | - 34 | name: Set up QEMU 35 | uses: docker/setup-qemu-action@v2 36 | 37 | - 38 | name: Set up Docker Buildx 39 | uses: docker/setup-buildx-action@v2 40 | 41 | - 42 | name: Build and push Docker image 43 | uses: docker/build-push-action@v4 44 | with: 45 | context: . 46 | platforms: linux/amd64,linux/arm64 47 | push: true 48 | tags: ${{ steps.meta.outputs.tags }} 49 | labels: ${{ steps.meta.outputs.labels }} 50 | cache-from: type=gha 51 | cache-to: type=gha,mode=max 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | __pycache__ 3 | node_modules 4 | .wrangler -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "editor.defaultFormatter": "charliermarsh.ruff" 4 | }, 5 | "editor.formatOnSave": true, 6 | "python.analysis.autoImportCompletions": true, 7 | "python.analysis.typeCheckingMode": "basic" 8 | } 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim-buster 2 | 3 | WORKDIR /app 4 | 5 | # Set the environment variables 6 | ENV CLAUDE_BASE_URL="https://api.anthropic.com" 7 | ENV LOG_LEVEL="info" 8 | 9 | # Copy the rest of the application code to the container 10 | COPY . . 11 | 12 | # Install Poetry 13 | RUN pip install poetry 14 | 15 | RUN poetry install --only main 16 | 17 | # Expose the port the app runs on 18 | EXPOSE 8000 19 | 20 | # Set the command to run the application 21 | CMD ["poetry", "run", "python", "claude_to_chatgpt/app.py"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-PRESENT James Tsang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | English | 简体中文 | 日本語 3 |

4 | 5 |
6 |

Claude to ChatGPT

7 |

8 | 9 | release 10 | 11 | 12 | GitHub Repo stars 13 | 14 | 15 | GitHub Repo Badge 16 | 17 | 18 | GitHub Repo Language 19 | 20 |

21 |
22 | 23 | This project converts the API of Anthropic's Claude model to the OpenAI Chat API format. 24 | 25 | * ✨ Call Claude API like OpenAI ChatGPT API 26 | * 💦 Support streaming response 27 | * 🐻 Support `claude-instant-1`, `claude-2` models 28 | * 🌩️ Deploy by Cloudflare Workers or Docker 29 | 30 | ## Getting Started 31 | 32 | You can run this project using Cloudflare Workers or Docker: 33 | 34 | ### Deployment 35 | 36 | #### Using Cloudflare Workers 37 | 38 | By using Cloudflare Workers, you don't need a server to deploy this project. 39 | 40 | 1. Create a Cloudflare Worker 41 | 2. Paste the code in [`cloudflare-worker.js`](https://github.com/jtsang4/claude-to-chatgpt/blob/main/cloudflare-worker.js) to Cloudflare Worker "Quick Edit" Editor 42 | 3. Save and deploy 43 | 4. (Optional) Set custom domain for your Cloudflare Worker 44 | 45 | The Cloudfalre Workers support 100k requests a day, If you need to call more than that, you can use Docker to deploy as below. 46 | 47 | #### Using Docker 48 | 49 | ```bash 50 | docker run -p 8000:8000 wtzeng/claude-to-chatgpt:latest 51 | ``` 52 | 53 | #### Using Docker Compose 54 | 55 | ```bash 56 | docker-compose up 57 | ``` 58 | 59 | 60 | The API will then be available at http://localhost:8000. API endpoint: `/v1/chat/completions` 61 | 62 | ### Usage 63 | 64 | When you input the model parameter as `gpt-3.5-turbo` or `gpt-3.5-turbo-0613`, it will be substituted with `claude-instant-1`. otherwise, `claude-2` will be utilized. 65 | 66 | 67 | #### GUI 68 | 69 | Here are some recommended GUI software that supports this project: 70 | 71 | * [Bin-Huang/chatbox](https://github.com/Bin-Huang/chatbox) 72 | * [Yidadaa/ChatGPT-Next-Web](https://github.com/Yidadaa/ChatGPT-Next-Web) 73 | 74 | #### CLI 75 | 76 | ```bash 77 | curl http://localhost:8000/v1/chat/completions \ 78 | -H "Content-Type: application/json" \ 79 | -H "Authorization: Bearer $CLAUDE_API_KEY" \ 80 | -d '{ 81 | "model": "gpt-3.5-turbo", 82 | "messages": [{"role": "user", "content": "Hello!"}] 83 | }' 84 | ``` 85 | 86 | ## Conversion Details 87 | 88 | The Claude Completion API has an endpoint `/v1/complete` which takes the following JSON request: 89 | 90 | ```json 91 | { 92 | "prompt": "\n\nHuman: Hello, AI.\n\nAssistant: ", 93 | "model": "claude-instant-1", 94 | "max_tokens_to_sample": 100, 95 | "temperature": 1, 96 | "stream": true 97 | } 98 | ``` 99 | 100 | 101 | And returns JSON with choices and completions. 102 | 103 | The OpenAI Chat API has a similar `/v1/chat/completions` endpoint which takes: 104 | 105 | ```json 106 | { 107 | "model": "gpt-3.5-turbo", 108 | "messages": [ 109 | { 110 | "role": "user", 111 | "content": "Hello, AI." 112 | } 113 | ], 114 | "max_tokens": 100, 115 | "temperature": 1, 116 | "stream": true 117 | } 118 | ``` 119 | 120 | 121 | And returns JSON with a response string. 122 | 123 | This project converts between these two APIs, get completions from the Claude model and formatting them as OpenAI Chat responses. 124 | 125 | ## License 126 | 127 | This project is licensed under the MIT License - see the LICENSE file for details. -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 |

2 | 简体中文 | English | 日本語 3 |

4 | 5 |
6 |

Claude to ChatGPT

7 |

8 | 9 | release 10 | 11 | 12 | GitHub Repo stars 13 | 14 | 15 | GitHub Repo Badge 16 | 17 | 18 | GitHub Repo Language 19 | 20 |

21 |
22 | 23 | 此项目将 Anthropic 的 Claude 模型的 API 转换为 OpenAI Chat API 格式。 24 | 25 | * ✨ 以 OpenAI ChatGPT API 的方式调用 Claude API 26 | * 💦 支持流式响应,实现打印机效果 27 | * 🐻 支持 `claude-instant-1`,`claude-2` 模型 28 | * 🌩️ 通过 Cloudflare Workers 或 Docker 部署 29 | 30 | ## 开始使用 31 | 32 | 您可以使用 Cloudflare Workers 或 Docker 进行部署: 33 | 34 | ### 部署 35 | 36 | #### 使用 Cloudflare Workers 37 | 38 | 通过使用 Cloudflare Workers,您无需服务器即可部署此项目。 39 | 40 | 1. 创建一个 Cloudflare Worker 41 | 2. 将 [`cloudflare-worker.js`](https://github.com/jtsang4/claude-to-chatgpt/blob/main/cloudflare-worker.js) 中的代码粘贴到 Cloudflare Worker 的“快速编辑”编辑器中 42 | 3. 保存并部署 43 | 4. (可选)为 Cloudflare Worker 设置自定义域名 44 | 45 | Cloudfalre Workers 支持每天 100k 请求,如果需要发起更多调用,请参照下面的方式使用 Docker 进行部署。 46 | 47 | #### 使用 Docker 48 | 49 | ```bash 50 | docker run -p 8000:8000 wtzeng/claude-to-chatgpt:latest 51 | ``` 52 | 53 | #### 使用 Docker Compose 54 | 55 | ```bash 56 | docker-compose up 57 | ``` 58 | 59 | 然后,API 将在 `http://localhost:8000` 上可用。API 路径:`/v1/chat/completions`。 60 | 61 | ### 使用方法 62 | 63 | 当您将模型参数 `model` 为 `gpt-3.5-turbo` 或 `gpt-3.5-turbo-0613` 时,它将替换为 `claude-instant-1`。否则,将使用 `claude-2`。 64 | 65 | 66 | #### 图形界面软件 67 | 68 | 以下是一些支持使用本项目的推荐 GUI 软件: 69 | 70 | * [Bin-Huang/chatbox](https://github.com/Bin-Huang/chatbox) 71 | * [Yidadaa/ChatGPT-Next-Web](https://github.com/Yidadaa/ChatGPT-Next-Web) 72 | 73 | #### 命令行调用 74 | 75 | ```bash 76 | curl http://localhost:8000/v1/chat/completions \ 77 | -H "Content-Type: application/json" \ 78 | -H "Authorization: Bearer $CLAUDE_API_KEY" \ 79 | -d '{ 80 | "model": "gpt-3.5-turbo", 81 | "messages": [{"role": "user", "content": "Hello!"}] 82 | }' 83 | ``` 84 | 85 | ## 转换细节 86 | 87 | Claude Completion API endpoint 为 `/v1/complete`,它接受以下请求格式并返回 Completion 接口的内容。: 88 | 89 | ```json 90 | { 91 | "prompt": "\n\nHuman: Hello, AI.\n\nAssistant: ", 92 | "model": "claude-instant-1", 93 | "max_tokens_to_sample": 100, 94 | "temperature": 1, 95 | "stream": true 96 | } 97 | ``` 98 | 99 | OpenAI Chat API 有一个相似的 `/v1/chat/completions` API,它接受以下请求格式: 100 | 101 | ```json 102 | { 103 | "model": "gpt-3.5-turbo", 104 | "messages": [ 105 | { 106 | "role": "user", 107 | "content": "Hello, AI." 108 | } 109 | ], 110 | "max_tokens": 100, 111 | "temperature": 1, 112 | "stream": true 113 | } 114 | ``` 115 | 116 | 此项目在这两个 API 之间进行转换,从 Claude 模型获取返回结果并将它们格式化为 OpenAI ChatAPI 的响应。 117 | 118 | ## 许可证 119 | 120 | 此项目为 MIT 许可证授权,有关详细信息,请参阅 LICENSE 文件。 121 | -------------------------------------------------------------------------------- /README_JA.md: -------------------------------------------------------------------------------- 1 |

2 | English | 简体中文 | 日本語 3 |

4 | 5 |
6 |

Claude to ChatGPT

7 |

8 | 9 | release 10 | 11 | 12 | GitHub Repo stars 13 | 14 | 15 | GitHub Repo Badge 16 | 17 | 18 | GitHub Repo Language 19 | 20 |

21 |
22 | 23 | 本プロジェクトでは、Anthropic の Claude モデルの API を OpenAI Chat API 形式に変換しています。 24 | 25 | - ✨ OpenAI ChatGPT API のように Claude API を呼び出す 26 | - 💦 ストリーミングレス対応 27 | - 🐻 `claude-instant-1`, `claude-2` のモデルをサポートする 28 | - 🌩️ Cloudflare Workers や Docker でデプロイする 29 | 30 | ## はじめに 31 | 32 | このプロジェクトは、Cloudflare Workers または Docker を使用して実行することができます: 33 | 34 | ### デプロイ 35 | 36 | #### Cloudflare Workers の利用について 37 | 38 | Cloudflare Workers を利用することで、このプロジェクトを展開するためのサーバーが不要になります。 39 | 40 | 1. Cloudflare Worker を作成する 41 | 2. [`Cloudflare-worker.js`](https://github.com/jtsang4/claude-to-chatgpt/blob/main/cloudflare-worker.js)のコードを Cloudflare Worker の "Quick Edit" エディタに貼り付けます。 42 | 3. 保存してデプロイする 43 | 4. オプションとして、Cloudflare Worker にカスタムドメインを設定します 44 | 45 | Cloudfalre Workers は 1 日 10 万件のリクエストをサポートしていますが、それ以上呼ぶ必要がある場合は、以下のように Docker を使ってデプロイします。 46 | 47 | #### Docker の使用 48 | 49 | ```bash 50 | docker run -p 8000:8000 wtzeng/claude-to-chatgpt:latest 51 | ``` 52 | 53 | #### Docker Compose の使用 54 | 55 | ```bash 56 | docker-compose up 57 | ``` 58 | 59 | その後、http://localhost:8000、API が利用できるようになります。API エンドポイント: `/v1/chat/completions` 60 | 61 | ### 使用方法 62 | 63 | モデルパラメータを `gpt-3.5-turbo` または `gpt-3.5-turbo-0613` と入力すると `claude-instant-1` に置換されます。 64 | 65 | #### GUI 66 | 67 | このプロジェクトをサポートする、おすすめの GUI ソフトをご紹介します: 68 | 69 | - [Bin-Huang/chatbox](https://github.com/Bin-Huang/chatbox) 70 | - [Yidadaa/ChatGPT-Next-Web](https://github.com/Yidadaa/ChatGPT-Next-Web) 71 | 72 | #### CLI 73 | 74 | ```bash 75 | curl http://localhost:8000/v1/chat/completions \ 76 | -H "Content-Type: application/json" \ 77 | -H "Authorization: Bearer $CLAUDE_API_KEY" \ 78 | -d '{ 79 | "model": "gpt-3.5-turbo", 80 | "messages": [{"role": "user", "content": "Hello!"}] 81 | }' 82 | ``` 83 | 84 | ## コンバージョンの詳細 85 | 86 | Claude Completion API にはエンドポイント `/v1/complete` があり、以下の JSON リクエストを受け取ります: 87 | 88 | ```json 89 | { 90 | "prompt": "\n\nHuman: Hello, AI.\n\nAssistant: ", 91 | "model": "claude-instant-1", 92 | "max_tokens_to_sample": 100, 93 | "temperature": 1, 94 | "stream": true 95 | } 96 | ``` 97 | 98 | そして、選択肢と完了を含む JSON を返します。 99 | 100 | OpenAI Chat API には同様の `/v1/chat/completions` エンドポイントがあり、これは次のように受け取ります: 101 | 102 | ```json 103 | { 104 | "model": "gpt-3.5-turbo", 105 | "messages": [ 106 | { 107 | "role": "user", 108 | "content": "Hello, AI." 109 | } 110 | ], 111 | "max_tokens": 100, 112 | "temperature": 1, 113 | "stream": true 114 | } 115 | ``` 116 | 117 | そして、応答文字列を持つ JSON を返します。 118 | 119 | このプロジェクトはこれら 2 つの API 間を変換し、Claude モデルから完了を取得し、OpenAI Chat のレスポンスとしてフォーマットします。 120 | 121 | ## ライセンス 122 | 123 | このプロジェクトは MIT ライセンスでライセンスされています - 詳しくは、LICENSE ファイルをご覧ください。 124 | -------------------------------------------------------------------------------- /claude_to_chatgpt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtsang4/claude-to-chatgpt/e7dcee758b509b2beb44034357862cdf1f6446c6/claude_to_chatgpt/__init__.py -------------------------------------------------------------------------------- /claude_to_chatgpt/adapter.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import time 3 | import json 4 | import os 5 | from fastapi import Request 6 | from claude_to_chatgpt.util import num_tokens_from_string 7 | from claude_to_chatgpt.logger import logger 8 | from claude_to_chatgpt.models import model_map 9 | 10 | role_map = { 11 | "system": "Human", 12 | "user": "Human", 13 | "assistant": "Assistant", 14 | } 15 | 16 | stop_reason_map = { 17 | "stop_sequence": "stop", 18 | "max_tokens": "length", 19 | } 20 | 21 | 22 | class ClaudeAdapter: 23 | def __init__(self, claude_base_url="https://api.anthropic.com"): 24 | self.claude_api_key = os.getenv("CLAUDE_API_KEY", None) 25 | self.claude_base_url = claude_base_url 26 | 27 | def get_api_key(self, headers): 28 | auth_header = headers.get("authorization", None) 29 | if auth_header: 30 | return auth_header.split(" ")[1] 31 | else: 32 | return self.claude_api_key 33 | 34 | def convert_messages_to_prompt(self, messages): 35 | prompt = "" 36 | for message in messages: 37 | role = message["role"] 38 | content = message["content"] 39 | transformed_role = role_map[role] 40 | prompt += f"\n\n{transformed_role.capitalize()}: {content}" 41 | prompt += "\n\nAssistant: " 42 | return prompt 43 | 44 | def openai_to_claude_params(self, openai_params): 45 | model = model_map.get(openai_params["model"], "claude-2") 46 | messages = openai_params["messages"] 47 | 48 | prompt = self.convert_messages_to_prompt(messages) 49 | 50 | claude_params = { 51 | "model": model, 52 | "prompt": prompt, 53 | "max_tokens_to_sample": 100000, 54 | } 55 | 56 | if openai_params.get("max_tokens"): 57 | claude_params["max_tokens_to_sample"] = openai_params["max_tokens"] 58 | 59 | if openai_params.get("stop"): 60 | claude_params["stop_sequences"] = openai_params.get("stop") 61 | 62 | if openai_params.get("temperature"): 63 | claude_params["temperature"] = openai_params.get("temperature") 64 | 65 | if openai_params.get("stream"): 66 | claude_params["stream"] = True 67 | 68 | return claude_params 69 | 70 | def claude_to_chatgpt_response_stream(self, claude_response): 71 | completion = claude_response.get("completion", "") 72 | completion_tokens = num_tokens_from_string(completion) 73 | openai_response = { 74 | "id": f"chatcmpl-{str(time.time())}", 75 | "object": "chat.completion.chunk", 76 | "created": int(time.time()), 77 | "model": "gpt-3.5-turbo-0613", 78 | "usage": { 79 | "prompt_tokens": 0, 80 | "completion_tokens": completion_tokens, 81 | "total_tokens": completion_tokens, 82 | }, 83 | "choices": [ 84 | { 85 | "delta": { 86 | "role": "assistant", 87 | "content": completion, 88 | }, 89 | "index": 0, 90 | "finish_reason": stop_reason_map[claude_response.get("stop_reason")] 91 | if claude_response.get("stop_reason") 92 | else None, 93 | } 94 | ], 95 | } 96 | 97 | return openai_response 98 | 99 | def claude_to_chatgpt_response(self, claude_response): 100 | completion_tokens = num_tokens_from_string( 101 | claude_response.get("completion", "") 102 | ) 103 | openai_response = { 104 | "id": f"chatcmpl-{str(time.time())}", 105 | "object": "chat.completion", 106 | "created": int(time.time()), 107 | "model": "gpt-3.5-turbo-0613", 108 | "usage": { 109 | "prompt_tokens": 0, 110 | "completion_tokens": completion_tokens, 111 | "total_tokens": completion_tokens, 112 | }, 113 | "choices": [ 114 | { 115 | "message": { 116 | "role": "assistant", 117 | "content": claude_response.get("completion", ""), 118 | }, 119 | "index": 0, 120 | "finish_reason": stop_reason_map[claude_response.get("stop_reason")] 121 | if claude_response.get("stop_reason") 122 | else None, 123 | } 124 | ], 125 | } 126 | 127 | return openai_response 128 | 129 | async def chat(self, request: Request): 130 | openai_params = await request.json() 131 | headers = request.headers 132 | claude_params = self.openai_to_claude_params(openai_params) 133 | api_key = self.get_api_key(headers) 134 | 135 | async with httpx.AsyncClient(timeout=120.0) as client: 136 | if not claude_params.get("stream", False): 137 | response = await client.post( 138 | f"{self.claude_base_url}/v1/complete", 139 | headers={ 140 | "x-api-key": api_key, 141 | "accept": "application/json", 142 | "content-type": "application/json", 143 | "anthropic-version": "2023-06-01", 144 | }, 145 | json=claude_params, 146 | ) 147 | if response.is_error: 148 | raise Exception(f"Error: {response.status_code}") 149 | claude_response = response.json() 150 | openai_response = self.claude_to_chatgpt_response(claude_response) 151 | yield openai_response 152 | else: 153 | async with client.stream( 154 | "POST", 155 | f"{self.claude_base_url}/v1/complete", 156 | headers={ 157 | "x-api-key": api_key, 158 | "accept": "application/json", 159 | "content-type": "application/json", 160 | "anthropic-version": "2023-06-01", 161 | }, 162 | json=claude_params, 163 | ) as response: 164 | if response.is_error: 165 | raise Exception(f"Error: {response.status_code}") 166 | async for line in response.aiter_lines(): 167 | if line: 168 | stripped_line = line.lstrip("data:") 169 | if stripped_line: 170 | try: 171 | decoded_line = json.loads(stripped_line) 172 | stop_reason = decoded_line.get("stop_reason") 173 | if stop_reason: 174 | yield self.claude_to_chatgpt_response_stream( 175 | { 176 | "completion": "", 177 | "stop_reason": stop_reason, 178 | } 179 | ) 180 | yield "[DONE]" 181 | else: 182 | completion = decoded_line.get("completion") 183 | if completion: 184 | openai_response = ( 185 | self.claude_to_chatgpt_response_stream( 186 | decoded_line 187 | ) 188 | ) 189 | yield openai_response 190 | except json.JSONDecodeError as e: 191 | logger.debug( 192 | f"Error decoding JSON: {e}" 193 | ) # Debug output 194 | logger.debug( 195 | f"Failed to decode line: {stripped_line}" 196 | ) # Debug output 197 | -------------------------------------------------------------------------------- /claude_to_chatgpt/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Request 2 | from fastapi.responses import JSONResponse, StreamingResponse 3 | from fastapi.middleware.cors import CORSMiddleware 4 | from claude_to_chatgpt.adapter import ClaudeAdapter 5 | import json 6 | import os 7 | from claude_to_chatgpt.logger import logger 8 | from claude_to_chatgpt.models import models_list 9 | 10 | CLAUDE_BASE_URL = os.getenv("CLAUDE_BASE_URL", "https://api.anthropic.com") 11 | LOG_LEVEL = os.getenv("LOG_LEVEL", "info") 12 | PORT = os.getenv("PORT", 8000) 13 | 14 | logger.debug(f"claude_base_url: {CLAUDE_BASE_URL}") 15 | 16 | adapter = ClaudeAdapter(CLAUDE_BASE_URL) 17 | 18 | app = FastAPI() 19 | 20 | # Add CORS middleware 21 | app.add_middleware( 22 | CORSMiddleware, 23 | allow_origins=["*"], # Allow all origins 24 | allow_credentials=True, 25 | allow_methods=["*"], # Allow all methods, including OPTIONS 26 | allow_headers=["*"], 27 | ) 28 | 29 | 30 | @app.api_route( 31 | "/v1/chat/completions", 32 | methods=["POST", "OPTIONS"], 33 | ) 34 | async def chat(request: Request): 35 | openai_params = await request.json() 36 | if openai_params.get("stream", False): 37 | 38 | async def generate(): 39 | async for response in adapter.chat(request): 40 | if response == "[DONE]": 41 | yield "data: [DONE]" 42 | break 43 | yield f"data: {json.dumps(response)}\n\n" 44 | 45 | return StreamingResponse(generate(), media_type="text/event-stream") 46 | else: 47 | openai_response = None 48 | response = adapter.chat(request) 49 | openai_response = await response.__anext__() 50 | return JSONResponse(content=openai_response) 51 | 52 | 53 | @app.route("/v1/models", methods=["GET"]) 54 | async def models(request: Request): 55 | # return a dict with key "object" and "data", "object" value is "list", "data" values is models list 56 | return JSONResponse(content={"object": "list", "data": models_list}) 57 | 58 | 59 | if __name__ == "__main__": 60 | import uvicorn 61 | 62 | uvicorn.run("app:app", host="0.0.0.0", port=PORT, log_level=LOG_LEVEL) 63 | -------------------------------------------------------------------------------- /claude_to_chatgpt/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger("debug") 4 | -------------------------------------------------------------------------------- /claude_to_chatgpt/models.py: -------------------------------------------------------------------------------- 1 | models_list = [ 2 | { 3 | "id": "gpt-3.5-turbo", 4 | "object": "model", 5 | "created": 1677610602, 6 | "owned_by": "openai", 7 | "permission": [ 8 | { 9 | "id": "modelperm-YO9wdQnaovI4GD1HLV59M0AV", 10 | "object": "model_permission", 11 | "created": 1683753011, 12 | "allow_create_engine": False, 13 | "allow_sampling": True, 14 | "allow_logprobs": True, 15 | "allow_search_indices": False, 16 | "allow_view": True, 17 | "allow_fine_tuning": False, 18 | "organization": "*", 19 | "group": None, 20 | "is_blocking": False, 21 | } 22 | ], 23 | "root": "gpt-3.5-turbo", 24 | "parent": None, 25 | }, 26 | { 27 | "id": "gpt-3.5-turbo-0613", 28 | "object": "model", 29 | "created": 1677649963, 30 | "owned_by": "openai", 31 | "permission": [ 32 | { 33 | "id": "modelperm-tsdKKNwiNtHfnKWWTkKChjoo", 34 | "object": "model_permission", 35 | "created": 1683753015, 36 | "allow_create_engine": False, 37 | "allow_sampling": True, 38 | "allow_logprobs": True, 39 | "allow_search_indices": False, 40 | "allow_view": True, 41 | "allow_fine_tuning": False, 42 | "organization": "*", 43 | "group": None, 44 | "is_blocking": False, 45 | } 46 | ], 47 | "root": "gpt-3.5-turbo-0613", 48 | "parent": None, 49 | }, 50 | { 51 | "id": "gpt-4", 52 | "object": "model", 53 | "created": 1678604602, 54 | "owned_by": "openai", 55 | "permission": [ 56 | { 57 | "id": "modelperm-nqKDpzYoZMlqbIltZojY48n9", 58 | "object": "model_permission", 59 | "created": 1683768705, 60 | "allow_create_engine": False, 61 | "allow_sampling": False, 62 | "allow_logprobs": False, 63 | "allow_search_indices": False, 64 | "allow_view": False, 65 | "allow_fine_tuning": False, 66 | "organization": "*", 67 | "group": None, 68 | "is_blocking": False, 69 | } 70 | ], 71 | "root": "gpt-4", 72 | "parent": None, 73 | }, 74 | { 75 | "id": "gpt-4-0613", 76 | "object": "model", 77 | "created": 1678604601, 78 | "owned_by": "openai", 79 | "permission": [ 80 | { 81 | "id": "modelperm-PGbNkIIZZLRipow1uFL0LCvV", 82 | "object": "model_permission", 83 | "created": 1683768678, 84 | "allow_create_engine": False, 85 | "allow_sampling": False, 86 | "allow_logprobs": False, 87 | "allow_search_indices": False, 88 | "allow_view": False, 89 | "allow_fine_tuning": False, 90 | "organization": "*", 91 | "group": None, 92 | "is_blocking": False, 93 | } 94 | ], 95 | "root": "gpt-4-0613", 96 | "parent": None, 97 | }, 98 | ] 99 | 100 | model_map = { 101 | "gpt-3.5-turbo": "claude-instant-1", 102 | "gpt-3.5-turbo-0613": "claude-instant-1", 103 | "gpt-4": "claude-2", 104 | "gpt-4-0613": "claude-2", 105 | } 106 | -------------------------------------------------------------------------------- /claude_to_chatgpt/util.py: -------------------------------------------------------------------------------- 1 | import tiktoken 2 | 3 | 4 | def num_tokens_from_string(string: str, encoding_name: str = "cl100k_base") -> int: 5 | """Returns the number of tokens in a text string.""" 6 | encoding = tiktoken.get_encoding(encoding_name) 7 | num_tokens = len(encoding.encode(string)) 8 | return num_tokens 9 | -------------------------------------------------------------------------------- /cloudflare-worker.js: -------------------------------------------------------------------------------- 1 | const version = '0.6.0'; 2 | 3 | addEventListener('fetch', (event) => { 4 | event.respondWith(handleRequest(event.request)); 5 | }); 6 | 7 | const CLAUDE_API_KEY = ''; // Optional: default claude api key if you don't want to pass it in the request header 8 | const CLAUDE_BASE_URL = 'https://api.anthropic.com/v1/messages'; // Changed to messages endpoint 9 | const MAX_TOKENS = 4096; 10 | 11 | function getAPIKey(headers) { 12 | const authorization = headers.authorization; 13 | if (authorization) { 14 | return authorization.split(' ')[1] || CLAUDE_API_KEY; 15 | } 16 | return CLAUDE_API_KEY; 17 | } 18 | 19 | function formatStreamResponseJson(claudeResponse) { 20 | switch (claudeResponse.type) { 21 | case 'message_start': 22 | return { 23 | id: claudeResponse.message.id, 24 | model: claudeResponse.message.model, 25 | inputTokens: claudeResponse.message.usage.input_tokens, 26 | }; 27 | case 'content_block_start': 28 | case 'ping': 29 | return null; 30 | case 'content_block_delta': 31 | return { 32 | content: claudeResponse.delta.text, 33 | }; 34 | case 'content_block_stop': 35 | return null; 36 | case 'message_delta': 37 | return { 38 | stopReason: claudeResponse.delta.stop_reason, 39 | outputTokens: claudeResponse.usage.output_tokens, 40 | }; 41 | case 'message_stop': 42 | return null; 43 | default: 44 | return null; 45 | } 46 | } 47 | 48 | function claudeToChatGPTResponse(claudeResponse, metaInfo, stream = false) { 49 | const timestamp = Math.floor(Date.now() / 1000); 50 | const completionTokens = metaInfo.outputTokens || 0; 51 | const promptTokens = metaInfo.inputTokens || 0; 52 | if (metaInfo.stopReason && stream) { 53 | return { 54 | id: metaInfo.id, 55 | object: 'chat.completion.chunk', 56 | created: timestamp, 57 | model: metaInfo.model, 58 | choices: [ 59 | { 60 | index: 0, 61 | delta: {}, 62 | logprobs: null, 63 | finish_reason: 'stop', 64 | }, 65 | ], 66 | usage: { 67 | prompt_tokens: promptTokens, 68 | completion_tokens: completionTokens, 69 | total_tokens: promptTokens + completionTokens, 70 | }, 71 | }; 72 | } 73 | const content = claudeResponse.content; 74 | const result = { 75 | id: metaInfo.id || 'unknown', 76 | created: timestamp, 77 | model: metaInfo.model, 78 | usage: { 79 | prompt_tokens: promptTokens, 80 | completion_tokens: completionTokens, 81 | total_tokens: promptTokens + completionTokens, 82 | }, 83 | choices: [ 84 | { 85 | index: 0, 86 | finish_reason: metaInfo.stopReason === 'end_turn' ? 'stop' : null, 87 | }, 88 | ], 89 | }; 90 | const message = { 91 | role: 'assistant', 92 | content: content || '', 93 | }; 94 | if (!stream) { 95 | result.object = 'chat.completion'; 96 | result.choices[0].message = message; 97 | } else { 98 | result.object = 'chat.completion.chunk'; 99 | result.choices[0].delta = message; 100 | } 101 | return result; 102 | } 103 | 104 | async function streamJsonResponseBodies(response, writable, model) { 105 | const reader = response.body.getReader(); 106 | const writer = writable.getWriter(); 107 | 108 | const encoder = new TextEncoder(); 109 | const decoder = new TextDecoder(); 110 | 111 | let buffer = ''; 112 | const metaInfo = { 113 | model, 114 | }; 115 | while (true) { 116 | const { done, value } = await reader.read(); 117 | if (done) { 118 | writer.write(encoder.encode('data: [DONE]')); 119 | break; 120 | } 121 | const currentText = decoder.decode(value, { stream: true }); // stream: true is important here,fix the bug of incomplete line 122 | buffer += currentText; 123 | console.log('🚀 ~ streamJsonResponseBodies ~ buffer:', buffer); 124 | const regex = /event:\s*.*?\s*\ndata:\s*(.*?)(?=\n\n|\s*$)/gs; 125 | 126 | let match; 127 | while ((match = regex.exec(buffer)) !== null) { 128 | try { 129 | const decodedLine = JSON.parse(match[1].trim()); 130 | const formatedChunk = formatStreamResponseJson(decodedLine); 131 | if (formatedChunk === null) { 132 | continue; 133 | } 134 | metaInfo.id = formatedChunk.id ?? metaInfo.id; 135 | metaInfo.model = formatedChunk.model ?? metaInfo.model; 136 | metaInfo.inputTokens = 137 | formatedChunk.inputTokens ?? metaInfo.inputTokens; 138 | metaInfo.outputTokens = 139 | formatedChunk.outputTokens ?? metaInfo.outputTokens; 140 | metaInfo.stopReason = formatedChunk.stopReason ?? metaInfo.stopReason; 141 | const transformedLine = claudeToChatGPTResponse( 142 | formatedChunk, 143 | metaInfo, 144 | true 145 | ); 146 | writer.write( 147 | encoder.encode(`data: ${JSON.stringify(transformedLine)}\n\n`) 148 | ); 149 | } catch (e) {} 150 | // 从buffer中移除已处理的部分 151 | buffer = buffer.slice(match.index + match[0].length); 152 | } 153 | } 154 | await writer.close(); 155 | } 156 | 157 | async function handleRequest(request) { 158 | if (request.method === 'GET') { 159 | const path = new URL(request.url).pathname; 160 | if (path === '/v1/models') { 161 | return new Response( 162 | JSON.stringify({ 163 | object: 'list', 164 | data: models_list, 165 | }), 166 | { 167 | status: 200, 168 | headers: { 'Content-Type': 'application/json' }, 169 | } 170 | ); 171 | } 172 | return new Response('Not Found', { status: 404 }); 173 | } else if (request.method === 'OPTIONS') { 174 | return handleOPTIONS(); 175 | } else if (request.method === 'POST') { 176 | const headers = Object.fromEntries(request.headers); 177 | const apiKey = getAPIKey(headers); 178 | if (!apiKey) { 179 | return new Response('Not Allowed', { 180 | status: 403, 181 | }); 182 | } 183 | 184 | const requestBody = await request.json(); 185 | const { model, messages, temperature, stop, stream } = requestBody; 186 | const claudeModel = model; 187 | 188 | // Convert OpenAI API request to Claude API request 189 | const systemMessage = messages.find((message) => message.role === 'system'); 190 | const claudeRequestBody = { 191 | model: claudeModel, 192 | messages: messages.filter((message) => message.role !== 'system'), 193 | temperature, 194 | max_tokens: MAX_TOKENS, 195 | stop_sequences: stop, 196 | system: systemMessage?.content, 197 | stream, 198 | }; 199 | 200 | const claudeResponse = await fetch(CLAUDE_BASE_URL, { 201 | method: 'POST', 202 | headers: { 203 | 'Content-Type': 'application/json', 204 | 'x-api-key': apiKey, 205 | 'anthropic-version': '2023-06-01', 206 | }, 207 | body: JSON.stringify(claudeRequestBody), 208 | }); 209 | 210 | if (!stream) { 211 | const claudeResponseBody = await claudeResponse.json(); 212 | const formatedResult = { 213 | id: claudeResponseBody.id, 214 | model: claudeResponseBody.model, 215 | inputTokens: claudeResponseBody.usage.input_tokens, 216 | outputTokens: claudeResponseBody.usage.output_tokens, 217 | stopReason: claudeResponseBody.stop_reason, 218 | }; 219 | const openAIResponseBody = claudeToChatGPTResponse( 220 | { content: claudeResponseBody.content[0].text }, 221 | formatedResult 222 | ); 223 | if (openAIResponseBody === null) { 224 | return new Response('Error processing Claude response', { 225 | status: 500, 226 | }); 227 | } 228 | return new Response(JSON.stringify(openAIResponseBody), { 229 | status: claudeResponse.status, 230 | headers: { 231 | 'Content-Type': 'application/json', 232 | 'Access-Control-Allow-Origin': '*', 233 | 'Access-Control-Allow-Methods': '*', 234 | 'Access-Control-Allow-Headers': '*', 235 | 'Access-Control-Allow-Credentials': 'true', 236 | }, 237 | }); 238 | } else { 239 | // Implement streaming logic here 240 | const { readable, writable } = new TransformStream(); 241 | streamJsonResponseBodies(claudeResponse, writable); 242 | return new Response(readable, { 243 | headers: { 244 | 'Content-Type': 'text/event-stream', 245 | 'Access-Control-Allow-Origin': '*', 246 | 'Access-Control-Allow-Methods': '*', 247 | 'Access-Control-Allow-Headers': '*', 248 | 'Access-Control-Allow-Credentials': 'true', 249 | }, 250 | }); 251 | } 252 | } else { 253 | return new Response('Method not allowed', { status: 405 }); 254 | } 255 | } 256 | 257 | function handleOPTIONS() { 258 | return new Response(null, { 259 | headers: { 260 | 'Access-Control-Allow-Origin': '*', 261 | 'Access-Control-Allow-Methods': '*', 262 | 'Access-Control-Allow-Headers': '*', 263 | 'Access-Control-Allow-Credentials': 'true', 264 | }, 265 | }); 266 | } 267 | 268 | const models_list = [ 269 | { 270 | id: 'gpt-3.5-turbo', 271 | object: 'model', 272 | created: 1677610602, 273 | owned_by: 'openai', 274 | permission: [ 275 | { 276 | id: 'modelperm-YO9wdQnaovI4GD1HLV59M0AV', 277 | object: 'model_permission', 278 | created: 1683753011, 279 | allow_create_engine: false, 280 | allow_sampling: true, 281 | allow_logprobs: true, 282 | allow_search_indices: false, 283 | allow_view: true, 284 | allow_fine_tuning: false, 285 | organization: '*', 286 | group: null, 287 | is_blocking: false, 288 | }, 289 | ], 290 | root: 'gpt-3.5-turbo', 291 | parent: null, 292 | }, 293 | { 294 | id: 'gpt-3.5-turbo-0613', 295 | object: 'model', 296 | created: 1677649963, 297 | owned_by: 'openai', 298 | permission: [ 299 | { 300 | id: 'modelperm-tsdKKNwiNtHfnKWWTkKChjoo', 301 | object: 'model_permission', 302 | created: 1683753015, 303 | allow_create_engine: false, 304 | allow_sampling: true, 305 | allow_logprobs: true, 306 | allow_search_indices: false, 307 | allow_view: true, 308 | allow_fine_tuning: false, 309 | organization: '*', 310 | group: null, 311 | is_blocking: false, 312 | }, 313 | ], 314 | root: 'gpt-3.5-turbo-0613', 315 | parent: null, 316 | }, 317 | { 318 | id: 'gpt-4', 319 | object: 'model', 320 | created: 1678604602, 321 | owned_by: 'openai', 322 | permission: [ 323 | { 324 | id: 'modelperm-nqKDpzYoZMlqbIltZojY48n9', 325 | object: 'model_permission', 326 | created: 1683768705, 327 | allow_create_engine: false, 328 | allow_sampling: false, 329 | allow_logprobs: false, 330 | allow_search_indices: false, 331 | allow_view: false, 332 | allow_fine_tuning: false, 333 | organization: '*', 334 | group: null, 335 | is_blocking: false, 336 | }, 337 | ], 338 | root: 'gpt-4', 339 | parent: null, 340 | }, 341 | { 342 | id: 'gpt-4-0613', 343 | object: 'model', 344 | created: 1678604601, 345 | owned_by: 'openai', 346 | permission: [ 347 | { 348 | id: 'modelperm-PGbNkIIZZLRipow1uFL0LCvV', 349 | object: 'model_permission', 350 | created: 1683768678, 351 | allow_create_engine: false, 352 | allow_sampling: false, 353 | allow_logprobs: false, 354 | allow_search_indices: false, 355 | allow_view: false, 356 | allow_fine_tuning: false, 357 | organization: '*', 358 | group: null, 359 | is_blocking: false, 360 | }, 361 | ], 362 | root: 'gpt-4-0613', 363 | parent: null, 364 | }, 365 | ]; 366 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | claude-to-chatgpt: 5 | container_name: claude-to-chatgpt 6 | image: wtzeng/claude-to-chatgpt:latest 7 | restart: unless-stopped 8 | environment: 9 | CLAUDE_BASE_URL: "https://api.anthropic.com" # Claude API base URL 10 | # LOG_LEVEL: "info" # log level 11 | ports: 12 | - "8000:8000" 13 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "anyio" 5 | version = "3.6.2" 6 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 7 | category = "main" 8 | optional = false 9 | python-versions = ">=3.6.2" 10 | files = [ 11 | {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, 12 | {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, 13 | ] 14 | 15 | [package.dependencies] 16 | idna = ">=2.8" 17 | sniffio = ">=1.1" 18 | 19 | [package.extras] 20 | doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 21 | test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] 22 | trio = ["trio (>=0.16,<0.22)"] 23 | 24 | [[package]] 25 | name = "certifi" 26 | version = "2023.5.7" 27 | description = "Python package for providing Mozilla's CA Bundle." 28 | category = "main" 29 | optional = false 30 | python-versions = ">=3.6" 31 | files = [ 32 | {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, 33 | {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, 34 | ] 35 | 36 | [[package]] 37 | name = "charset-normalizer" 38 | version = "3.1.0" 39 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 40 | category = "main" 41 | optional = false 42 | python-versions = ">=3.7.0" 43 | files = [ 44 | {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, 45 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, 46 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, 47 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, 48 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, 49 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, 50 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, 51 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, 52 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, 53 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, 54 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, 55 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, 56 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, 57 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, 58 | {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, 59 | {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, 60 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, 61 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, 62 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, 63 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, 64 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, 65 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, 66 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, 67 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, 68 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, 69 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, 70 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, 71 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, 72 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, 73 | {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, 74 | {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, 75 | {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, 76 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, 77 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, 78 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, 79 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, 80 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, 81 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, 82 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, 83 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, 84 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, 85 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, 86 | {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, 87 | {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, 88 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, 89 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, 90 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, 91 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, 92 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, 93 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, 94 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, 95 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, 96 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, 97 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, 98 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, 99 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, 100 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, 101 | {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, 102 | {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, 103 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, 104 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, 105 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, 106 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, 107 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, 108 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, 109 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, 110 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, 111 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, 112 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, 113 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, 114 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, 115 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, 116 | {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, 117 | {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, 118 | {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, 119 | ] 120 | 121 | [[package]] 122 | name = "click" 123 | version = "8.1.3" 124 | description = "Composable command line interface toolkit" 125 | category = "main" 126 | optional = false 127 | python-versions = ">=3.7" 128 | files = [ 129 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 130 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 131 | ] 132 | 133 | [package.dependencies] 134 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 135 | 136 | [[package]] 137 | name = "colorama" 138 | version = "0.4.6" 139 | description = "Cross-platform colored terminal text." 140 | category = "main" 141 | optional = false 142 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 143 | files = [ 144 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 145 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 146 | ] 147 | 148 | [[package]] 149 | name = "fastapi" 150 | version = "0.95.1" 151 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 152 | category = "main" 153 | optional = false 154 | python-versions = ">=3.7" 155 | files = [ 156 | {file = "fastapi-0.95.1-py3-none-any.whl", hash = "sha256:a870d443e5405982e1667dfe372663abf10754f246866056336d7f01c21dab07"}, 157 | {file = "fastapi-0.95.1.tar.gz", hash = "sha256:9569f0a381f8a457ec479d90fa01005cfddaae07546eb1f3fa035bc4797ae7d5"}, 158 | ] 159 | 160 | [package.dependencies] 161 | pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" 162 | starlette = ">=0.26.1,<0.27.0" 163 | 164 | [package.extras] 165 | all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] 166 | dev = ["pre-commit (>=2.17.0,<3.0.0)", "ruff (==0.0.138)", "uvicorn[standard] (>=0.12.0,<0.21.0)"] 167 | doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer-cli (>=0.0.13,<0.0.14)", "typer[all] (>=0.6.1,<0.8.0)"] 168 | test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6.5.0,<8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.982)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.7)", "pyyaml (>=5.3.1,<7.0.0)", "ruff (==0.0.138)", "sqlalchemy (>=1.3.18,<1.4.43)", "types-orjson (==3.6.2)", "types-ujson (==5.7.0.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] 169 | 170 | [[package]] 171 | name = "h11" 172 | version = "0.14.0" 173 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 174 | category = "main" 175 | optional = false 176 | python-versions = ">=3.7" 177 | files = [ 178 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 179 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 180 | ] 181 | 182 | [[package]] 183 | name = "httpcore" 184 | version = "0.17.0" 185 | description = "A minimal low-level HTTP client." 186 | category = "main" 187 | optional = false 188 | python-versions = ">=3.7" 189 | files = [ 190 | {file = "httpcore-0.17.0-py3-none-any.whl", hash = "sha256:0fdfea45e94f0c9fd96eab9286077f9ff788dd186635ae61b312693e4d943599"}, 191 | {file = "httpcore-0.17.0.tar.gz", hash = "sha256:cc045a3241afbf60ce056202301b4d8b6af08845e3294055eb26b09913ef903c"}, 192 | ] 193 | 194 | [package.dependencies] 195 | anyio = ">=3.0,<5.0" 196 | certifi = "*" 197 | h11 = ">=0.13,<0.15" 198 | sniffio = ">=1.0.0,<2.0.0" 199 | 200 | [package.extras] 201 | http2 = ["h2 (>=3,<5)"] 202 | socks = ["socksio (>=1.0.0,<2.0.0)"] 203 | 204 | [[package]] 205 | name = "httpx" 206 | version = "0.24.0" 207 | description = "The next generation HTTP client." 208 | category = "main" 209 | optional = false 210 | python-versions = ">=3.7" 211 | files = [ 212 | {file = "httpx-0.24.0-py3-none-any.whl", hash = "sha256:447556b50c1921c351ea54b4fe79d91b724ed2b027462ab9a329465d147d5a4e"}, 213 | {file = "httpx-0.24.0.tar.gz", hash = "sha256:507d676fc3e26110d41df7d35ebd8b3b8585052450f4097401c9be59d928c63e"}, 214 | ] 215 | 216 | [package.dependencies] 217 | certifi = "*" 218 | httpcore = ">=0.15.0,<0.18.0" 219 | idna = "*" 220 | sniffio = "*" 221 | 222 | [package.extras] 223 | brotli = ["brotli", "brotlicffi"] 224 | cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] 225 | http2 = ["h2 (>=3,<5)"] 226 | socks = ["socksio (>=1.0.0,<2.0.0)"] 227 | 228 | [[package]] 229 | name = "idna" 230 | version = "3.4" 231 | description = "Internationalized Domain Names in Applications (IDNA)" 232 | category = "main" 233 | optional = false 234 | python-versions = ">=3.5" 235 | files = [ 236 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 237 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 238 | ] 239 | 240 | [[package]] 241 | name = "pydantic" 242 | version = "1.10.7" 243 | description = "Data validation and settings management using python type hints" 244 | category = "main" 245 | optional = false 246 | python-versions = ">=3.7" 247 | files = [ 248 | {file = "pydantic-1.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d"}, 249 | {file = "pydantic-1.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e"}, 250 | {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a"}, 251 | {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f"}, 252 | {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209"}, 253 | {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af"}, 254 | {file = "pydantic-1.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a"}, 255 | {file = "pydantic-1.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1"}, 256 | {file = "pydantic-1.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe"}, 257 | {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd"}, 258 | {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb"}, 259 | {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b"}, 260 | {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca"}, 261 | {file = "pydantic-1.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d"}, 262 | {file = "pydantic-1.10.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918"}, 263 | {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe"}, 264 | {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee"}, 265 | {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1"}, 266 | {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a"}, 267 | {file = "pydantic-1.10.7-cp37-cp37m-win_amd64.whl", hash = "sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914"}, 268 | {file = "pydantic-1.10.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd"}, 269 | {file = "pydantic-1.10.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245"}, 270 | {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d"}, 271 | {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3"}, 272 | {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52"}, 273 | {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209"}, 274 | {file = "pydantic-1.10.7-cp38-cp38-win_amd64.whl", hash = "sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e"}, 275 | {file = "pydantic-1.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143"}, 276 | {file = "pydantic-1.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e"}, 277 | {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d"}, 278 | {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f"}, 279 | {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd"}, 280 | {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5"}, 281 | {file = "pydantic-1.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e"}, 282 | {file = "pydantic-1.10.7-py3-none-any.whl", hash = "sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6"}, 283 | {file = "pydantic-1.10.7.tar.gz", hash = "sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e"}, 284 | ] 285 | 286 | [package.dependencies] 287 | typing-extensions = ">=4.2.0" 288 | 289 | [package.extras] 290 | dotenv = ["python-dotenv (>=0.10.4)"] 291 | email = ["email-validator (>=1.0.3)"] 292 | 293 | [[package]] 294 | name = "regex" 295 | version = "2023.5.5" 296 | description = "Alternative regular expression module, to replace re." 297 | category = "main" 298 | optional = false 299 | python-versions = ">=3.6" 300 | files = [ 301 | {file = "regex-2023.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:48c9ec56579d4ba1c88f42302194b8ae2350265cb60c64b7b9a88dcb7fbde309"}, 302 | {file = "regex-2023.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f4541550459c08fdd6f97aa4e24c6f1932eec780d58a2faa2068253df7d6ff"}, 303 | {file = "regex-2023.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e22e4460f0245b468ee645156a4f84d0fc35a12d9ba79bd7d79bdcd2f9629d"}, 304 | {file = "regex-2023.5.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b870b6f632fc74941cadc2a0f3064ed8409e6f8ee226cdfd2a85ae50473aa94"}, 305 | {file = "regex-2023.5.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:171c52e320fe29260da550d81c6b99f6f8402450dc7777ef5ced2e848f3b6f8f"}, 306 | {file = "regex-2023.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad5524c2aedaf9aa14ef1bc9327f8abd915699dea457d339bebbe2f0d218f86"}, 307 | {file = "regex-2023.5.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a0f874ee8c0bc820e649c900243c6d1e6dc435b81da1492046716f14f1a2a96"}, 308 | {file = "regex-2023.5.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e645c757183ee0e13f0bbe56508598e2d9cd42b8abc6c0599d53b0d0b8dd1479"}, 309 | {file = "regex-2023.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a4c5da39bca4f7979eefcbb36efea04471cd68db2d38fcbb4ee2c6d440699833"}, 310 | {file = "regex-2023.5.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5e3f4468b8c6fd2fd33c218bbd0a1559e6a6fcf185af8bb0cc43f3b5bfb7d636"}, 311 | {file = "regex-2023.5.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:59e4b729eae1a0919f9e4c0fc635fbcc9db59c74ad98d684f4877be3d2607dd6"}, 312 | {file = "regex-2023.5.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ba73a14e9c8f9ac409863543cde3290dba39098fc261f717dc337ea72d3ebad2"}, 313 | {file = "regex-2023.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0bbd5dcb19603ab8d2781fac60114fb89aee8494f4505ae7ad141a3314abb1f9"}, 314 | {file = "regex-2023.5.5-cp310-cp310-win32.whl", hash = "sha256:40005cbd383438aecf715a7b47fe1e3dcbc889a36461ed416bdec07e0ef1db66"}, 315 | {file = "regex-2023.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:59597cd6315d3439ed4b074febe84a439c33928dd34396941b4d377692eca810"}, 316 | {file = "regex-2023.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f08276466fedb9e36e5193a96cb944928301152879ec20c2d723d1031cd4ddd"}, 317 | {file = "regex-2023.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cd46f30e758629c3ee91713529cfbe107ac50d27110fdcc326a42ce2acf4dafc"}, 318 | {file = "regex-2023.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2910502f718828cecc8beff004917dcf577fc5f8f5dd40ffb1ea7612124547b"}, 319 | {file = "regex-2023.5.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:445d6f4fc3bd9fc2bf0416164454f90acab8858cd5a041403d7a11e3356980e8"}, 320 | {file = "regex-2023.5.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18196c16a584619c7c1d843497c069955d7629ad4a3fdee240eb347f4a2c9dbe"}, 321 | {file = "regex-2023.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33d430a23b661629661f1fe8395be2004006bc792bb9fc7c53911d661b69dd7e"}, 322 | {file = "regex-2023.5.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72a28979cc667e5f82ef433db009184e7ac277844eea0f7f4d254b789517941d"}, 323 | {file = "regex-2023.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f764e4dfafa288e2eba21231f455d209f4709436baeebb05bdecfb5d8ddc3d35"}, 324 | {file = "regex-2023.5.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23d86ad2121b3c4fc78c58f95e19173790e22ac05996df69b84e12da5816cb17"}, 325 | {file = "regex-2023.5.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:690a17db524ee6ac4a27efc5406530dd90e7a7a69d8360235323d0e5dafb8f5b"}, 326 | {file = "regex-2023.5.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:1ecf3dcff71f0c0fe3e555201cbe749fa66aae8d18f80d2cc4de8e66df37390a"}, 327 | {file = "regex-2023.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:811040d7f3dd9c55eb0d8b00b5dcb7fd9ae1761c454f444fd9f37fe5ec57143a"}, 328 | {file = "regex-2023.5.5-cp311-cp311-win32.whl", hash = "sha256:c8c143a65ce3ca42e54d8e6fcaf465b6b672ed1c6c90022794a802fb93105d22"}, 329 | {file = "regex-2023.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:586a011f77f8a2da4b888774174cd266e69e917a67ba072c7fc0e91878178a80"}, 330 | {file = "regex-2023.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b6365703e8cf1644b82104cdd05270d1a9f043119a168d66c55684b1b557d008"}, 331 | {file = "regex-2023.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a56c18f21ac98209da9c54ae3ebb3b6f6e772038681d6cb43b8d53da3b09ee81"}, 332 | {file = "regex-2023.5.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8b942d8b3ce765dbc3b1dad0a944712a89b5de290ce8f72681e22b3c55f3cc8"}, 333 | {file = "regex-2023.5.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:844671c9c1150fcdac46d43198364034b961bd520f2c4fdaabfc7c7d7138a2dd"}, 334 | {file = "regex-2023.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2ce65bdeaf0a386bb3b533a28de3994e8e13b464ac15e1e67e4603dd88787fa"}, 335 | {file = "regex-2023.5.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fee0016cc35a8a91e8cc9312ab26a6fe638d484131a7afa79e1ce6165328a135"}, 336 | {file = "regex-2023.5.5-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:18f05d14f14a812fe9723f13afafefe6b74ca042d99f8884e62dbd34dcccf3e2"}, 337 | {file = "regex-2023.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:941b3f1b2392f0bcd6abf1bc7a322787d6db4e7457be6d1ffd3a693426a755f2"}, 338 | {file = "regex-2023.5.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:921473a93bcea4d00295799ab929522fc650e85c6b9f27ae1e6bb32a790ea7d3"}, 339 | {file = "regex-2023.5.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:e2205a81f815b5bb17e46e74cc946c575b484e5f0acfcb805fb252d67e22938d"}, 340 | {file = "regex-2023.5.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:385992d5ecf1a93cb85adff2f73e0402dd9ac29b71b7006d342cc920816e6f32"}, 341 | {file = "regex-2023.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:890a09cb0a62198bff92eda98b2b507305dd3abf974778bae3287f98b48907d3"}, 342 | {file = "regex-2023.5.5-cp36-cp36m-win32.whl", hash = "sha256:821a88b878b6589c5068f4cc2cfeb2c64e343a196bc9d7ac68ea8c2a776acd46"}, 343 | {file = "regex-2023.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:7918a1b83dd70dc04ab5ed24c78ae833ae8ea228cef84e08597c408286edc926"}, 344 | {file = "regex-2023.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:338994d3d4ca4cf12f09822e025731a5bdd3a37aaa571fa52659e85ca793fb67"}, 345 | {file = "regex-2023.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a69cf0c00c4d4a929c6c7717fd918414cab0d6132a49a6d8fc3ded1988ed2ea"}, 346 | {file = "regex-2023.5.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f5e06df94fff8c4c85f98c6487f6636848e1dc85ce17ab7d1931df4a081f657"}, 347 | {file = "regex-2023.5.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8906669b03c63266b6a7693d1f487b02647beb12adea20f8840c1a087e2dfb5"}, 348 | {file = "regex-2023.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fda3e50abad8d0f48df621cf75adc73c63f7243cbe0e3b2171392b445401550"}, 349 | {file = "regex-2023.5.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ac2b7d341dc1bd102be849d6dd33b09701223a851105b2754339e390be0627a"}, 350 | {file = "regex-2023.5.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fb2b495dd94b02de8215625948132cc2ea360ae84fe6634cd19b6567709c8ae2"}, 351 | {file = "regex-2023.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aa7d032c1d84726aa9edeb6accf079b4caa87151ca9fabacef31fa028186c66d"}, 352 | {file = "regex-2023.5.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d45864693351c15531f7e76f545ec35000d50848daa833cead96edae1665559"}, 353 | {file = "regex-2023.5.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21e90a288e6ba4bf44c25c6a946cb9b0f00b73044d74308b5e0afd190338297c"}, 354 | {file = "regex-2023.5.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:10250a093741ec7bf74bcd2039e697f519b028518f605ff2aa7ac1e9c9f97423"}, 355 | {file = "regex-2023.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6b8d0c153f07a953636b9cdb3011b733cadd4178123ef728ccc4d5969e67f3c2"}, 356 | {file = "regex-2023.5.5-cp37-cp37m-win32.whl", hash = "sha256:10374c84ee58c44575b667310d5bbfa89fb2e64e52349720a0182c0017512f6c"}, 357 | {file = "regex-2023.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9b320677521aabf666cdd6e99baee4fb5ac3996349c3b7f8e7c4eee1c00dfe3a"}, 358 | {file = "regex-2023.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:afb1c70ec1e594a547f38ad6bf5e3d60304ce7539e677c1429eebab115bce56e"}, 359 | {file = "regex-2023.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cf123225945aa58b3057d0fba67e8061c62d14cc8a4202630f8057df70189051"}, 360 | {file = "regex-2023.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a99757ad7fe5c8a2bb44829fc57ced11253e10f462233c1255fe03888e06bc19"}, 361 | {file = "regex-2023.5.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a623564d810e7a953ff1357f7799c14bc9beeab699aacc8b7ab7822da1e952b8"}, 362 | {file = "regex-2023.5.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ced02e3bd55e16e89c08bbc8128cff0884d96e7f7a5633d3dc366b6d95fcd1d6"}, 363 | {file = "regex-2023.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cbe6b5be3b9b698d8cc4ee4dee7e017ad655e83361cd0ea8e653d65e469468"}, 364 | {file = "regex-2023.5.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a6e4b0e0531223f53bad07ddf733af490ba2b8367f62342b92b39b29f72735a"}, 365 | {file = "regex-2023.5.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e9c4f778514a560a9c9aa8e5538bee759b55f6c1dcd35613ad72523fd9175b8"}, 366 | {file = "regex-2023.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:256f7f4c6ba145f62f7a441a003c94b8b1af78cee2cccacfc1e835f93bc09426"}, 367 | {file = "regex-2023.5.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd7b68fd2e79d59d86dcbc1ccd6e2ca09c505343445daaa4e07f43c8a9cc34da"}, 368 | {file = "regex-2023.5.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4a5059bd585e9e9504ef9c07e4bc15b0a621ba20504388875d66b8b30a5c4d18"}, 369 | {file = "regex-2023.5.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:6893544e06bae009916a5658ce7207e26ed17385149f35a3125f5259951f1bbe"}, 370 | {file = "regex-2023.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c64d5abe91a3dfe5ff250c6bb267ef00dbc01501518225b45a5f9def458f31fb"}, 371 | {file = "regex-2023.5.5-cp38-cp38-win32.whl", hash = "sha256:7923470d6056a9590247ff729c05e8e0f06bbd4efa6569c916943cb2d9b68b91"}, 372 | {file = "regex-2023.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:4035d6945cb961c90c3e1c1ca2feb526175bcfed44dfb1cc77db4fdced060d3e"}, 373 | {file = "regex-2023.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:50fd2d9b36938d4dcecbd684777dd12a407add4f9f934f235c66372e630772b0"}, 374 | {file = "regex-2023.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d19e57f888b00cd04fc38f5e18d0efbd91ccba2d45039453ab2236e6eec48d4d"}, 375 | {file = "regex-2023.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd966475e963122ee0a7118ec9024388c602d12ac72860f6eea119a3928be053"}, 376 | {file = "regex-2023.5.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db09e6c18977a33fea26fe67b7a842f706c67cf8bda1450974d0ae0dd63570df"}, 377 | {file = "regex-2023.5.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6164d4e2a82f9ebd7752a06bd6c504791bedc6418c0196cd0a23afb7f3e12b2d"}, 378 | {file = "regex-2023.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84397d3f750d153ebd7f958efaa92b45fea170200e2df5e0e1fd4d85b7e3f58a"}, 379 | {file = "regex-2023.5.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c3efee9bb53cbe7b285760c81f28ac80dc15fa48b5fe7e58b52752e642553f1"}, 380 | {file = "regex-2023.5.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:144b5b017646b5a9392a5554a1e5db0000ae637be4971c9747566775fc96e1b2"}, 381 | {file = "regex-2023.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1189fbbb21e2c117fda5303653b61905aeeeea23de4a94d400b0487eb16d2d60"}, 382 | {file = "regex-2023.5.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f83fe9e10f9d0b6cf580564d4d23845b9d692e4c91bd8be57733958e4c602956"}, 383 | {file = "regex-2023.5.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:72aa4746993a28c841e05889f3f1b1e5d14df8d3daa157d6001a34c98102b393"}, 384 | {file = "regex-2023.5.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:de2f780c3242ea114dd01f84848655356af4dd561501896c751d7b885ea6d3a1"}, 385 | {file = "regex-2023.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:290fd35219486dfbc00b0de72f455ecdd63e59b528991a6aec9fdfc0ce85672e"}, 386 | {file = "regex-2023.5.5-cp39-cp39-win32.whl", hash = "sha256:732176f5427e72fa2325b05c58ad0b45af341c459910d766f814b0584ac1f9ac"}, 387 | {file = "regex-2023.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:1307aa4daa1cbb23823d8238e1f61292fd07e4e5d8d38a6efff00b67a7cdb764"}, 388 | {file = "regex-2023.5.5.tar.gz", hash = "sha256:7d76a8a1fc9da08296462a18f16620ba73bcbf5909e42383b253ef34d9d5141e"}, 389 | ] 390 | 391 | [[package]] 392 | name = "requests" 393 | version = "2.30.0" 394 | description = "Python HTTP for Humans." 395 | category = "main" 396 | optional = false 397 | python-versions = ">=3.7" 398 | files = [ 399 | {file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"}, 400 | {file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"}, 401 | ] 402 | 403 | [package.dependencies] 404 | certifi = ">=2017.4.17" 405 | charset-normalizer = ">=2,<4" 406 | idna = ">=2.5,<4" 407 | urllib3 = ">=1.21.1,<3" 408 | 409 | [package.extras] 410 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 411 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 412 | 413 | [[package]] 414 | name = "sniffio" 415 | version = "1.3.0" 416 | description = "Sniff out which async library your code is running under" 417 | category = "main" 418 | optional = false 419 | python-versions = ">=3.7" 420 | files = [ 421 | {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, 422 | {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, 423 | ] 424 | 425 | [[package]] 426 | name = "socksio" 427 | version = "1.0.0" 428 | description = "Sans-I/O implementation of SOCKS4, SOCKS4A, and SOCKS5." 429 | category = "main" 430 | optional = false 431 | python-versions = ">=3.6" 432 | files = [ 433 | {file = "socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3"}, 434 | {file = "socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac"}, 435 | ] 436 | 437 | [[package]] 438 | name = "starlette" 439 | version = "0.26.1" 440 | description = "The little ASGI library that shines." 441 | category = "main" 442 | optional = false 443 | python-versions = ">=3.7" 444 | files = [ 445 | {file = "starlette-0.26.1-py3-none-any.whl", hash = "sha256:e87fce5d7cbdde34b76f0ac69013fd9d190d581d80681493016666e6f96c6d5e"}, 446 | {file = "starlette-0.26.1.tar.gz", hash = "sha256:41da799057ea8620e4667a3e69a5b1923ebd32b1819c8fa75634bbe8d8bea9bd"}, 447 | ] 448 | 449 | [package.dependencies] 450 | anyio = ">=3.4.0,<5" 451 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} 452 | 453 | [package.extras] 454 | full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] 455 | 456 | [[package]] 457 | name = "tiktoken" 458 | version = "0.4.0" 459 | description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" 460 | category = "main" 461 | optional = false 462 | python-versions = ">=3.8" 463 | files = [ 464 | {file = "tiktoken-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:176cad7f053d2cc82ce7e2a7c883ccc6971840a4b5276740d0b732a2b2011f8a"}, 465 | {file = "tiktoken-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:450d504892b3ac80207700266ee87c932df8efea54e05cefe8613edc963c1285"}, 466 | {file = "tiktoken-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d662de1e7986d129139faf15e6a6ee7665ee103440769b8dedf3e7ba6ac37f"}, 467 | {file = "tiktoken-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5727d852ead18b7927b8adf558a6f913a15c7766725b23dbe21d22e243041b28"}, 468 | {file = "tiktoken-0.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c06cd92b09eb0404cedce3702fa866bf0d00e399439dad3f10288ddc31045422"}, 469 | {file = "tiktoken-0.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9ec161e40ed44e4210d3b31e2ff426b4a55e8254f1023e5d2595cb60044f8ea6"}, 470 | {file = "tiktoken-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:1e8fa13cf9889d2c928b9e258e9dbbbf88ab02016e4236aae76e3b4f82dd8288"}, 471 | {file = "tiktoken-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bb2341836b725c60d0ab3c84970b9b5f68d4b733a7bcb80fb25967e5addb9920"}, 472 | {file = "tiktoken-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ca30367ad750ee7d42fe80079d3092bd35bb266be7882b79c3bd159b39a17b0"}, 473 | {file = "tiktoken-0.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dc3df19ddec79435bb2a94ee46f4b9560d0299c23520803d851008445671197"}, 474 | {file = "tiktoken-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d980fa066e962ef0f4dad0222e63a484c0c993c7a47c7dafda844ca5aded1f3"}, 475 | {file = "tiktoken-0.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:329f548a821a2f339adc9fbcfd9fc12602e4b3f8598df5593cfc09839e9ae5e4"}, 476 | {file = "tiktoken-0.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b1a038cee487931a5caaef0a2e8520e645508cde21717eacc9af3fbda097d8bb"}, 477 | {file = "tiktoken-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:08efa59468dbe23ed038c28893e2a7158d8c211c3dd07f2bbc9a30e012512f1d"}, 478 | {file = "tiktoken-0.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f3020350685e009053829c1168703c346fb32c70c57d828ca3742558e94827a9"}, 479 | {file = "tiktoken-0.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba16698c42aad8190e746cd82f6a06769ac7edd415d62ba027ea1d99d958ed93"}, 480 | {file = "tiktoken-0.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c15d9955cc18d0d7ffcc9c03dc51167aedae98542238b54a2e659bd25fe77ed"}, 481 | {file = "tiktoken-0.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64e1091c7103100d5e2c6ea706f0ec9cd6dc313e6fe7775ef777f40d8c20811e"}, 482 | {file = "tiktoken-0.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e87751b54eb7bca580126353a9cf17a8a8eaadd44edaac0e01123e1513a33281"}, 483 | {file = "tiktoken-0.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e063b988b8ba8b66d6cc2026d937557437e79258095f52eaecfafb18a0a10c03"}, 484 | {file = "tiktoken-0.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9c6dd439e878172dc163fced3bc7b19b9ab549c271b257599f55afc3a6a5edef"}, 485 | {file = "tiktoken-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d1d97f83697ff44466c6bef5d35b6bcdb51e0125829a9c0ed1e6e39fb9a08fb"}, 486 | {file = "tiktoken-0.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b6bce7c68aa765f666474c7c11a7aebda3816b58ecafb209afa59c799b0dd2d"}, 487 | {file = "tiktoken-0.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a73286c35899ca51d8d764bc0b4d60838627ce193acb60cc88aea60bddec4fd"}, 488 | {file = "tiktoken-0.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0394967d2236a60fd0aacef26646b53636423cc9c70c32f7c5124ebe86f3093"}, 489 | {file = "tiktoken-0.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:dae2af6f03ecba5f679449fa66ed96585b2fa6accb7fd57d9649e9e398a94f44"}, 490 | {file = "tiktoken-0.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55e251b1da3c293432179cf7c452cfa35562da286786be5a8b1ee3405c2b0dd2"}, 491 | {file = "tiktoken-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:c835d0ee1f84a5aa04921717754eadbc0f0a56cf613f78dfc1cf9ad35f6c3fea"}, 492 | {file = "tiktoken-0.4.0.tar.gz", hash = "sha256:59b20a819969735b48161ced9b92f05dc4519c17be4015cfb73b65270a243620"}, 493 | ] 494 | 495 | [package.dependencies] 496 | regex = ">=2022.1.18" 497 | requests = ">=2.26.0" 498 | 499 | [package.extras] 500 | blobfile = ["blobfile (>=2)"] 501 | 502 | [[package]] 503 | name = "typing-extensions" 504 | version = "4.5.0" 505 | description = "Backported and Experimental Type Hints for Python 3.7+" 506 | category = "main" 507 | optional = false 508 | python-versions = ">=3.7" 509 | files = [ 510 | {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, 511 | {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, 512 | ] 513 | 514 | [[package]] 515 | name = "urllib3" 516 | version = "2.0.2" 517 | description = "HTTP library with thread-safe connection pooling, file post, and more." 518 | category = "main" 519 | optional = false 520 | python-versions = ">=3.7" 521 | files = [ 522 | {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"}, 523 | {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"}, 524 | ] 525 | 526 | [package.extras] 527 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 528 | secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] 529 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 530 | zstd = ["zstandard (>=0.18.0)"] 531 | 532 | [[package]] 533 | name = "uvicorn" 534 | version = "0.22.0" 535 | description = "The lightning-fast ASGI server." 536 | category = "main" 537 | optional = false 538 | python-versions = ">=3.7" 539 | files = [ 540 | {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"}, 541 | {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"}, 542 | ] 543 | 544 | [package.dependencies] 545 | click = ">=7.0" 546 | h11 = ">=0.8" 547 | 548 | [package.extras] 549 | standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] 550 | 551 | [metadata] 552 | lock-version = "2.0" 553 | python-versions = "^3.9.16" 554 | content-hash = "c829784cca288416558dd573e9762dd0a9f104b9b50c329db21f3382b89826c3" 555 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "claude-to-chatgpt" 3 | version = "0.4.0" 4 | description = "" 5 | authors = ["jtsang4 "] 6 | readme = "README.md" 7 | packages = [] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.9.16" 11 | tiktoken = "^0.4.0" 12 | fastapi = "^0.95.1" 13 | uvicorn = "^0.22.0" 14 | httpx = "^0.24.0" 15 | socksio = "^1.0.0" 16 | 17 | 18 | [build-system] 19 | requires = ["poetry-core"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtsang4/claude-to-chatgpt/e7dcee758b509b2beb44034357862cdf1f6446c6/tests/__init__.py -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "claude-to-chatgpt" 2 | main = "cloudflare-worker.js" 3 | compatibility_date = "2023-05-13" --------------------------------------------------------------------------------