indoxrouter 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. indoxRouter/__init__.py +83 -0
  2. indoxRouter/client.py +564 -218
  3. indoxRouter/client_resourses/__init__.py +20 -0
  4. indoxRouter/client_resourses/base.py +67 -0
  5. indoxRouter/client_resourses/chat.py +144 -0
  6. indoxRouter/client_resourses/completion.py +138 -0
  7. indoxRouter/client_resourses/embedding.py +83 -0
  8. indoxRouter/client_resourses/image.py +116 -0
  9. indoxRouter/client_resourses/models.py +114 -0
  10. indoxRouter/config.py +151 -0
  11. indoxRouter/constants/__init__.py +81 -0
  12. indoxRouter/exceptions/__init__.py +70 -0
  13. indoxRouter/models/__init__.py +111 -0
  14. indoxRouter/providers/__init__.py +50 -50
  15. indoxRouter/providers/ai21labs.json +128 -0
  16. indoxRouter/providers/base_provider.py +62 -30
  17. indoxRouter/providers/claude.json +164 -0
  18. indoxRouter/providers/cohere.json +116 -0
  19. indoxRouter/providers/databricks.json +110 -0
  20. indoxRouter/providers/deepseek.json +110 -0
  21. indoxRouter/providers/google.json +128 -0
  22. indoxRouter/providers/meta.json +128 -0
  23. indoxRouter/providers/mistral.json +146 -0
  24. indoxRouter/providers/nvidia.json +110 -0
  25. indoxRouter/providers/openai.json +308 -0
  26. indoxRouter/providers/openai.py +471 -72
  27. indoxRouter/providers/qwen.json +110 -0
  28. indoxRouter/utils/__init__.py +240 -0
  29. indoxrouter-0.1.2.dist-info/LICENSE +21 -0
  30. indoxrouter-0.1.2.dist-info/METADATA +259 -0
  31. indoxrouter-0.1.2.dist-info/RECORD +33 -0
  32. indoxRouter/api_endpoints.py +0 -336
  33. indoxRouter/client_package.py +0 -138
  34. indoxRouter/init_db.py +0 -71
  35. indoxRouter/main.py +0 -711
  36. indoxRouter/migrations/__init__.py +0 -1
  37. indoxRouter/migrations/env.py +0 -98
  38. indoxRouter/migrations/versions/__init__.py +0 -1
  39. indoxRouter/migrations/versions/initial_schema.py +0 -84
  40. indoxRouter/providers/ai21.py +0 -268
  41. indoxRouter/providers/claude.py +0 -177
  42. indoxRouter/providers/cohere.py +0 -171
  43. indoxRouter/providers/databricks.py +0 -166
  44. indoxRouter/providers/deepseek.py +0 -166
  45. indoxRouter/providers/google.py +0 -216
  46. indoxRouter/providers/llama.py +0 -164
  47. indoxRouter/providers/meta.py +0 -227
  48. indoxRouter/providers/mistral.py +0 -182
  49. indoxRouter/providers/nvidia.py +0 -164
  50. indoxrouter-0.1.0.dist-info/METADATA +0 -179
  51. indoxrouter-0.1.0.dist-info/RECORD +0 -27
  52. {indoxrouter-0.1.0.dist-info → indoxrouter-0.1.2.dist-info}/WHEEL +0 -0
  53. {indoxrouter-0.1.0.dist-info → indoxrouter-0.1.2.dist-info}/top_level.txt +0 -0
@@ -1,182 +0,0 @@
1
- from typing import Dict, Any, Optional, List
2
- import json
3
- import os
4
- from pathlib import Path
5
-
6
- try:
7
- import mistralai
8
- from mistralai.client import MistralClient
9
- from mistralai.models.chatcompletionrequest import (
10
- UserMessage,
11
- SystemMessage,
12
- AssistantMessage,
13
- )
14
- except ImportError:
15
- raise ImportError(
16
- "Mistral package not installed. Install it with 'pip install mistralai'"
17
- )
18
-
19
- from ..utils.exceptions import RateLimitError, ModelNotFoundError
20
- from .base_provider import BaseProvider
21
-
22
-
23
- class Provider(BaseProvider):
24
- """
25
- Mistral provider implementation
26
- """
27
-
28
- def __init__(self, api_key: str, model_name: str):
29
- """
30
- Initialize the Mistral provider
31
-
32
- Args:
33
- api_key: Mistral API key
34
- model_name: Model name to use (e.g., 'mistral-large-latest')
35
- """
36
- super().__init__(api_key, model_name)
37
-
38
- # Initialize Mistral client
39
- self.client = MistralClient(api_key=api_key)
40
-
41
- # Load model configuration
42
- self.model_config = self._load_model_config(model_name)
43
-
44
- def _load_model_config(self, model_name: str) -> Dict[str, Any]:
45
- """
46
- Load model configuration from JSON file
47
-
48
- Args:
49
- model_name: Model name
50
-
51
- Returns:
52
- Model configuration dictionary
53
- """
54
- # Get the path to the model configuration file
55
- config_path = Path(__file__).parent / "mistral.json"
56
-
57
- # Load the configuration
58
- with open(config_path, "r") as f:
59
- models = json.load(f)
60
-
61
- # Find the model configuration
62
- for model in models:
63
- if model.get("modelName") == model_name:
64
- return model
65
-
66
- # If model not found, raise an error
67
- raise ModelNotFoundError(
68
- f"Model {model_name} not found in Mistral configuration"
69
- )
70
-
71
- def estimate_cost(self, prompt: str, max_tokens: int) -> float:
72
- """
73
- Estimate the cost of generating a completion
74
-
75
- Args:
76
- prompt: The prompt to generate a completion for
77
- max_tokens: Maximum number of tokens to generate
78
-
79
- Returns:
80
- Estimated cost in credits
81
- """
82
- # Estimate token count (rough approximation)
83
- prompt_tokens = self.count_tokens(prompt)
84
-
85
- # Get pricing for the model
86
- input_price = self.model_config.get("inputPricePer1KTokens", 0)
87
- output_price = self.model_config.get("outputPricePer1KTokens", 0)
88
-
89
- # Calculate cost
90
- prompt_cost = (prompt_tokens / 1000) * input_price
91
- completion_cost = (max_tokens / 1000) * output_price
92
-
93
- return prompt_cost + completion_cost
94
-
95
- def count_tokens(self, text: str) -> int:
96
- """
97
- Count the number of tokens in a text
98
-
99
- Args:
100
- text: Text to count tokens for
101
-
102
- Returns:
103
- Number of tokens
104
- """
105
- # Simple approximation - in production, use a proper tokenizer
106
- return len(text.split()) * 1.3
107
-
108
- def generate(self, prompt: str, **kwargs) -> Dict[str, Any]:
109
- """
110
- Generate a completion using Mistral
111
-
112
- Args:
113
- prompt: The prompt to generate a completion for
114
- **kwargs: Additional parameters for the generation
115
- - max_tokens: Maximum number of tokens to generate
116
- - temperature: Sampling temperature (0.0 to 1.0)
117
- - top_p: Nucleus sampling parameter (0.0 to 1.0)
118
- - system_prompt: System prompt to use
119
-
120
- Returns:
121
- Dictionary containing the response text, cost, and other metadata
122
- """
123
- try:
124
- # Extract parameters
125
- max_tokens = kwargs.get("max_tokens", 1024)
126
- temperature = kwargs.get("temperature", 0.7)
127
- top_p = kwargs.get("top_p", 1.0)
128
- system_prompt = kwargs.get(
129
- "system_prompt", self.model_config.get("systemPrompt", "")
130
- )
131
-
132
- # Prepare messages
133
- messages = []
134
-
135
- # Add system message if provided
136
- if system_prompt:
137
- messages.append(SystemMessage(content=system_prompt))
138
-
139
- # Add user message
140
- messages.append(UserMessage(content=prompt))
141
-
142
- # Make API call
143
- response = self.client.chat(
144
- model=self.model_name,
145
- messages=messages,
146
- max_tokens=max_tokens,
147
- temperature=temperature,
148
- top_p=top_p,
149
- )
150
-
151
- # Extract response text
152
- text = response.choices[0].message.content
153
-
154
- # Calculate actual cost
155
- input_price = self.model_config.get("inputPricePer1KTokens", 0)
156
- output_price = self.model_config.get("outputPricePer1KTokens", 0)
157
-
158
- prompt_tokens = response.usage.prompt_tokens
159
- completion_tokens = response.usage.completion_tokens
160
-
161
- prompt_cost = (prompt_tokens / 1000) * input_price
162
- completion_cost = (completion_tokens / 1000) * output_price
163
- total_cost = prompt_cost + completion_cost
164
-
165
- # Return standardized response
166
- return {
167
- "text": text,
168
- "cost": total_cost,
169
- "usage": {
170
- "prompt_tokens": prompt_tokens,
171
- "completion_tokens": completion_tokens,
172
- "total_tokens": prompt_tokens + completion_tokens,
173
- },
174
- "model": self.model_name,
175
- }
176
-
177
- except mistralai.exceptions.MistralAPIError as e:
178
- if "rate limit" in str(e).lower():
179
- raise RateLimitError(f"Mistral rate limit exceeded: {str(e)}")
180
- raise Exception(f"Mistral API error: {str(e)}")
181
- except Exception as e:
182
- raise Exception(f"Error generating completion: {str(e)}")
@@ -1,164 +0,0 @@
1
- import os
2
- import json
3
- import requests
4
- from typing import Dict, Any, Optional, List
5
- from .base_provider import BaseProvider
6
-
7
-
8
- class Provider(BaseProvider):
9
- def __init__(self, api_key: str, model_name: str):
10
- """
11
- Initialize the NVIDIA provider with API key and model name.
12
-
13
- Args:
14
- api_key (str): The API key for authentication
15
- model_name (str): The name of the model to use
16
- """
17
- super().__init__(api_key, model_name)
18
- self.base_url = os.environ.get("NVIDIA_API_BASE", "https://api.nvidia.com/v1")
19
- self.headers = {
20
- "Authorization": f"Bearer {api_key}",
21
- "Content-Type": "application/json",
22
- }
23
- self.model_config = self._load_model_config()
24
-
25
- def _load_model_config(self) -> Dict[str, Any]:
26
- """
27
- Load the model configuration from the JSON file.
28
-
29
- Returns:
30
- Dict[str, Any]: The model configuration
31
- """
32
- current_dir = os.path.dirname(os.path.abspath(__file__))
33
- config_path = os.path.join(current_dir, "nvidia.json")
34
-
35
- with open(config_path, "r") as f:
36
- models = json.load(f)
37
-
38
- for model in models:
39
- if model["modelName"] == self.model_name:
40
- return model
41
-
42
- raise ValueError(f"Model {self.model_name} not found in configuration")
43
-
44
- def estimate_cost(self, prompt: str, max_tokens: int = 100) -> float:
45
- """
46
- Estimate the cost of generating a completion.
47
-
48
- Args:
49
- prompt (str): The prompt to generate a completion for
50
- max_tokens (int, optional): The maximum number of tokens to generate. Defaults to 100.
51
-
52
- Returns:
53
- float: The estimated cost in USD
54
- """
55
- input_tokens = self.count_tokens(prompt)
56
- input_cost = (input_tokens / 1000) * self.model_config["inputPricePer1KTokens"]
57
- output_cost = (max_tokens / 1000) * self.model_config["outputPricePer1KTokens"]
58
- return input_cost + output_cost
59
-
60
- def count_tokens(self, text: str) -> int:
61
- """
62
- Count the number of tokens in a text.
63
- This is a simple approximation. For more accurate counts, consider using a tokenizer.
64
-
65
- Args:
66
- text (str): The text to count tokens for
67
-
68
- Returns:
69
- int: The number of tokens
70
- """
71
- # Simple approximation: 1 token ≈ 4 characters
72
- return len(text) // 4
73
-
74
- def generate(
75
- self,
76
- prompt: str,
77
- max_tokens: int = 100,
78
- temperature: float = 0.7,
79
- top_p: float = 1.0,
80
- frequency_penalty: float = 0.0,
81
- presence_penalty: float = 0.0,
82
- stop: Optional[List[str]] = None,
83
- ) -> Dict[str, Any]:
84
- """
85
- Generate a completion for the given prompt.
86
-
87
- Args:
88
- prompt (str): The prompt to generate a completion for
89
- max_tokens (int, optional): The maximum number of tokens to generate. Defaults to 100.
90
- temperature (float, optional): The temperature for sampling. Defaults to 0.7.
91
- top_p (float, optional): The top-p value for nucleus sampling. Defaults to 1.0.
92
- frequency_penalty (float, optional): The frequency penalty. Defaults to 0.0.
93
- presence_penalty (float, optional): The presence penalty. Defaults to 0.0.
94
- stop (Optional[List[str]], optional): A list of stop sequences. Defaults to None.
95
-
96
- Returns:
97
- Dict[str, Any]: The generated completion
98
- """
99
- # Format the prompt according to the model's template
100
- prompt_template = self.model_config.get("promptTemplate", "%1%2")
101
- formatted_prompt = prompt_template.replace("%1", prompt).replace("%2", "")
102
-
103
- # Prepare the request payload
104
- payload = {
105
- "model": self.model_config.get("companyModelName", self.model_name),
106
- "prompt": formatted_prompt,
107
- "max_tokens": max_tokens,
108
- "temperature": temperature,
109
- "top_p": top_p,
110
- "frequency_penalty": frequency_penalty,
111
- "presence_penalty": presence_penalty,
112
- }
113
-
114
- if stop:
115
- payload["stop"] = stop
116
-
117
- # Make the API request
118
- try:
119
- response = requests.post(
120
- f"{self.base_url}/completions", headers=self.headers, json=payload
121
- )
122
- response.raise_for_status()
123
- result = response.json()
124
-
125
- # Calculate the cost
126
- input_tokens = result.get("usage", {}).get(
127
- "prompt_tokens", self.count_tokens(prompt)
128
- )
129
- output_tokens = result.get("usage", {}).get("completion_tokens", 0)
130
- input_cost = (input_tokens / 1000) * self.model_config[
131
- "inputPricePer1KTokens"
132
- ]
133
- output_cost = (output_tokens / 1000) * self.model_config[
134
- "outputPricePer1KTokens"
135
- ]
136
- total_cost = input_cost + output_cost
137
-
138
- # Format the response
139
- return self.validate_response(
140
- {
141
- "text": result.get("choices", [{}])[0].get("text", ""),
142
- "cost": total_cost,
143
- "usage": {
144
- "input_tokens": input_tokens,
145
- "output_tokens": output_tokens,
146
- "input_cost": input_cost,
147
- "output_cost": output_cost,
148
- },
149
- "raw_response": result,
150
- }
151
- )
152
-
153
- except requests.exceptions.RequestException as e:
154
- return {
155
- "text": f"Error: {str(e)}",
156
- "cost": 0,
157
- "usage": {
158
- "input_tokens": 0,
159
- "output_tokens": 0,
160
- "input_cost": 0,
161
- "output_cost": 0,
162
- },
163
- "error": str(e),
164
- }
@@ -1,179 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: indoxrouter
3
- Version: 0.1.0
4
- Summary: Client library for IndoxRouter - A unified API for multiple LLM providers
5
- Home-page: https://github.com/yourusername/indoxRouter
6
- Author: Ashkan Eskandari
7
- Author-email: ashkan.eskandari.dev@gmail.com
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.8
12
- Description-Content-Type: text/markdown
13
- Requires-Dist: requests>=2.25.0
14
- Requires-Dist: pyjwt>=2.0.0
15
- Dynamic: author
16
- Dynamic: author-email
17
- Dynamic: classifier
18
- Dynamic: description
19
- Dynamic: description-content-type
20
- Dynamic: home-page
21
- Dynamic: requires-dist
22
- Dynamic: requires-python
23
- Dynamic: summary
24
-
25
- # IndoxRouter
26
-
27
- A unified API for multiple LLM providers, allowing you to switch between different models seamlessly.
28
-
29
- ## Features
30
-
31
- - Support for multiple providers (OpenAI, Anthropic, Mistral, Cohere, Google, Meta, AI21, Llama, NVIDIA, Deepseek, Databricks)
32
- - Unified API for all providers
33
- - User authentication and API key management
34
- - Request logging and monitoring
35
- - Docker support for easy deployment
36
- - Ready for Railway deployment
37
-
38
- ## Quick Start
39
-
40
- ### Local Installation
41
-
42
- 1. Clone the repository:
43
-
44
- ```bash
45
- git clone https://github.com/yourusername/indoxRouter.git
46
- cd indoxRouter
47
- ```
48
-
49
- 2. Create a virtual environment and install dependencies:
50
-
51
- ```bash
52
- python -m venv venv
53
- # On Windows
54
- venv\Scripts\activate
55
- # On Unix or MacOS
56
- source venv/bin/activate
57
- pip install -r requirements.txt
58
- ```
59
-
60
- 3. Initialize the database:
61
-
62
- ```bash
63
- python -m indoxRouter.init_db
64
- ```
65
-
66
- 4. Run the application:
67
-
68
- ```bash
69
- python run.py
70
- ```
71
-
72
- 5. Access the application:
73
- - API: http://localhost:8000
74
- - API Documentation: http://localhost:8000/docs
75
-
76
- ### Docker Installation
77
-
78
- 1. Clone the repository and configure:
79
-
80
- ```bash
81
- git clone https://github.com/yourusername/indoxRouter.git
82
- cd indoxRouter
83
- cp .env.example .env
84
- # Edit .env with your configuration
85
- ```
86
-
87
- 2. Start with Docker Compose:
88
-
89
- ```bash
90
- docker-compose up -d
91
- ```
92
-
93
- 3. Access the application:
94
- - API: http://localhost:8000
95
- - API Documentation: http://localhost:8000/docs
96
- - pgAdmin: http://localhost:5050
97
-
98
- ## Railway Deployment
99
-
100
- IndoxRouter is ready to be deployed on Railway. Follow these steps:
101
-
102
- 1. Fork the repository on GitHub.
103
-
104
- 2. Create a new project on Railway and connect it to your GitHub repository.
105
-
106
- 3. Add the following environment variables in Railway:
107
-
108
- - `DATABASE_URL`: Your PostgreSQL connection string
109
- - `JWT_SECRET`: A secure secret key for JWT tokens
110
- - Provider API keys (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.)
111
-
112
- 4. Railway will automatically detect the Dockerfile and deploy your application.
113
-
114
- 5. Once deployed, you can access your API at the URL provided by Railway.
115
-
116
- ## Detailed Documentation
117
-
118
- For detailed deployment instructions, configuration options, and troubleshooting, please refer to the [Deployment Guide](DEPLOYMENT_GUIDE.md).
119
-
120
- ## API Usage
121
-
122
- ### Authentication
123
-
124
- 1. Register a new user through the API.
125
- 2. Generate an API key for the user.
126
- 3. Use the API key in your requests.
127
-
128
- ### Making Requests
129
-
130
- ```python
131
- import requests
132
-
133
- api_key = "your_api_key"
134
- url = "http://localhost:8000/v1/completions"
135
-
136
- payload = {
137
- "provider": "openai",
138
- "model": "gpt-3.5-turbo",
139
- "prompt": "Hello, world!",
140
- "temperature": 0.7,
141
- "max_tokens": 100
142
- }
143
-
144
- headers = {
145
- "Authorization": f"Bearer {api_key}",
146
- "Content-Type": "application/json"
147
- }
148
-
149
- response = requests.post(url, json=payload, headers=headers)
150
- print(response.json())
151
- ```
152
-
153
- ## Client Library
154
-
155
- IndoxRouter includes a Python client library for easy integration:
156
-
157
- ```python
158
- from indoxRouter.client import Client
159
-
160
- client = Client(api_key="your_api_key")
161
-
162
- response = client.completions(
163
- provider="openai",
164
- model="gpt-3.5-turbo",
165
- prompt="Hello, world!",
166
- temperature=0.7,
167
- max_tokens=100
168
- )
169
-
170
- print(response)
171
- ```
172
-
173
- ## Contributing
174
-
175
- Contributions are welcome! Please feel free to submit a Pull Request.
176
-
177
- ## License
178
-
179
- This project is licensed under the MIT License - see the LICENSE file for details.
@@ -1,27 +0,0 @@
1
- indoxRouter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- indoxRouter/api_endpoints.py,sha256=NffBgwJvMb28WP-Pik1839iX80lXNEcTjIVcFOACJco,12722
3
- indoxRouter/client.py,sha256=twFG3Ft0jdLPLLlrrRxlUKWwflLXGNJzvevpnL0KYSQ,8773
4
- indoxRouter/client_package.py,sha256=KLEYd1EdIjrh6CeC1hyfRW1aSkWsK9MreMfn85sjJcw,4412
5
- indoxRouter/init_db.py,sha256=qGnAelEPLW01on7AuDMAtja9KOFUSxHlau8kAwmcKl4,2233
6
- indoxRouter/main.py,sha256=nKYvE8p00BNIjSid4k_1qUwkyo_PSlfOLuCrJbTt-Co,25210
7
- indoxRouter/migrations/__init__.py,sha256=TL8thhSRiyyy--PU54L7j8YSfXgVwavIZREImgSR87c,34
8
- indoxRouter/migrations/env.py,sha256=ZNU49FMiJedxr0wUBC1B54KABoZxjo7Ganw0tO7b5VE,2942
9
- indoxRouter/migrations/versions/__init__.py,sha256=8xCO-6ARRUyMPDDiRf__k4sgzvrEUEPExy7h9Z9pMYo,43
10
- indoxRouter/migrations/versions/initial_schema.py,sha256=Wg5fJrEG6_KtjtpsHAZ7x3YawbKF6AhUBYXSKBNzK90,2884
11
- indoxRouter/providers/__init__.py,sha256=yDqtgxSnNPtYfHhkvO5TMS3t7bLWARxKJN4pFmZkYKg,2582
12
- indoxRouter/providers/ai21.py,sha256=3Z2TjXp3LVuUWfMMmJSKtgOJbgQf5mPOLAwvltCnh94,9370
13
- indoxRouter/providers/base_provider.py,sha256=Lon3-Z3kC2TfWnY0lG_0UiQMqZRLope8XhpNhpioB8s,1972
14
- indoxRouter/providers/claude.py,sha256=quCNyS6u6nHycoyVeGfieTmz9R2OIcMR8eyuROAsu-8,6068
15
- indoxRouter/providers/cohere.py,sha256=QrY0huHL5Kt-CGsHAkO_wSNdLEVU_3i6NZEuDpUo6yg,5794
16
- indoxRouter/providers/databricks.py,sha256=izq4a7bzS1bEpGdY-DW5WBurq2ibHZjSgAmUr-RohBU,6135
17
- indoxRouter/providers/deepseek.py,sha256=ixYzR0OSlbrZpmZmd0sYlD9dmG3JU2R7h83vrtgcYIo,6127
18
- indoxRouter/providers/google.py,sha256=kWpatz-sIl-Xd_h1o9AdE_rHPvoQBPCUf5XwYPmlvgo,7368
19
- indoxRouter/providers/llama.py,sha256=DsAjanXyy7Ir_B-PQnoFbgs4_oW4aQe1jlfctUcKcmM,6095
20
- indoxRouter/providers/meta.py,sha256=9c8urwygn2363TGErEqpo4Vvj19TV-I0iUHu6_HmBQs,7643
21
- indoxRouter/providers/mistral.py,sha256=E5sT1Pyw9Kl_glvwd437UF72tiWDR9KFEwNHMEWiyaY,6090
22
- indoxRouter/providers/nvidia.py,sha256=QtWP04z_mOSxYveg6BoaXLYG1CxdzsMkcIg68SS2DYQ,6095
23
- indoxRouter/providers/openai.py,sha256=qKbsmsSW8WvCxC7SCkNV0KwhV6PXV_DDJjr7BDkBfVg,3928
24
- indoxrouter-0.1.0.dist-info/METADATA,sha256=jSp9kBgAuVW5a3tB7cDSb2CUHZxji0EMocNmWZABgUo,4254
25
- indoxrouter-0.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
26
- indoxrouter-0.1.0.dist-info/top_level.txt,sha256=m_uFNrFxPIq7tGC2XYm2tw2Xx3cxEAkv1l_4lH-RmlQ,12
27
- indoxrouter-0.1.0.dist-info/RECORD,,