├── omnidim_mcp_server ├── README.md ├── __init__.py ├── .python-version ├── pyproject.toml ├── main.py └── uv.lock ├── .DS_Store ├── omnidimension ├── __init__.py ├── PhoneNumber │ └── __init__.py ├── Call │ └── __init__.py ├── Agent │ └── __init__.py ├── KnowledgeBase │ └── __init__.py ├── BulkCall │ └── __init__.py ├── Providers │ ├── __init__.py │ └── README.md ├── client.py ├── Simulation │ └── __init__.py └── Integrations │ └── __init__.py ├── requirements.txt ├── omnidim_mcp.json ├── setup.py ├── LICENSE ├── .gitignore ├── README.md └── example.py /omnidim_mcp_server/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /omnidim_mcp_server/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /omnidim_mcp_server/.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omnidim/omnidim-python-sdk/HEAD/.DS_Store -------------------------------------------------------------------------------- /omnidimension/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import Client 2 | from .client import APIError 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | fastapi>=0.95.0 3 | uvicorn>=0.21.0 4 | fastmcp>=0.1.0 5 | pydantic>=1.10.0 -------------------------------------------------------------------------------- /omnidim_mcp_server/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "omnidimension mcp server" 3 | version = "0.1.0" 4 | description = "omnidimension create your voice AI assistant" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "fastmcp>=2.3.3", 9 | "httpx>=0.28.1", 10 | "mcp[cli]>=1.8.1", 11 | "omnidimension>=0.2.4", 12 | ] 13 | -------------------------------------------------------------------------------- /omnidim_mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "Omnidimension": { 4 | "command": "uvx", 5 | "args": [ 6 | "--no-cache", 7 | "--with", 8 | "fastmcp", 9 | "--from", 10 | "omnidimension", 11 | "omnidim-mcp-server" 12 | ], 13 | "env": { 14 | "OMNIDIMENSION_API_KEY": "" 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="omnidimension", 5 | version="0.2.17", 6 | packages=find_packages() + ["omnidim_mcp_server"], 7 | install_requires=["requests"], 8 | extras_require={ 9 | "mcp": ["fastapi>=0.95.0", "uvicorn>=0.21.0", "fastmcp>=0.1.0", "pydantic>=1.10.0"] 10 | }, 11 | entry_points={ 12 | "console_scripts": [ 13 | "omnidim-mcp-server=omnidim_mcp_server.main:create_app", 14 | ], 15 | }, 16 | author="https://www.omnidim.io/", 17 | author_email="kevin@omnidim.io", 18 | description="SDK and MCP Server for Omni Assistant services", 19 | long_description=open("README.md").read(), 20 | long_description_content_type="text/markdown", 21 | url="https://github.com/kevin-omnidim/omnidim-sdk", 22 | classifiers=[ 23 | "Programming Language :: Python :: 3", 24 | "License :: OSI Approved :: MIT License", 25 | "Operating System :: OS Independent", 26 | ], 27 | python_requires=">=3.6", 28 | ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 kevin-omnidim 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. 22 | -------------------------------------------------------------------------------- /omnidimension/PhoneNumber/__init__.py: -------------------------------------------------------------------------------- 1 | class PhoneNumber(): 2 | def __init__(self, client): 3 | """ 4 | Initialize the PhoneNumber client with a reference to the main API client. 5 | 6 | Args: 7 | client: The main API client instance. 8 | """ 9 | self.client = client 10 | 11 | def list(self, page=1, page_size=30): 12 | """ 13 | Get all phone numbers for the authenticated user. 14 | 15 | Args: 16 | page (int): Page number for pagination (default: 1). 17 | page_size (int): Number of items per page (default: 30). 18 | 19 | Returns: 20 | dict: Response containing the list of phone numbers. 21 | """ 22 | params = { 23 | 'pageno': page, 24 | 'pagesize': page_size 25 | } 26 | return self.client.get("phone_number/list", params=params) 27 | 28 | def attach(self, phone_number_id, agent_id): 29 | """ 30 | Attach a phone number to an agent. 31 | 32 | Args: 33 | phone_number_id (int): ID of the phone number to attach. 34 | agent_id (int): ID of the agent to attach the phone number to. 35 | 36 | Returns: 37 | dict: Response indicating success or failure. 38 | 39 | Raises: 40 | ValueError: If phone_number_id or agent_id is not an integer. 41 | """ 42 | if not isinstance(phone_number_id, int): 43 | raise ValueError("phone_number_id must be an integer.") 44 | if not isinstance(agent_id, int): 45 | raise ValueError("agent_id must be an integer.") 46 | 47 | data = { 48 | "phone_number_id": phone_number_id, 49 | "agent_id": agent_id 50 | } 51 | 52 | return self.client.post("phone_number/attach", data=data) 53 | 54 | def detach(self, phone_number_id): 55 | """ 56 | Detach a phone number from any agent it's attached to. 57 | 58 | Args: 59 | phone_number_id (int): ID of the phone number to detach. 60 | 61 | Returns: 62 | dict: Response indicating success or failure. 63 | 64 | Raises: 65 | ValueError: If phone_number_id is not an integer. 66 | """ 67 | if not isinstance(phone_number_id, int): 68 | raise ValueError("phone_number_id must be an integer.") 69 | 70 | data = { 71 | "phone_number_id": phone_number_id 72 | } 73 | 74 | return self.client.post("phone_number/detach", data=data) -------------------------------------------------------------------------------- /omnidim_mcp_server/main.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | from re import T 4 | from typing import Optional, Dict, List, TypedDict 5 | 6 | from fastmcp import FastMCP 7 | from omnidimension import Client 8 | import os 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | mcp = FastMCP(name="Omnidimension MCP Server") 13 | 14 | class TContextBreakdown(TypedDict): 15 | title: str 16 | body: str 17 | 18 | 19 | @mcp.tool(description="for creating a new assistant") 20 | def dispatch_a_call(assistant_id: int, to_number: str, call_context: dict) -> Dict: 21 | """ 22 | Dispatch a call to given number 23 | params: 24 | assistant_id: id of the assistant 25 | to_number: a valid phone number 26 | call_context: a dict which contain information about whom we are calling to and purpose. like { 'caller_name': 'John Doe', 'purpose': 'appointment' } 27 | Returns: 28 | - success 29 | """ 30 | return get_client().call.dispatch_call(agent_id=assistant_id, call_context=call_context, to_number=to_number) 31 | 32 | 33 | @mcp.tool(description="for creating a new assistant") 34 | def create_assistant(name: str, context_breakdown: List[TContextBreakdown], welcome_message: str) -> Dict: 35 | """ 36 | params: 37 | assistant_id: id of the assistant 38 | context_breakdown: 39 | [{ 40 | title: title of section, 41 | body: prompt for assistant for this section. 42 | }] 43 | welcome_message: first message that played when agent called 44 | Returns: 45 | - details of the assistant 46 | """ 47 | return get_client().agent.create(name=name, context_breakdown=context_breakdown,welcome_message=welcome_message) 48 | 49 | 50 | @mcp.resource("assistants://{page}/{page_size}") 51 | def get_all_assistants(page=1, page_size=20) -> Dict: 52 | """Get a All available assistants 53 | 54 | params: 55 | page: number of page. 56 | page_size: number of assistant per page. 57 | 58 | Returns: 59 | - list of assistants 60 | """ 61 | return get_client().agent.list(page=page, page_size=page_size) 62 | 63 | @mcp.resource("assistants://{assistant_id}") 64 | def get_assistant_details(assistant_id = int) -> Dict: 65 | """Get assistant's details 66 | 67 | params: 68 | assistant_id: id of the assistant 69 | Returns: 70 | - details of the assistant 71 | """ 72 | return get_client().agent.get(agent_id=assistant_id) 73 | 74 | @mcp.resource("greeting://{name}") 75 | def get_greeting(name: str) -> str: 76 | """Get a personalized greeting""" 77 | return f"Hello, {name}!" 78 | 79 | 80 | def get_client() -> Client: 81 | API_KEY = os.getenv(f"OMNIDIMENSION_API_KEY") 82 | return Client(api_key=API_KEY) 83 | 84 | 85 | def create_app(): 86 | mcp.run(transport='stdio') -------------------------------------------------------------------------------- /omnidimension/Call/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | class Call(): 3 | def __init__(self, client): 4 | """ 5 | Initialize the Agent client with a reference to the main API client. 6 | 7 | Args: 8 | client: The main API client instance. 9 | """ 10 | self.client = client 11 | 12 | def dispatch_call(self, agent_id, to_number, from_number_id=None, call_context={}): 13 | """ 14 | Dispatch a call to agent with the provided call context. 15 | 16 | Args: 17 | agent_id (int): id for the agent. 18 | to_number (string): valid phone number with country code. 19 | from_number_id(number)(optional): valid id of imported phone number api/v1/phone_number/list? 20 | call_context (dict): call context to be passed to agent during call. 21 | 22 | Returns: 23 | dict: Response from the API containing success. 24 | 25 | Raises: 26 | ValueError: If required fields are missing or invalid. 27 | """ 28 | # Validate required inputs 29 | if not isinstance(agent_id, int): 30 | raise ValueError("agent id must be a integer.") 31 | if from_number_id and not isinstance(from_number_id, int): 32 | raise ValueError("from number id must be a integer.") 33 | if not isinstance(to_number, str) and to_number[0] != '+': 34 | raise ValueError("To Number must be a valid number and starts with + and country code.") 35 | 36 | data = { 37 | "agent_id": agent_id, 38 | "to_number": to_number, 39 | "call_context": call_context, 40 | "from_number_id": from_number_id, 41 | } 42 | 43 | return self.client.post("calls/dispatch", data=data) 44 | 45 | 46 | def get_call_logs(self, page=1, page_size=30, agent_id=None, call_status=None): 47 | """ 48 | Get all call logs for the authenticated user. 49 | 50 | Args: 51 | page (int): Page number for pagination (default: 1). 52 | page_size (int): Number of items per page (default: 30). 53 | agent_id (int): Filter by agent ID (optional). 54 | call_status (string): Filter by call status (optional). 55 | Returns: 56 | dict: Response containing the list of call logs . 57 | """ 58 | params = { 59 | 'pageno': page, 60 | 'pagesize': page_size, 61 | 'agentid': agent_id, 62 | 'call_status': call_status, 63 | } 64 | return self.client.get("calls/logs", params=params) 65 | 66 | def get_call_log(self, call_log_id): 67 | """ 68 | Get a specific agent by ID. 69 | 70 | Args: 71 | agent_id (int): The ID of the agent to retrieve. 72 | 73 | Returns: 74 | dict: Response containing the call log details. 75 | """ 76 | return self.client.get(f"calls/logs/{call_log_id}") 77 | -------------------------------------------------------------------------------- /omnidimension/Agent/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | class Agent(): 3 | def __init__(self, client): 4 | """ 5 | Initialize the Agent client with a reference to the main API client. 6 | 7 | Args: 8 | client: The main API client instance. 9 | """ 10 | self.client = client 11 | 12 | def list(self, page=1, page_size=30): 13 | """ 14 | Get all agents for the authenticated user. 15 | 16 | Args: 17 | page (int): Page number for pagination (default: 1). 18 | page_size (int): Number of items per page (default: 30). 19 | 20 | Returns: 21 | dict: Response containing the list of agents. 22 | """ 23 | params = { 24 | 'pageno': page, 25 | 'pagesize': page_size 26 | } 27 | return self.client.get("agents", params=params) 28 | 29 | def get(self, agent_id): 30 | """ 31 | Get a specific agent by ID. 32 | 33 | Args: 34 | agent_id (int): The ID of the agent to retrieve. 35 | 36 | Returns: 37 | dict: Response containing the agent details. 38 | """ 39 | return self.client.get(f"agents/{agent_id}") 40 | 41 | def create(self, name, context_breakdown, **kwargs): 42 | """ 43 | Create a custom agent with the provided configuration and optional parameters. 44 | 45 | Args: 46 | name (str): name for the agent. 47 | context_breakdown (list): List of context breakdowns, each containing 48 | 'title' and 'body'. 49 | **kwargs: Additional optional parameters to include in the API request. 50 | 51 | Returns: 52 | dict: Response from the API containing agent details. 53 | 54 | Raises: 55 | ValueError: If required fields are missing or invalid. 56 | """ 57 | # Validate required inputs 58 | if not isinstance(name, str): 59 | raise ValueError("name must be a string.") 60 | if not isinstance(context_breakdown, list) or not all( 61 | isinstance(context, dict) and 'title' in context and 'body' in context 62 | for context in context_breakdown 63 | ): 64 | raise ValueError( 65 | "context_breakdown must be a list of dictionaries with 'title' and 'body'." 66 | ) 67 | 68 | # Prepare the data payload 69 | data = { 70 | "name": name, 71 | "context_breakdown": context_breakdown, 72 | **kwargs # Include any additional parameters 73 | } 74 | 75 | return self.client.post("agents/create", data=data) 76 | 77 | def update(self, agent_id, data): 78 | """ 79 | Update an existing agent. 80 | 81 | Args: 82 | agent_id (int): The ID of the agent to update. 83 | data (dict): The updated agent data. 84 | 85 | Returns: 86 | dict: Response containing the updated agent details. 87 | """ 88 | return self.client.put(f"agents/{agent_id}", data=data) 89 | 90 | def delete(self, agent_id): 91 | """ 92 | Delete an agent. 93 | 94 | Args: 95 | agent_id (int): The ID of the agent to delete. 96 | 97 | Returns: 98 | dict: Response indicating success or failure. 99 | """ 100 | return self.client.delete(f"agents/{agent_id}") 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # Ruff stuff: 171 | .ruff_cache/ 172 | 173 | # PyPI configuration file 174 | .pypirc 175 | -------------------------------------------------------------------------------- /omnidimension/KnowledgeBase/__init__.py: -------------------------------------------------------------------------------- 1 | class KnowledgeBase(): 2 | def __init__(self, client): 3 | """ 4 | Initialize the KnowledgeBase client with a reference to the main API client. 5 | 6 | Args: 7 | client: The main API client instance. 8 | """ 9 | self.client = client 10 | 11 | def list(self): 12 | """ 13 | Get all knowledge base files for the authenticated user. 14 | 15 | Returns: 16 | dict: Response containing the list of files. 17 | """ 18 | return self.client.get("knowledge_base/list") 19 | 20 | def create(self, file_data, filename): 21 | """ 22 | Upload a file to the knowledge base. 23 | 24 | Args: 25 | file_data (str): Base64 encoded file content. 26 | filename (str): Name of the file (must end with .pdf). 27 | 28 | Returns: 29 | dict: Response containing the created file details. 30 | 31 | Raises: 32 | ValueError: If the file is not a PDF. 33 | """ 34 | # Validate file is a PDF 35 | if not filename.lower().endswith('.pdf'): 36 | raise ValueError("Only PDF files are supported.") 37 | 38 | data = { 39 | "file": file_data, 40 | "filename": filename 41 | } 42 | 43 | return self.client.post("knowledge_base/create", data=data) 44 | 45 | def can_upload(self, file_size, file_type="pdf"): 46 | """ 47 | Validate if a file can be uploaded based on size and type. 48 | 49 | Args: 50 | file_size (int): Size of the file in bytes. 51 | file_type (str): Type of the file (only 'pdf' is supported). 52 | 53 | Returns: 54 | dict: Response containing validation result and quota information. 55 | """ 56 | data = { 57 | "file_size": file_size, 58 | "file_type": file_type 59 | } 60 | 61 | return self.client.post("knowledge_base/can_upload", data=data) 62 | 63 | def delete(self, file_id): 64 | """ 65 | Delete a file from the knowledge base. 66 | 67 | Args: 68 | file_id (int): ID of the file to delete. 69 | 70 | Returns: 71 | dict: Response indicating success or failure. 72 | """ 73 | data = { 74 | "file_id": file_id 75 | } 76 | 77 | return self.client.post("knowledge_base/delete", data=data) 78 | 79 | def attach(self, file_ids, agent_id, when_to_use=None): 80 | """ 81 | Attach multiple files to an agent. 82 | 83 | Args: 84 | file_ids (list): List of file IDs to attach. 85 | agent_id (int): ID of the agent to attach files to. 86 | when_to_use: when to use this files 87 | 88 | Returns: 89 | dict: Response indicating success or failure. 90 | 91 | Raises: 92 | ValueError: If file_ids is not a list or agent_id is not an integer. 93 | """ 94 | if not isinstance(file_ids, list): 95 | raise ValueError("file_ids must be a list of integers.") 96 | if not isinstance(agent_id, int): 97 | raise ValueError("agent_id must be an integer.") 98 | 99 | data = { 100 | "file_ids": file_ids, 101 | "agent_id": agent_id, 102 | "when_to_use": when_to_use 103 | } 104 | 105 | return self.client.post("knowledge_base/attach", data=data) 106 | 107 | def detach(self, file_ids, agent_id): 108 | """ 109 | Detach multiple files from an agent. 110 | 111 | Args: 112 | file_ids (list): List of file IDs to detach. 113 | agent_id (int): ID of the agent to detach files from. 114 | 115 | Returns: 116 | dict: Response indicating success or failure. 117 | 118 | Raises: 119 | ValueError: If file_ids is not a list or agent_id is not an integer. 120 | """ 121 | if not isinstance(file_ids, list): 122 | raise ValueError("file_ids must be a list of integers.") 123 | if not isinstance(agent_id, int): 124 | raise ValueError("agent_id must be an integer.") 125 | 126 | data = { 127 | "file_ids": file_ids, 128 | "agent_id": agent_id 129 | } 130 | 131 | return self.client.post("knowledge_base/detach", data=data) -------------------------------------------------------------------------------- /omnidimension/BulkCall/__init__.py: -------------------------------------------------------------------------------- 1 | class BulkCall(): 2 | def __init__(self, client): 3 | """ 4 | Initialize the BulkCall client with a reference to the main API client. 5 | 6 | Args: 7 | client: The main API client instance. 8 | """ 9 | self.client = client 10 | 11 | def fetch_bulk_calls(self, page=1, page_size=10, status=None): 12 | """ 13 | Fetch all bulk calls with optional filtering and pagination. 14 | 15 | Args: 16 | page (int): Page number for pagination (default: 1). 17 | page_size (int): Number of items per page (default: 10). 18 | status (str): Filter by bulk call status (optional). 19 | 20 | Returns: 21 | dict: Response containing the list of bulk calls. 22 | """ 23 | params = { 24 | 'pageno': page, 25 | 'pagesize': page_size 26 | } 27 | if status: 28 | params['status'] = status 29 | 30 | return self.client.get("calls/bulk_call", params=params) 31 | 32 | def create_bulk_calls(self, name, contact_list, phone_number_id, 33 | is_scheduled=False, scheduled_datetime=None, timezone='UTC', 34 | retry_config=None, enabled_reschedule_call=False): 35 | """ 36 | Create a new bulk call campaign. 37 | 38 | Args: 39 | name (str): Name of the bulk call campaign. 40 | contact_list (list): List of contact dictionaries with phone_number and extra_data. 41 | phone_number_id (int): ID of the phone number to use for the calls. 42 | is_scheduled (bool): Whether the call is scheduled for later (default: False). 43 | scheduled_datetime (str): Scheduled datetime in format "YYYY-MM-DD HH:MM:SS" (required if is_scheduled=True). 44 | timezone (str): Timezone for the scheduled datetime (default: 'UTC'). 45 | retry_config (dict): Auto-retry configuration with keys: auto_retry, auto_retry_schedule, retry_schedule_days, retry_schedule_hours, retry_limit. 46 | enabled_reschedule_call (bool): Enable call rescheduling (default: False). 47 | 48 | Returns: 49 | dict: Response containing the created bulk call details. 50 | 51 | Raises: 52 | ValueError: If required fields are missing or invalid. 53 | """ 54 | # Validate required inputs 55 | if not isinstance(name, str) or not name.strip(): 56 | raise ValueError("name must be a non-empty string") 57 | if not isinstance(contact_list, list) or not contact_list: 58 | raise ValueError("contact_list must be a non-empty list") 59 | if not isinstance(phone_number_id, int): 60 | raise ValueError("phone_number_id must be an integer") 61 | 62 | # Validate contact list format 63 | for i, contact in enumerate(contact_list): 64 | if not isinstance(contact, dict): 65 | raise ValueError(f"contact_list[{i}] must be a dictionary") 66 | if 'phone_number' not in contact: 67 | raise ValueError(f"contact_list[{i}] must contain 'phone_number' field") 68 | if not isinstance(contact['phone_number'], str) or not contact['phone_number'].startswith('+'): 69 | raise ValueError(f"contact_list[{i}]['phone_number'] must be a string starting with '+'") 70 | 71 | if is_scheduled and not scheduled_datetime: 72 | raise ValueError("scheduled_datetime is required when is_scheduled is True") 73 | 74 | data = { 75 | 'name': name, 76 | 'contact_list': contact_list, 77 | 'phone_number_id': phone_number_id, 78 | 'is_scheduled': is_scheduled, 79 | 'timezone': timezone, 80 | 'enabled_reschedule_call': enabled_reschedule_call 81 | } 82 | 83 | if is_scheduled: 84 | data['scheduled_datetime'] = scheduled_datetime 85 | 86 | if retry_config: 87 | data['retry_config'] = retry_config 88 | 89 | return self.client.post("calls/bulk_call/create", data=data) 90 | 91 | def bulk_calls_actions(self, bulk_call_id, action, new_timezone=None, new_scheduled_datetime=None): 92 | """ 93 | Perform actions on a bulk call (pause, resume, reschedule). 94 | 95 | Args: 96 | bulk_call_id (int): ID of the bulk call to modify. 97 | action (str): Action to perform ('pause', 'resume', or 'reschedule'). 98 | new_timezone (str): New timezone for reschedule action (optional). 99 | new_scheduled_datetime (str): New scheduled datetime for reschedule action (required for reschedule). 100 | 101 | Returns: 102 | dict: Response containing the action result. 103 | 104 | Raises: 105 | ValueError: If required fields are missing or invalid. 106 | """ 107 | if action not in ['pause', 'resume', 'reschedule']: 108 | raise ValueError("action must be 'pause', 'resume', or 'reschedule'") 109 | 110 | if action == 'reschedule' and not new_scheduled_datetime: 111 | raise ValueError("new_scheduled_datetime is required for reschedule action") 112 | 113 | data = { 114 | 'action': action 115 | } 116 | 117 | if new_timezone: 118 | data['new_timezone'] = new_timezone 119 | if new_scheduled_datetime: 120 | data['new_scheduled_datetime'] = new_scheduled_datetime 121 | 122 | return self.client.put(f"calls/bulk_call/{bulk_call_id}", data=data) 123 | 124 | def cancel_bulk_calls(self, bulk_call_id): 125 | """ 126 | Cancel a bulk call campaign. 127 | 128 | Args: 129 | bulk_call_id (int): ID of the bulk call to cancel. 130 | 131 | Returns: 132 | dict: Response containing the cancellation result. 133 | """ 134 | return self.client.delete(f"calls/bulk_call/{bulk_call_id}") 135 | 136 | def detail_bulk_calls(self, bulk_call_id): 137 | """ 138 | Get detailed information about a specific bulk call campaign. 139 | 140 | Args: 141 | bulk_call_id (int): ID of the bulk call to retrieve. 142 | 143 | Returns: 144 | dict: Response containing the bulk call details and contact list. 145 | """ 146 | return self.client.get(f"calls/bulk_call/{bulk_call_id}") 147 | -------------------------------------------------------------------------------- /omnidimension/Providers/__init__.py: -------------------------------------------------------------------------------- 1 | from ..client import APIError 2 | 3 | class Providers: 4 | """ 5 | Client for interacting with the OmniDim Providers API endpoints. 6 | """ 7 | 8 | def __init__(self, client): 9 | """ 10 | Initialize the Providers client with a reference to the main API client. 11 | 12 | Args: 13 | client: The main API client instance. 14 | """ 15 | self.client = client 16 | 17 | def list_llms(self): 18 | """ 19 | Fetch all LLM providers. 20 | 21 | Returns: 22 | dict: Response containing the list of LLM providers with 'llms' and 'total' keys. 23 | """ 24 | response = self.client.get("providers/llms") 25 | return response['json'] 26 | 27 | def list_voices(self, provider=None, search=None, language=None, accent=None, 28 | gender=None, page=1, page_size=30): 29 | """ 30 | Fetch all voice providers with advanced filtering and pagination. 31 | 32 | Args: 33 | provider (str): Filter by specific TTS provider (e.g., 'eleven_labs', 'google', 'deepgram'). 34 | Only 'eleven_labs' supports advanced filtering (search, language, accent, gender). 35 | search (str): Search term for voice name/description (ElevenLabs only). 36 | language (str): Language filter (ElevenLabs only). 37 | accent (str): Accent filter (ElevenLabs only). 38 | gender (str): Gender filter ('male' or 'female', ElevenLabs only). 39 | page (int): Page number for pagination (default: 1). 40 | page_size (int): Number of items per page (default: 30, max: 100). 41 | 42 | Returns: 43 | dict: Response containing the list of voices with 'voices', 'total', 'page', 44 | 'page_size', and 'filters_applied' keys. 45 | """ 46 | # basic parameter validation for immediate feedback 47 | if not isinstance(page, int) or page < 1: 48 | raise ValueError("page must be a positive integer (1 or greater)") 49 | if not isinstance(page_size, int) or page_size < 1 or page_size > 100: 50 | raise ValueError("page_size must be an integer between 1 and 100") 51 | if gender and gender not in ['male', 'female']: 52 | raise ValueError("gender must be 'male' or 'female'") 53 | 54 | # client-side validation for filter limitations 55 | if provider and provider != 'eleven_labs': 56 | advanced_filters = [search, language, accent, gender] 57 | if any(filter_val is not None for filter_val in advanced_filters): 58 | raise ValueError( 59 | f"Advanced filtering (search, language, accent, gender) is only supported " 60 | f"for provider 'eleven_labs'. Provider '{provider}' only supports pagination." 61 | ) 62 | 63 | params = {} 64 | 65 | # pagination parameters (integers) 66 | params['page'] = page 67 | params['page_size'] = page_size 68 | 69 | # optional string parameters 70 | if provider: 71 | params['provider'] = provider 72 | if search: 73 | params['search'] = search 74 | if language: 75 | params['language'] = language 76 | if accent: 77 | params['accent'] = accent 78 | if gender: 79 | params['gender'] = gender 80 | 81 | # let backend handle validation of unknown providers, invalid languages, etc. 82 | # but catch common user-facing issues here 83 | 84 | try: 85 | response = self.client.get("providers/voices", params=params) 86 | return response['json'] 87 | except APIError as e: 88 | # handle rate limiting 89 | if e.status_code == 429: 90 | retry_after = getattr(e, 'response', {}).get('retry_after', 60) 91 | raise ValueError(f"Rate limit exceeded. Please wait {retry_after} seconds before trying again.") 92 | # handle authentication errors 93 | elif e.status_code == 401: 94 | raise ValueError("Authentication failed. Please check your API key.") 95 | # handle not found errors 96 | elif e.status_code == 404: 97 | raise ValueError("The requested provider endpoint was not found.") 98 | # let other API errors fill up with backend's message 99 | else: 100 | raise ValueError(f"API Error: {e.message}") 101 | 102 | def list_tts(self): 103 | """ 104 | Fetch all TTS providers. 105 | 106 | Returns: 107 | dict: Response containing the list of TTS providers with 'tts' and 'total' keys. 108 | """ 109 | response = self.client.get("providers/tts") 110 | return response['json'] 111 | 112 | def list_stt(self): 113 | """ 114 | Fetch all STT providers. 115 | 116 | Returns: 117 | dict: Response containing the list of STT providers with 'stt' and 'total' keys. 118 | """ 119 | response = self.client.get("providers/stt") 120 | return response['json'] 121 | 122 | def list_all(self): 123 | """ 124 | Fetch all providers (services and voices) in a comprehensive response. 125 | 126 | Returns: 127 | dict: Response containing all provider types with 'services', 'voices', 128 | 'total_services', and 'total_voices' keys. 129 | """ 130 | response = self.client.get("providers/all") 131 | return response['json'] 132 | 133 | def get_voice(self, voice_id): 134 | """ 135 | Get detailed information about a specific voice. 136 | 137 | Args: 138 | voice_id (int): ID of the voice to retrieve. 139 | 140 | Returns: 141 | dict: Response containing the voice details. 142 | """ 143 | if not isinstance(voice_id, int) or voice_id < 1: 144 | raise ValueError("voice_id must be a positive integer") 145 | 146 | try: 147 | response = self.client.get(f"providers/voice/{voice_id}") 148 | return response['json'] 149 | except APIError as e: 150 | # handle rate limiting 151 | if e.status_code == 429: 152 | retry_after = getattr(e, 'response', {}).get('retry_after', 60) 153 | raise ValueError(f"Rate limit exceeded. Please wait {retry_after} seconds before trying again.") 154 | # handle authentication errors 155 | elif e.status_code == 401: 156 | raise ValueError("Authentication failed. Please check your API key.") 157 | # handle not found errors 158 | elif e.status_code == 404: 159 | raise ValueError(f"Voice with ID {voice_id} was not found.") 160 | # let other API errors fill up with backend's message 161 | else: 162 | raise ValueError(f"API Error: {e.message}") 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # OmniDimension Python SDK 3 | 4 | *Build and ship AI voice agents from a single prompt.* 5 | 6 | OmniDimension lets you **build, test, and deploy** reliable voice AI assistants by simply describing them in plain text. The platform offers **rigorous simulation testing** and **real-time observability**, making it easy to debug and monitor agents in production. 7 | 8 | 👉 [Try the Web UI](https://www.omnidim.io/) — You can also build and test voice agents visually using our no-code interface. 9 | 10 | --- 11 | 12 | ## 🚀 Features 13 | 14 | - **Prompt-based creation:** Define voice agents with natural language. 15 | - **Drag-and-drop editor:** Chat or visually edit flows, voices, models, and more. 16 | - **Prebuilt templates:** Use plug-and-play agent templates for common use cases. 17 | - **Testing & monitoring:** Simulate edge cases and debug live calls. 18 | - **Knowledge Base support:** Upload and attach documents (PDFs) for factual grounding. 19 | - **Integrations:** Connect to external APIs, CRMs, or tools like Cal.com. 20 | - **Phone agents:** Assign numbers and initiate real voice calls via the SDK. 21 | 22 | --- 23 | 24 | ## 📦 Installation 25 | 26 | ### Basic SDK 27 | 28 | ```bash 29 | pip install omnidimension 30 | ```` 31 | 32 | ### With MCP Server Support 33 | 34 | ```bash 35 | pip install omnidimension[mcp] 36 | ``` 37 | 38 | > Requires Python 3.9+ 39 | 40 | --- 41 | 42 | ## 🔐 Authentication 43 | 44 | First, obtain your API key from the OmniDimension dashboard. Store it in your environment variables: 45 | 46 | ### Linux/macOS 47 | 48 | ```bash 49 | export OMNIDIM_API_KEY="your_api_key_here" 50 | ``` 51 | 52 | ### Windows (CMD) 53 | 54 | ```cmd 55 | set OMNIDIM_API_KEY=your_api_key_here 56 | ``` 57 | 58 | ### In Python 59 | 60 | ```python 61 | import os 62 | from omnidimension import Client 63 | 64 | api_key = os.environ.get("OMNIDIM_API_KEY") 65 | client = Client(api_key) 66 | ``` 67 | 68 | --- 69 | 70 | ## ✨ SDK Usage 71 | 72 | ```python 73 | from omnidimension import Client 74 | 75 | # Initialize the client with your API key 76 | client = Client(api_key="your_api_key") 77 | 78 | # List agents 79 | agents = client.agent.list() 80 | print(agents) 81 | ``` 82 | 83 | --- 84 | 85 | ## 🛰️ MCP Server Usage 86 | 87 | You can run the MCP server in several ways: 88 | 89 | ### 1. Using the Python module: 90 | 91 | ```bash 92 | python -m omnidimension.mcp_server --api-key your_api_key 93 | ``` 94 | 95 | ### 2. Using the CLI entry point: 96 | 97 | ```bash 98 | omnidim-mcp-server --api-key your_api_key 99 | ``` 100 | 101 | ### 3. Using the compatibility module (for MCP clients): 102 | 103 | ```bash 104 | python -m omnidim_mcp_server --api-key your_api_key 105 | ``` 106 | 107 | You can also set the API key using the environment variable: 108 | 109 | ```bash 110 | export OMNIDIM_API_KEY=your_api_key 111 | python -m omnidimension.mcp_server 112 | ``` 113 | 114 | --- 115 | 116 | ## ⚙️ MCP Client Configuration 117 | 118 | To use OmniDimension with MCP clients like Claude Desktop, save the following configuration to a file named `omnidim_mcp.json`: 119 | 120 | ```json 121 | { 122 | "mcpServers": { 123 | "omnidim-mcp-server": { 124 | "command": "python3", 125 | "args": [ 126 | "-m", 127 | "omnidim_mcp_server" 128 | ], 129 | "env": { 130 | "OMNIDIM_API_KEY": "" 131 | } 132 | } 133 | } 134 | } 135 | ``` 136 | 137 | --- 138 | 139 | ## 📡 Providers API 140 | 141 | The Providers API allows you to discover and explore all available AI providers for LLMs, voices, STT, and TTS services. 142 | 143 | ### Quick Examples 144 | 145 | ```python 146 | # Get all LLM providers 147 | llms = client.providers.list_llms() 148 | print(f"[SUCCESS] Found {llms['total']} LLM providers") 149 | 150 | # List voices with pagination 151 | voices = client.providers.list_voices(page=1, page_size=50) 152 | print(f"[SUCCESS] Found {voices['total']} voices") 153 | ``` 154 | 155 | > ![INFO] 156 | > **[Complete Providers Documentation](omnidimension/Providers/README.md)** 157 | 158 | --- 159 | 160 | ## 📚 Knowledge Base 161 | 162 | ```python 163 | files = client.knowledge_base.list() 164 | print(files) 165 | 166 | file_ids = [123] 167 | agent_id = 456 168 | response = client.knowledge_base.attach(file_ids, agent_id) 169 | print(response) 170 | ``` 171 | 172 | --- 173 | 174 | ## 🔌 Integrations 175 | 176 | ```python 177 | response = client.integrations.create_custom_api_integration( 178 | name="WeatherAPI", 179 | url="https://api.example.com/weather", 180 | method="GET" 181 | ) 182 | print(response) 183 | 184 | client.integrations.add_integration_to_agent(agent_id=123, integration_id=789) 185 | ``` 186 | 187 | --- 188 | 189 | ## ☎️ Phone Number Management 190 | 191 | ```python 192 | numbers = client.phone_number.list(page=1, page_size=10) 193 | print(numbers) 194 | 195 | client.phone_number.attach(phone_number_id=321, agent_id=123) 196 | ``` 197 | 198 | --- 199 | 200 | ## 📞 Bulk Call Management 201 | 202 | ```python 203 | # First, get your phone number IDs 204 | phone_numbers = client.phone_number.list() 205 | phone_number_id = phone_numbers['phone_numbers'][0]['id'] # Use the first available phone number 206 | 207 | # Create a bulk call campaign 208 | contact_list = [ 209 | { 210 | "phone_number": "+1234567890", 211 | "customer_name": "John Doe", 212 | "product_interest": "Premium Plan" 213 | }, 214 | { 215 | "phone_number": "+1987654321", 216 | "customer_name": "Jane Smith", 217 | "product_interest": "Basic Plan" 218 | } 219 | ] 220 | 221 | # Create immediate bulk call 222 | bulk_call = client.bulk_call.create_bulk_calls( 223 | name="Marketing Campaign - Q4 2024", 224 | contact_list=contact_list, 225 | phone_number_id=phone_number_id 226 | ) 227 | 228 | # Create scheduled bulk call 229 | scheduled_call = client.bulk_call.create_bulk_calls( 230 | name="Scheduled Campaign", 231 | contact_list=contact_list, 232 | phone_number_id=phone_number_id, 233 | is_scheduled=True, 234 | scheduled_datetime="2024-12-25 10:00:00", 235 | timezone="America/New_York" 236 | ) 237 | 238 | # Fetch all bulk calls 239 | bulk_calls = client.bulk_call.fetch_bulk_calls(page=1, page_size=10, status="active") 240 | 241 | # Get bulk call details 242 | details = client.bulk_call.detail_bulk_calls(bulk_call_id=123) 243 | 244 | # Control bulk call actions 245 | client.bulk_call.bulk_calls_actions(bulk_call_id=123, action="pause") 246 | client.bulk_call.bulk_calls_actions(bulk_call_id=123, action="resume") 247 | client.bulk_call.bulk_calls_actions( 248 | bulk_call_id=123, 249 | action="reschedule", 250 | new_scheduled_datetime="2024-12-26 14:00:00", 251 | new_timezone="America/New_York" 252 | ) 253 | 254 | # Cancel bulk call 255 | client.bulk_call.cancel_bulk_calls(bulk_call_id=123) 256 | ``` 257 | 258 | --- 259 | 260 | ## 📁 Recommended Project Structure 261 | 262 | ``` 263 | /docs/ 264 | ├── agents/ 265 | ├── calling/ 266 | ├── integrations/ 267 | ├── knowledge_base/ 268 | └── phone_numbers/ 269 | 270 | /examples/ # Sample Python scripts 271 | /cookbook/ # Ready-made project use cases 272 | ``` 273 | 274 | --- 275 | 276 | 277 | ## 🌐 Learn More 278 | 279 | Visit [omnidim.io](https://www.omnidim.io/) to explore the full platform, UI builder, and templates. 280 | 281 | -------------------------------------------------------------------------------- /omnidimension/client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from urllib.parse import urljoin 4 | 5 | class APIError(Exception): 6 | """Exception raised for API errors.""" 7 | def __init__(self, status_code, message, response=None): 8 | self.status_code = status_code 9 | self.message = message 10 | self.response = response 11 | super().__init__(f"API Error ({status_code}): {message}") 12 | 13 | class Client(object): 14 | def __init__(self, api_key, base_url='https://backend.omnidim.io/api/v1'): 15 | """ 16 | Initialize the OmniClient with API key and base URL. 17 | 18 | Args: 19 | api_key (str): The API key for authentication. 20 | base_url (str): The base URL of the API. 21 | """ 22 | if not api_key: 23 | raise ValueError("API key is required.") 24 | 25 | self.api_key = api_key 26 | self.base_url = base_url.rstrip('/') 27 | print(self.base_url) 28 | # Lazy-loaded domain clients 29 | self._agent = None 30 | self._bulk_call = None 31 | self._call = None 32 | self._integrations = None 33 | self._knowledge_base = None 34 | self._phone_number = None 35 | self._providers = None 36 | self._simulation = None 37 | 38 | # Verify API key format (basic validation) 39 | if not isinstance(api_key, str) or len(api_key.strip()) < 8: 40 | raise ValueError("API key appears to be invalid. Please check your credentials.") 41 | 42 | def request(self, method, endpoint, params=None, headers=None, data=None, json_data=None): 43 | """ 44 | Universal request method to handle all API requests. 45 | 46 | Args: 47 | method (str): HTTP method (GET, POST, PUT, DELETE) 48 | endpoint (str): API endpoint path (without base URL) 49 | params (dict, optional): URL parameters 50 | headers (dict, optional): HTTP headers 51 | data (dict, optional): Form data 52 | json_data (dict, optional): JSON data 53 | 54 | Returns: 55 | dict: Response with status code and JSON data 56 | 57 | Raises: 58 | APIError: If the API returns an error status code 59 | requests.exceptions.RequestException: For network-related errors 60 | """ 61 | # Prepare request 62 | headers = headers or {} 63 | params = params or {} 64 | method = method.upper() 65 | 66 | # Add authorization header 67 | headers.setdefault('Authorization', f'Bearer {self.api_key}') 68 | headers.setdefault('Content-Type', 'application/json') 69 | headers.setdefault('Accept', 'application/json') 70 | 71 | # Build full URL 72 | url = self.base_url + '/' + endpoint.lstrip('/') 73 | try: 74 | # Make the request 75 | response = requests.request( 76 | method=method, 77 | url=url, 78 | params=params, 79 | headers=headers, 80 | data=data, 81 | json=json_data 82 | ) 83 | 84 | # Check for HTTP errors 85 | response.raise_for_status() 86 | 87 | # Process response based on method 88 | if method == "DELETE": 89 | json_response = {} 90 | else: 91 | json_response = response.json() if response.content else {} 92 | 93 | return { 94 | "status": response.status_code, 95 | "json": json_response 96 | } 97 | 98 | except requests.exceptions.HTTPError as e: 99 | # Handle API errors with response 100 | error_message = "Unknown error" 101 | error_data = {} 102 | 103 | try: 104 | error_data = e.response.json() 105 | error_message = error_data.get('error_description', error_data.get('error', str(e))) 106 | except (ValueError, AttributeError, KeyError): 107 | error_message = str(e) 108 | 109 | raise APIError( 110 | status_code=e.response.status_code, 111 | message=error_message, 112 | response=error_data 113 | ) 114 | 115 | except requests.exceptions.RequestException as e: 116 | # Handle network errors 117 | raise APIError( 118 | status_code=0, 119 | message=f"Network error: {str(e)}" 120 | ) 121 | 122 | # Convenience methods for different HTTP methods 123 | def get(self, endpoint, params=None, headers=None): 124 | """Make a GET request to the API.""" 125 | return self.request("GET", endpoint, params=params, headers=headers) 126 | 127 | def post(self, endpoint, data=None, params=None, headers=None): 128 | """Make a POST request to the API.""" 129 | return self.request("POST", endpoint, params=params, headers=headers, json_data=data) 130 | 131 | def put(self, endpoint, data=None, params=None, headers=None): 132 | """Make a PUT request to the API.""" 133 | return self.request("PUT", endpoint, params=params, headers=headers, json_data=data) 134 | 135 | def delete(self, endpoint, params=None, headers=None): 136 | """Make a DELETE request to the API.""" 137 | return self.request("DELETE", endpoint, params=params, headers=headers) 138 | 139 | # Domain-specific clients (lazy-loaded) 140 | @property 141 | def agent(self): 142 | """Get the Agent client.""" 143 | if self._agent is None: 144 | from .Agent import Agent 145 | self._agent = Agent(self) 146 | return self._agent 147 | 148 | @property 149 | def bulk_call(self): 150 | """Get the BulkCall client.""" 151 | if self._bulk_call is None: 152 | from .BulkCall import BulkCall 153 | self._bulk_call = BulkCall(self) 154 | return self._bulk_call 155 | 156 | @property 157 | def call(self): 158 | """Get the Callback client.""" 159 | if self._call is None: 160 | from .Call import Call 161 | self._call = Call(self) 162 | return self._call 163 | 164 | @property 165 | def integrations(self): 166 | """Get the Integrations client.""" 167 | if self._integrations is None: 168 | from .Integrations import Integrations 169 | self._integrations = Integrations(self) 170 | return self._integrations 171 | 172 | @property 173 | def knowledge_base(self): 174 | """Get the KnowledgeBase client.""" 175 | if self._knowledge_base is None: 176 | from .KnowledgeBase import KnowledgeBase 177 | self._knowledge_base = KnowledgeBase(self) 178 | return self._knowledge_base 179 | 180 | @property 181 | def phone_number(self): 182 | """Get the PhoneNumber client.""" 183 | if self._phone_number is None: 184 | from .PhoneNumber import PhoneNumber 185 | self._phone_number = PhoneNumber(self) 186 | return self._phone_number 187 | 188 | @property 189 | def providers(self): 190 | """Get the Providers client.""" 191 | if self._providers is None: 192 | from .Providers import Providers 193 | self._providers = Providers(self) 194 | return self._providers 195 | 196 | @property 197 | def simulation(self): 198 | """Get the Simulation client.""" 199 | if self._simulation is None: 200 | from .Simulation import Simulation 201 | self._simulation = Simulation(self) 202 | return self._simulation -------------------------------------------------------------------------------- /omnidimension/Simulation/__init__.py: -------------------------------------------------------------------------------- 1 | class Simulation(): 2 | def __init__(self, client): 3 | """ 4 | Initialize the Simulation client with a reference to the main API client. 5 | 6 | Args: 7 | client: The main API client instance. 8 | """ 9 | self.client = client 10 | 11 | def create(self, name, agent_id, number_of_call_to_make=1, concurrent_call_count=3, 12 | max_call_duration_in_minutes=3, scenarios=None): 13 | """ 14 | Create a new simulation with specified scenarios to test your agent's performance. 15 | 16 | Args: 17 | name (str): Name of the simulation for identification. 18 | agent_id (int): ID of the agent to test. 19 | number_of_call_to_make (int, optional): Number of calls to make per scenario (default: 1). 20 | concurrent_call_count (int, optional): Number of concurrent calls to run (default: 3). 21 | max_call_duration_in_minutes (int, optional): Maximum duration for each call in minutes (default: 3). 22 | scenarios (list, optional): List of test scenarios to execute. 23 | 24 | Returns: 25 | dict: Response from the API containing success status and simulation details. 26 | 27 | Raises: 28 | ValueError: If required fields are missing or invalid. 29 | 30 | Example: 31 | ```python 32 | # Create a simulation with scenarios 33 | client.simulation.create( 34 | name="Restaurant Order Taking Test", 35 | agent_id=123, 36 | number_of_call_to_make=1, 37 | concurrent_call_count=3, 38 | max_call_duration_in_minutes=3, 39 | scenarios=[ 40 | { 41 | "name": "Order Pizza", 42 | "description": "1. Act as a customer wanting to order pizza\\n2. Ask for menu items\\n3. Order a large pepperoni pizza\\n4. Provide contact details\\n5. End call with thank you", 43 | "expected_result": "Order should be placed successfully and confirmation provided", 44 | "selected_voices": [ 45 | {"id": "voice_id_1", "provider": "eleven_labs"}, 46 | {"id": "voice_id_2", "provider": "play_ht"} 47 | ] 48 | } 49 | ] 50 | ) 51 | ``` 52 | """ 53 | # Validate required inputs 54 | if not name or not isinstance(name, str): 55 | raise ValueError("Name is required and must be a string.") 56 | 57 | if not isinstance(agent_id, int): 58 | raise ValueError("Agent ID must be an integer.") 59 | 60 | # Validate scenarios if provided 61 | if scenarios is not None: 62 | self._validate_scenarios(scenarios) 63 | 64 | data = { 65 | "name": name, 66 | "agent_id": agent_id, 67 | "number_of_call_to_make": number_of_call_to_make, 68 | "concurrent_call_count": concurrent_call_count, 69 | "max_call_duration_in_minutes": max_call_duration_in_minutes 70 | } 71 | 72 | if scenarios is not None: 73 | data["scenarios"] = scenarios 74 | 75 | return self.client.post("simulations", data=data) 76 | 77 | def list(self, pageno=1, pagesize=10): 78 | """ 79 | Retrieve all simulations for the authenticated user with pagination support. 80 | 81 | Args: 82 | pageno (int, optional): Page number for pagination (default: 1). 83 | pagesize (int, optional): Number of records per page (default: 10). 84 | 85 | Returns: 86 | dict: Response containing the list of simulations. 87 | 88 | Example: 89 | ```python 90 | # List all simulations with pagination 91 | response = client.simulation.list(pageno=1, pagesize=10) 92 | ``` 93 | """ 94 | params = { 95 | 'pageno': pageno, 96 | 'pagesize': pagesize 97 | } 98 | return self.client.get("simulations", params=params) 99 | 100 | def get(self, simulation_id): 101 | """ 102 | Retrieve detailed information about a specific simulation including status, progress, and results. 103 | 104 | Args: 105 | simulation_id (int): The ID of the simulation to retrieve. 106 | 107 | Returns: 108 | dict: Response containing the simulation details. 109 | 110 | Raises: 111 | ValueError: If simulation_id is not an integer. 112 | 113 | Example: 114 | ```python 115 | # Get details of a specific simulation 116 | simulation_id = 456 117 | response = client.simulation.get(simulation_id) 118 | ``` 119 | """ 120 | if not isinstance(simulation_id, int): 121 | raise ValueError("Simulation ID must be an integer.") 122 | 123 | return self.client.get(f"simulations/{simulation_id}") 124 | 125 | def update(self, simulation_id, data): 126 | """ 127 | Update an existing simulation. Cannot update simulations that are currently in progress. 128 | 129 | Args: 130 | simulation_id (int): The ID of the simulation to update. 131 | data (dict): Dictionary containing the fields to update. Can include any field from create simulation. 132 | 133 | Returns: 134 | dict: Response from the API containing success status and updated simulation details. 135 | 136 | Raises: 137 | ValueError: If required fields are missing or invalid. 138 | 139 | Example: 140 | ```python 141 | # Update an existing simulation 142 | simulation_id = 456 143 | update_data = { 144 | "name": "Updated Restaurant Test", 145 | "max_call_duration_in_minutes": 5, 146 | "scenarios": [ 147 | { 148 | "name": "Updated Order Pizza", 149 | "description": "Updated instructions...", 150 | "expected_result": "Updated expected behavior..." 151 | } 152 | ] 153 | } 154 | response = client.simulation.update(simulation_id, update_data) 155 | ``` 156 | """ 157 | if not isinstance(simulation_id, int): 158 | raise ValueError("Simulation ID must be an integer.") 159 | 160 | if not isinstance(data, dict) or not data: 161 | raise ValueError("Update data must be a non-empty dictionary.") 162 | 163 | # Validate scenarios if provided in update data 164 | if "scenarios" in data and data["scenarios"] is not None: 165 | self._validate_scenarios(data["scenarios"]) 166 | 167 | return self.client.put(f"simulations/{simulation_id}", data=data) 168 | 169 | def delete(self, simulation_id): 170 | """ 171 | Delete (deactivate) a simulation. Running simulations are automatically stopped first. 172 | 173 | Args: 174 | simulation_id (int): The ID of the simulation to delete. 175 | 176 | Returns: 177 | dict: Response from the API containing success status. 178 | 179 | Raises: 180 | ValueError: If simulation_id is not an integer. 181 | 182 | Example: 183 | ```python 184 | # Delete a simulation 185 | simulation_id = 456 186 | response = client.simulation.delete(simulation_id) 187 | ``` 188 | """ 189 | if not isinstance(simulation_id, int): 190 | raise ValueError("Simulation ID must be an integer.") 191 | 192 | return self.client.delete(f"simulations/{simulation_id}") 193 | 194 | def start(self, simulation_id): 195 | """ 196 | Start a simulation. You can optionally update scenarios before starting the test run. 197 | 198 | Args: 199 | simulation_id (int): The ID of the simulation to start. 200 | scenarios (list, optional): Optional array of scenarios to update before starting. 201 | 202 | Returns: 203 | dict: Response from the API containing success status and simulation start details. 204 | 205 | Raises: 206 | ValueError: If simulation_id is not an integer or scenarios format is invalid. 207 | 208 | Example: 209 | ```python 210 | # Start a simulation 211 | simulation_id = 456 212 | response = client.simulation.start(simulation_id) 213 | 214 | # Start with updated scenarios 215 | response = client.simulation.start( 216 | simulation_id, 217 | ) 218 | ``` 219 | """ 220 | if not isinstance(simulation_id, int): 221 | raise ValueError("Simulation ID must be an integer.") 222 | 223 | return self.client.post(f"simulations/{simulation_id}/start", data={}) 224 | 225 | def stop(self, simulation_id): 226 | """ 227 | Stop a running simulation and disconnect any ongoing test calls. 228 | 229 | Args: 230 | simulation_id (int): The ID of the simulation to stop. 231 | 232 | Returns: 233 | dict: Response from the API containing success status. 234 | 235 | Raises: 236 | ValueError: If simulation_id is not an integer. 237 | 238 | Example: 239 | ```python 240 | # Stop a running simulation 241 | simulation_id = 456 242 | response = client.simulation.stop(simulation_id) 243 | ``` 244 | """ 245 | if not isinstance(simulation_id, int): 246 | raise ValueError("Simulation ID must be an integer.") 247 | 248 | return self.client.post(f"simulations/{simulation_id}/stop") 249 | 250 | def enhance_prompt(self, simulation_id): 251 | """ 252 | Analyze a completed simulation and receive AI-powered suggestions for improving your agent's prompts and performance. 253 | 254 | Args: 255 | simulation_id (int): The ID of the completed simulation to analyze. 256 | 257 | Returns: 258 | dict: Response containing enhanced prompt suggestions. 259 | 260 | Raises: 261 | ValueError: If simulation_id is not an integer. 262 | 263 | Example: 264 | ```python 265 | # Get enhanced prompt suggestions 266 | simulation_id = 456 267 | response = client.simulation.enhance_prompt(simulation_id) 268 | ``` 269 | """ 270 | if not isinstance(simulation_id, int): 271 | raise ValueError("Simulation ID must be an integer.") 272 | 273 | return self.client.post(f"simulations/{simulation_id}/enhance-prompt") 274 | 275 | 276 | @staticmethod 277 | def _validate_scenarios(scenarios, allow_id=False): 278 | """ 279 | Validate scenarios format. 280 | 281 | Args: 282 | scenarios: List of scenarios to validate. 283 | allow_id: Whether to allow 'id' field in scenarios (for updates). 284 | 285 | Raises: 286 | ValueError: If scenarios don't follow the expected format. 287 | """ 288 | 289 | if not isinstance(scenarios, list): 290 | raise ValueError("Scenarios must be a list of dictionaries.") 291 | 292 | for scenario in scenarios: 293 | if not isinstance(scenario, dict): 294 | raise ValueError("Each scenario must be a dictionary.") 295 | 296 | # Check required fields 297 | required_fields = ['name', 'description', 'expected_result'] 298 | for field in required_fields: 299 | if field not in scenario or not isinstance(scenario[field], str): 300 | raise ValueError(f"Each scenario must contain a '{field}' field as a string.") 301 | 302 | # Validate selected_voices if provided 303 | if 'selected_voices' in scenario: 304 | voices = scenario['selected_voices'] 305 | if not isinstance(voices, list): 306 | raise ValueError("selected_voices must be a list of dictionaries.") 307 | 308 | for voice in voices: 309 | if not isinstance(voice, dict): 310 | raise ValueError("Each voice in selected_voices must be a dictionary.") 311 | 312 | if 'id' not in voice or 'provider' not in voice: 313 | raise ValueError("Each voice must contain 'id' and 'provider' fields.") 314 | 315 | valid_providers = ['eleven_labs', 'deepgram', 'cartesia', 'rime', 'inworld'] 316 | if voice['provider'] not in valid_providers: 317 | raise ValueError(f"Voice provider must be one of: {', '.join(valid_providers)}") 318 | -------------------------------------------------------------------------------- /omnidimension/Providers/README.md: -------------------------------------------------------------------------------- 1 | # Providers Module 2 | 3 | The Providers module provides access to all AI service providers available in the OmniDimension platform, including LLMs, voices, STT, and TTS providers. 4 | 5 | ## Features 6 | 7 | - **LLM Providers**: List all available Large Language Models (23 providers) 8 | - **Voice Providers**: Advanced voice filtering with ElevenLabs support (104+ voices available through search) 9 | - **STT Providers**: Speech-to-Text service providers (5 providers) 10 | - **TTS Providers**: Text-to-Speech service providers (10 providers) 11 | - **Comprehensive Listing**: Get all providers in a single request (38 services total) 12 | - **Voice Details**: Get detailed information about specific voices 13 | - **Advanced Filtering**: ElevenLabs-specific filters (search, language, accent, gender) 14 | - **Pagination Support**: Handling of large result sets 15 | 16 | ## Quick Start 17 | 18 | ```python 19 | from omnidimension import Client 20 | 21 | client = Client('your-api-key') 22 | 23 | # List all LLM providers 24 | llms = client.providers.list_llms() 25 | print(f"[SUCCESS] Found {llms['total']} LLM providers") 26 | 27 | # List voices with pagination 28 | voices = client.providers.list_voices(page=1, page_size=50) 29 | print(f"[SUCCESS] Found {voices['total']} voices") 30 | 31 | # Search ElevenLabs voices 32 | professional_voices = client.providers.list_voices( 33 | provider='eleven_labs', 34 | search='professional', 35 | gender='male', 36 | page_size=20 37 | ) 38 | ``` 39 | 40 | ## API Methods 41 | 42 | ### Core Methods 43 | 44 | #### 1. `list_llms()` 45 | Returns all available LLM providers. 46 | 47 | ```python 48 | response = client.providers.list_llms() 49 | # Response contains: {'llms': [...], 'total': 23} 50 | ``` 51 | 52 | **Response:** 53 | ```json 54 | { 55 | "llms": [ 56 | { 57 | "id": 11, 58 | "name": "gpt-4o", 59 | "display_name": "gpt-4o", 60 | "service_type": "LLM" 61 | }, 62 | { 63 | "id": 12, 64 | "name": "gpt-4o-mini", 65 | "display_name": "gpt-4o-mini", 66 | "service_type": "LLM" 67 | } 68 | ], 69 | "total": 23 70 | } 71 | ``` 72 | 73 | #### 2. `list_voices(provider=None, search=None, language=None, accent=None, gender=None, page=1, page_size=30)` 74 | Returns voice providers with advanced filtering and pagination. 75 | 76 | **Parameters:** 77 | - `provider` (str): Filter by specific TTS provider ('eleven_labs', 'google', 'deepgram', 'cartesia', 'sarvam') 78 | - `search` (str): Search term for voice name/description (ElevenLabs only) 79 | - `language` (str): Language filter (ElevenLabs only, e.g., 'en', 'es', 'fr') 80 | - `accent` (str): Accent filter (ElevenLabs only, e.g., 'american', 'british') 81 | - `gender` (str): Gender filter ('male' or 'female', ElevenLabs only) 82 | - `page` (int): Page number for pagination (default: 1) 83 | - `page_size` (int): Number of items per page (default: 30, max: 100) 84 | 85 | ```python 86 | # Get all voices 87 | all_voices = client.providers.list_voices() 88 | 89 | # Search ElevenLabs voices 90 | professional_male = client.providers.list_voices( 91 | provider='eleven_labs', 92 | search='professional', 93 | gender='male', 94 | page_size=20 95 | ) 96 | 97 | # Filter by language and accent 98 | british_english = client.providers.list_voices( 99 | provider='eleven_labs', 100 | language='en', 101 | accent='british' 102 | ) 103 | ``` 104 | 105 | **Response:** 106 | ```json 107 | { 108 | "voices": [ 109 | { 110 | "id": 37, 111 | "name": "JBFqnCBsd6RMkjVDRZzb", 112 | "display_name": "George", 113 | "service": "eleven_labs", 114 | "sample_url": "https://storage.googleapis.com/eleven-public-prod/premade/voices/JBFqnCBsd6RMkjVDRZzb/e6206d1a-0721-4787-aafb-06a6e705cac5.mp3" 115 | }, 116 | { 117 | "id": 1, 118 | "name": "aura-luna-en", 119 | "display_name": "luna", 120 | "service": "deepgram", 121 | "sample_url": "https://res.cloudinary.com/deepgram/video/upload/v1709565351/aura/luna_docs_clom0e.wav" 122 | } 123 | ], 124 | "total": 104, 125 | "page": 1, 126 | "page_size": 30 127 | } 128 | ``` 129 | 130 | #### 3. `list_stt()` 131 | Returns all available Speech-to-Text providers. 132 | 133 | ```python 134 | stt_providers = client.providers.list_stt() 135 | ``` 136 | 137 | **Response:** 138 | ```json 139 | { 140 | "stt": [ 141 | { 142 | "id": 1, 143 | "name": "whisper", 144 | "display_name": "whisper", 145 | "service_type": "STT" 146 | }, 147 | { 148 | "id": 2, 149 | "name": "deepgram_stream", 150 | "display_name": "deepgram_stream", 151 | "service_type": "STT" 152 | }, 153 | { 154 | "id": 3, 155 | "name": "Cartesia", 156 | "display_name": "Cartesia", 157 | "service_type": "STT" 158 | }, 159 | { 160 | "id": 4, 161 | "name": "Sarvam", 162 | "display_name": "Sarvam", 163 | "service_type": "STT" 164 | }, 165 | { 166 | "id": 5, 167 | "name": "Azure", 168 | "display_name": "Azure", 169 | "service_type": "STT" 170 | } 171 | ], 172 | "total": 5 173 | } 174 | ``` 175 | 176 | #### 4. `list_tts()` 177 | Returns all available Text-to-Speech providers. 178 | 179 | ```python 180 | tts_providers = client.providers.list_tts() 181 | ``` 182 | 183 | **Response:** 184 | ```json 185 | { 186 | "tts": [ 187 | { 188 | "id": 29, 189 | "name": "deepgram", 190 | "display_name": "deepgram", 191 | "service_type": "TTS" 192 | }, 193 | { 194 | "id": 30, 195 | "name": "google", 196 | "display_name": "google", 197 | "service_type": "TTS" 198 | }, 199 | { 200 | "id": 31, 201 | "name": "eleven_labs", 202 | "display_name": "eleven_labs", 203 | "service_type": "TTS" 204 | } 205 | ], 206 | "total": 10 207 | } 208 | ``` 209 | 210 | #### 5. `list_all()` 211 | Returns all providers (services and voices) in a comprehensive response. 212 | 213 | ```python 214 | all_providers = client.providers.list_all() 215 | ``` 216 | 217 | **Response:** 218 | ```json 219 | { 220 | "services": { 221 | "STT": [ 222 | { 223 | "id": 1, 224 | "name": "whisper", 225 | "display_name": "whisper", 226 | "service_type": "STT" 227 | } 228 | ], 229 | "LLM": [ 230 | { 231 | "id": 11, 232 | "name": "gpt-4o", 233 | "display_name": "gpt-4o", 234 | "service_type": "LLM" 235 | } 236 | ], 237 | "TTS": [ 238 | { 239 | "id": 29, 240 | "name": "deepgram", 241 | "display_name": "deepgram", 242 | "service_type": "TTS" 243 | } 244 | ] 245 | }, 246 | "voices": [ 247 | { 248 | "id": 1, 249 | "name": "aura-luna-en", 250 | "display_name": "luna", 251 | "service": "deepgram", 252 | "sample_url": "https://res.cloudinary.com/deepgram/video/upload/v1709565351/aura/luna_docs_clom0e.wav" 253 | } 254 | ], 255 | "total_services": 38, 256 | "total_voices": 104 257 | } 258 | ``` 259 | 260 | #### 6 .`get_voice(voice_id)` 261 | Get detailed information about a specific voice. 262 | 263 | ```python 264 | voice_info = client.providers.get_voice(1) 265 | print(f"Voice: {voice_info['display_name']}") 266 | ``` 267 | 268 | **Response:** 269 | ```json 270 | { 271 | "id": 1, 272 | "name": "aura-luna-en", 273 | "display_name": "luna", 274 | "service": "deepgram", 275 | "sample_url": "https://res.cloudinary.com/deepgram/video/upload/v1709565351/aura/luna_docs_clom0e.wav" 276 | } 277 | ``` 278 | 279 | ## Important Notes 280 | 281 | ### ElevenLabs Voice Coverage 282 | 283 | **ElevenLabs Full Voice Library Access:** 284 | - Our API provides access to ElevenLabs' complete voice library (1000+ voices) 285 | - Use search, language, accent, and gender filters to find specific voices 286 | - The API returns paginated results (up to 104 voices per page) but supports searching the entire ElevenLabs catalog 287 | - All ElevenLabs voices are available through our search functionality 288 | 289 | ### Filter Support by Provider 290 | 291 | **ElevenLabs (Premium Provider)** 292 | 293 | - **FULL FILTER SUPPORT:** 294 | - `search` - Search by voice name/description 295 | - `language` - Filter by language (en, es, fr, etc.) 296 | - `accent` - Filter by accent (american, british, etc.) 297 | - `gender` - Filter by gender (male, female) 298 | 299 | **All Other Providers (Deepgram, Google, Cartesia, Sarvam, etc.)** 300 | [ERROR] **LIMITED FILTER SUPPORT:** 301 | - `search`, `language`, `accent`, `gender` - **NOT SUPPORTED** (returns error response) 302 | - `page` & `page_size` - **ONLY PAGINATION WORKS** 303 | 304 | ### Error Handling 305 | 306 | The SDK provides user-friendly error messages for common issues: 307 | 308 | ```python 309 | try: 310 | # This will work 311 | elevenlabs_voices = client.providers.list_voices( 312 | provider='eleven_labs', 313 | search='excited' 314 | ) 315 | except ValueError as e: 316 | print(f"Error: {e}") # Rate limit exceeded. Please wait 60 seconds before trying again. 317 | 318 | try: 319 | # This will return an error for non-ElevenLabs providers 320 | deepgram_voices = client.providers.list_voices( 321 | provider='deepgram', 322 | search='excited' # Not supported 323 | ) 324 | except ValueError as e: 325 | print(f"Error: {e}") # Advanced filtering (search, language, accent, gender) is only supported for provider 'eleven_labs'. Provider 'deepgram' only supports pagination. 326 | ``` 327 | 328 | ### Pagination 329 | 330 | - Default `page_size` is 30, maximum is 100 331 | - Page numbering starts from 1 332 | - All providers support pagination 333 | - `total` field represents actual results returned 334 | 335 | ### Rate Limiting 336 | 337 | - ElevenLabs requests are rate limited to 10 per minute per user/IP 338 | - Caching is implemented to reduce API calls and improve performance 339 | - Cached responses are served instantly for repeated requests 340 | 341 | ## Examples 342 | 343 | ### Basic Usage 344 | 345 | ```python 346 | from omnidimension import Client 347 | 348 | client = Client('your-api-key') 349 | 350 | # Get all providers 351 | llms = client.providers.list_llms() 352 | voices = client.providers.list_voices() 353 | stt = client.providers.list_stt() 354 | tts = client.providers.list_tts() 355 | 356 | print(f"[SUCCESS] LLMs: {llms['total']}, Voices: {voices['total']}, STT: {stt['total']}, TTS: {tts['total']}") 357 | ``` 358 | 359 | ### Advanced Voice Filtering 360 | 361 | ```python 362 | # Find professional male voices for ElevenLabs 363 | professional_male = client.providers.list_voices( 364 | provider='eleven_labs', 365 | search='professional', 366 | gender='male', 367 | language='en', 368 | page_size=25 369 | ) 370 | 371 | # Search by specific criteria 372 | excited_voices = client.providers.list_voices( 373 | provider='eleven_labs', 374 | search='excited' 375 | ) 376 | 377 | # Get voice details 378 | if professional_male['voices']: 379 | voice_id = professional_male['voices'][0]['id'] 380 | details = client.providers.get_voice(voice_id) 381 | print(f"[SUCCESS] Selected voice: {details['display_name']}") 382 | ``` 383 | 384 | ### Working with Different Providers 385 | 386 | ```python 387 | # ElevenLabs voices (full filtering support) 388 | elevenlabs_voices = client.providers.list_voices( 389 | provider='eleven_labs', 390 | search='calm', 391 | gender='female', 392 | language='en' 393 | ) 394 | 395 | # Google voices (pagination only) 396 | google_voices = client.providers.list_voices( 397 | provider='google', 398 | page_size=50 399 | ) 400 | 401 | # Deepgram voices (pagination only) 402 | deepgram_voices = client.providers.list_voices( 403 | provider='deepgram', 404 | page=1, 405 | page_size=20 406 | ) 407 | ``` 408 | 409 | ### Provider Overview 410 | 411 | ```python 412 | # Get everything at once 413 | all_providers = client.providers.list_all() 414 | 415 | print(f"[SUCCESS] Total Services: {all_providers['total_services']}") 416 | print(f"[SUCCESS] Total Voices: {all_providers['total_voices']}") 417 | 418 | # Access specific service types 419 | llm_providers = all_providers['services']['LLM'] 420 | tts_providers = all_providers['services']['TTS'] 421 | stt_providers = all_providers['services']['STT'] 422 | all_voices = all_providers['voices'] 423 | ``` 424 | 425 | ### Error Handling Examples 426 | 427 | ```python 428 | # Parameter validation 429 | try: 430 | client.providers.list_voices(page=0) 431 | except ValueError as e: 432 | print(f"[ERROR] Invalid page: {e}") # "page must be a positive integer (1 or greater)" 433 | 434 | try: 435 | client.providers.list_voices(page_size=150) 436 | except ValueError as e: 437 | print(f"[ERROR] Invalid page_size: {e}") # "page_size must be an integer between 1 and 100" 438 | 439 | try: 440 | client.providers.list_voices(gender='invalid') 441 | except ValueError as e: 442 | print(f"[ERROR] Invalid gender: {e}") # "gender must be 'male' or 'female'" 443 | 444 | # Unsupported filters 445 | try: 446 | client.providers.list_voices( 447 | provider='google', 448 | search='test' # Not supported for Google 449 | ) 450 | except ValueError as e: 451 | print(f"[ERROR] Unsupported filter: {e}") # "Advanced filtering (search, language, accent, gender) is only supported for provider 'eleven_labs'. Provider 'google' only supports pagination." 452 | 453 | # Rate limiting 454 | try: 455 | # Make many rapid requests 456 | for i in range(15): 457 | client.providers.list_voices(provider='eleven_labs', page_size=1) 458 | except ValueError as e: 459 | print(f"[ERROR] Rate limited: {e}") # "Rate limit exceeded. Please wait 60 seconds before trying again." 460 | 461 | # Authentication errors 462 | try: 463 | bad_client = Client('invalid-key') 464 | bad_client.providers.list_llms() 465 | except Exception as e: 466 | print(f"[ERROR] Auth error: {e}") # "API Error (401): Missing or invalid API key" 467 | 468 | # Voice not found 469 | try: 470 | client.providers.get_voice(999999) 471 | except ValueError as e: 472 | print(f"[ERROR] Voice not found: {e}") # "Voice with ID 999999 was not found." 473 | ``` 474 | 475 | ## Tips # 476 | 477 | 1. **Use Pagination**: Don't request all voices at once, use `page_size` appropriately 478 | 2. **Leverage Caching**: Repeated requests for the same filters will be served from cache 479 | 3. **Filter Efficiently**: Use specific filters to reduce result sets 480 | 4. **Choose Right Provider**: ElevenLabs for advanced filtering, others for basic listing 481 | 5. **Batch Operations**: Use `list_all()` when you need comprehensive data 482 | 483 | ## Troubleshooting 484 | 485 | **Common Issues:** 486 | 487 | 1. **"Advanced filtering (search, language, accent, gender) is only supported for provider 'eleven_labs'. Provider 'X' only supports pagination."**: You're using search/language/accent/gender filters on non-ElevenLabs providers 488 | 2. **"Rate limit exceeded. Please wait 60 seconds before trying again."**: Wait before making more ElevenLabs requests 489 | 3. **"Voice with ID X was not found."**: The voice ID doesn't exist or is invalid 490 | 4. **"page must be a positive integer (1 or greater)"**: Page number must be >= 1 491 | 5. **"page_size must be an integer between 1 and 100"**: Page size is outside valid range 492 | 6. **"gender must be 'male' or 'female'"**: Gender parameter is invalid 493 | 7. **"API Error (401): Missing or invalid API key"**: Authentication failed 494 | 495 | **Debug Tips:** 496 | 497 | ```python 498 | # Enable debug logging 499 | import logging 500 | logging.basicConfig(level=logging.DEBUG) 501 | 502 | # Check API key 503 | print(f"[SUCCESS] API Key configured: {bool(client.api_key)}") 504 | 505 | # Test basic connectivity 506 | try: 507 | client.providers.list_llms() 508 | print("[SUCCESS] API connection successful") 509 | except Exception as e: 510 | print(f"[ERROR] Connection failed: {e}") 511 | 512 | # Check response structure 513 | response = client.providers.list_voices(page_size=1) 514 | print(f"[SUCCESS] Response keys: {response.keys()}") 515 | print(f"[SUCCESS] First voice: {response['voices'][0] if response['voices'] else 'No voices'}") 516 | ``` 517 | 518 | ## Available Providers Summary 519 | 520 | - **LLM Providers**: 23 (OpenAI, Anthropic, Google, Azure, etc.) 521 | - **Voice Providers**: 104+ voices across 5 services (ElevenLabs: 1000+ via search, Deepgram, Google, Cartesia, Sarvam) 522 | - **STT Providers**: 5 (Whisper, Deepgram, Cartesia, Sarvam, Azure) 523 | - **TTS Providers**: 10 (Deepgram, Google, ElevenLabs, Cartesia, Hume, Inworld, OpenAI, Play.ht, Rime, Sarvam) 524 | 525 | For the most up-to-date list, use the API methods above. 526 | -------------------------------------------------------------------------------- /omnidimension/Integrations/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | class Integrations(): 3 | def __init__(self, client): 4 | """ 5 | Initialize the Integrations client with a reference to the main API client. 6 | 7 | Args: 8 | client: The main API client instance. 9 | """ 10 | self.client = client 11 | 12 | @staticmethod 13 | def get_integration_format_examples(): 14 | """ 15 | Returns examples of the expected format for different integration types. 16 | 17 | Returns: 18 | dict: A dictionary containing example formats for different integration types. 19 | """ 20 | return { 21 | "custom_api": { 22 | "name": "Example API Integration", 23 | "description": "Integration with external service", 24 | "url": "http://api.example.com/endpoint", 25 | "method": "GET", 26 | "headers": [ 27 | {"key": "Authorization", "value": "Bearer token123"}, 28 | {"key": "Content-Type", "value": "application/json"} 29 | ], 30 | "integration_type": "custom_api", 31 | "query_params": [ 32 | { 33 | "key": "user_id", 34 | "description": "User identifier", 35 | "type": "string", 36 | "required": True, 37 | "isLLMGenerated": False 38 | } 39 | ], 40 | "body_params": [ 41 | { 42 | "key": "message", 43 | "description": "Message content", 44 | "type": "string", 45 | "required": True, 46 | "isLLMGenerated": True 47 | } 48 | ] 49 | }, 50 | "cal": { 51 | "name": "Example Cal.com Integration", 52 | "description": "Integration with Cal.com calendar", 53 | "cal_api_key": "cal_api_key_12345", 54 | "cal_id": "cal_user_id", 55 | "cal_timezone": "America/New_York", 56 | "integration_type": "cal" 57 | } 58 | } 59 | 60 | def get_user_integrations(self): 61 | """ 62 | Get all integrations available for the authenticated user. 63 | 64 | Returns: 65 | dict: Response containing the list of integrations. 66 | """ 67 | return self.client.get("integrations") 68 | 69 | @staticmethod 70 | def _validate_integration_params(params_list, param_type): 71 | """ 72 | Validate integration parameters format. 73 | 74 | Args: 75 | params_list: List of parameters to validate. 76 | param_type: Type of parameters being validated (headers, query_params, body_params). 77 | 78 | Raises: 79 | ValueError: If parameters don't follow the expected format. 80 | """ 81 | if params_list is None: 82 | return 83 | 84 | if not isinstance(params_list, list): 85 | raise ValueError(f"{param_type} must be a list of dictionaries.") 86 | 87 | required_keys = { 88 | 'headers': ['key', 'value'], 89 | 'query_params': ['key'], 90 | 'body_params': ['key'] 91 | } 92 | 93 | for param in params_list: 94 | if not isinstance(param, dict): 95 | raise ValueError(f"Each {param_type} item must be a dictionary.") 96 | 97 | for key in required_keys.get(param_type, []): 98 | if key not in param: 99 | raise ValueError(f"Each {param_type} item must contain a '{key}' field.") 100 | 101 | # Additional validation for query_params and body_params 102 | if param_type in ['query_params', 'body_params']: 103 | for param in params_list: 104 | if 'type' in param and param['type'] not in ['string', 'number', 'boolean']: 105 | raise ValueError(f"Parameter type must be one of: 'string', 'number', 'boolean'. Got: {param['type']}") 106 | 107 | if 'required' in param and not isinstance(param['required'], bool): 108 | raise ValueError("'required' field must be a boolean value.") 109 | 110 | if 'isLLMGenerated' in param and not isinstance(param['isLLMGenerated'], bool): 111 | raise ValueError("'isLLMGenerated' field must be a boolean value.") 112 | 113 | def create_custom_api_integration(self, name, url, method, description="", headers=None, 114 | body_type=None, body_content=None, body_params=None, 115 | query_params=None , stop_listening=False, request_timeout=10 ): 116 | """ 117 | Create a custom API integration. 118 | 119 | Args: 120 | name (str): Name for the integration. 121 | url (str): URL for the API endpoint. 122 | method (str): HTTP method (GET, POST, PUT, DELETE, PATCH). 123 | description (str, optional): Description of the integration. 124 | headers (list, optional): Headers for the API request in the format: 125 | [ 126 | {"key": "header_name", "value": "header_value"}, 127 | ... 128 | ] 129 | body_type (str, optional): Body type (none, json, form). 130 | body_content (str, optional): Body content for the request. 131 | body_params (list, optional): Body parameters for the request in the format: 132 | [ 133 | { 134 | "key": "param_name", 135 | "description": "param_description", 136 | "type": "string|number|boolean", 137 | "required": true|false, 138 | "isLLMGenerated": true|false 139 | }, 140 | ... 141 | ] 142 | query_params (list, optional): Query parameters for the request in the format: 143 | [ 144 | { 145 | "key": "param_name", 146 | "description": "param_description", 147 | "type": "string|number|boolean", 148 | "required": true|false, 149 | "isLLMGenerated": true|false 150 | }, 151 | ... 152 | ] 153 | 154 | Returns: 155 | dict: Response from the API containing success status and integration ID. 156 | 157 | Raises: 158 | ValueError: If required fields are missing or invalid. 159 | 160 | Example: 161 | ```python 162 | # Create a custom API integration with headers and query parameters 163 | client.integrations.create_custom_api_integration( 164 | name="My API Integration", 165 | description="Integration with external service", 166 | url="http://api.example.com/endpoint", 167 | method="GET", 168 | headers=[ 169 | {"key": "Authorization", "value": "Bearer token123"}, 170 | {"key": "Content-Type", "value": "application/json"} 171 | ], 172 | query_params=[ 173 | { 174 | "key": "user_id", 175 | "description": "User identifier", 176 | "type": "string", 177 | "required": True, 178 | "isLLMGenerated": False 179 | } 180 | ] 181 | ) 182 | ``` 183 | """ 184 | # Validate required inputs 185 | if not name or not url or not method: 186 | raise ValueError("Name, URL, and method are required fields.") 187 | 188 | # Validate parameters format 189 | self._validate_integration_params(headers, 'headers') 190 | self._validate_integration_params(query_params, 'query_params') 191 | self._validate_integration_params(body_params, 'body_params') 192 | 193 | data = { 194 | "name": name, 195 | "url": url, 196 | "method": method, 197 | "description": description, 198 | "integration_type": "custom_api", 199 | "stop_listening": stop_listening, 200 | "request_timeout": request_timeout 201 | } 202 | 203 | # Add optional fields if provided 204 | if headers is not None: 205 | data["headers"] = headers 206 | 207 | if body_type is not None: 208 | data["body_type"] = body_type 209 | 210 | if body_content is not None: 211 | data["body_content"] = body_content 212 | 213 | if body_params is not None: 214 | data["body_params"] = body_params 215 | 216 | if query_params is not None: 217 | data["query_params"] = query_params 218 | 219 | return self.client.post("integrations/custom-api", data=data) 220 | 221 | def create_cal_integration(self, name, cal_api_key, cal_id, cal_timezone, description=""): 222 | """ 223 | Create a Cal.com integration. 224 | 225 | Args: 226 | name (str): Name for the integration. 227 | cal_api_key (str): Cal.com API key. 228 | cal_id (str): Cal.com ID. 229 | cal_timezone (str): Cal.com timezone. 230 | description (str, optional): Description of the integration. 231 | 232 | Returns: 233 | dict: Response from the API containing success status and integration ID. 234 | 235 | Raises: 236 | ValueError: If required fields are missing or invalid. 237 | 238 | Example: 239 | ```python 240 | # Create a Cal.com integration 241 | client.integrations.create_cal_integration( 242 | name="My Calendar Integration", 243 | description="Integration with Cal.com calendar", 244 | cal_api_key="cal_api_key_12345", 245 | cal_id="cal_user_id", 246 | cal_timezone="America/New_York" 247 | ) 248 | ``` 249 | """ 250 | # Validate required inputs 251 | if not name or not cal_api_key or not cal_id or not cal_timezone: 252 | raise ValueError("Name, Cal.com API key, Cal.com ID, and Cal.com timezone are required fields.") 253 | 254 | # Validate input types 255 | if not isinstance(name, str): 256 | raise ValueError("Name must be a string.") 257 | if not isinstance(cal_api_key, str): 258 | raise ValueError("Cal.com API key must be a string.") 259 | if not isinstance(cal_id, str): 260 | raise ValueError("Cal.com ID must be a string.") 261 | if not isinstance(cal_timezone, str): 262 | raise ValueError("Cal.com timezone must be a string.") 263 | 264 | data = { 265 | "name": name, 266 | "cal_api_key": cal_api_key, 267 | "cal_id": cal_id, 268 | "cal_timezone": cal_timezone, 269 | "description": description, 270 | "integration_type": "cal" 271 | } 272 | 273 | return self.client.post("integrations/cal", data=data) 274 | 275 | def create_integration_from_json(self, integration_data): 276 | """ 277 | Create an integration directly from a complete JSON object. 278 | 279 | This method is useful when you have a complete integration configuration 280 | in the expected format and want to create the integration in one call. 281 | 282 | Args: 283 | integration_data (dict): Complete integration data in the expected format. 284 | Must include 'integration_type' field and all required fields for that type. 285 | 286 | Returns: 287 | dict: Response from the API containing success status and integration ID. 288 | 289 | Raises: 290 | ValueError: If required fields are missing or invalid. 291 | 292 | Example: 293 | ```python 294 | # Create a custom API integration from a complete JSON object 295 | integration_data = { 296 | "name": "My API Integration", 297 | "description": "Integration with external service", 298 | "url": "http://api.example.com/endpoint", 299 | "method": "GET", 300 | "headers": [ 301 | {"key": "Authorization", "value": "Bearer token123"} 302 | ], 303 | "integration_type": "custom_api", 304 | "query_params": [ 305 | { 306 | "key": "user_id", 307 | "description": "User identifier", 308 | "type": "string", 309 | "required": True, 310 | "isLLMGenerated": False 311 | } 312 | ] 313 | } 314 | client.integrations.create_integration_from_json(integration_data) 315 | ``` 316 | """ 317 | if not integration_data or not isinstance(integration_data, dict): 318 | raise ValueError("Integration data must be a non-empty dictionary.") 319 | 320 | if 'integration_type' not in integration_data: 321 | raise ValueError("Integration data must include 'integration_type' field.") 322 | 323 | integration_type = integration_data.get('integration_type') 324 | 325 | # Validate based on integration type 326 | if integration_type == 'custom_api': 327 | # Required fields for custom API 328 | required_fields = ['name', 'url', 'method'] 329 | for field in required_fields: 330 | if field not in integration_data: 331 | raise ValueError(f"'{field}' is required for custom API integration.") 332 | 333 | # Validate parameters if present 334 | if 'headers' in integration_data: 335 | self._validate_integration_params(integration_data['headers'], 'headers') 336 | if 'query_params' in integration_data: 337 | self._validate_integration_params(integration_data['query_params'], 'query_params') 338 | if 'body_params' in integration_data: 339 | self._validate_integration_params(integration_data['body_params'], 'body_params') 340 | 341 | endpoint = "integrations/custom-api" 342 | 343 | elif integration_type == 'cal': 344 | # Required fields for Cal.com 345 | required_fields = ['name', 'cal_api_key', 'cal_id', 'cal_timezone'] 346 | for field in required_fields: 347 | if field not in integration_data: 348 | raise ValueError(f"'{field}' is required for Cal.com integration.") 349 | 350 | endpoint = "integrations/cal" 351 | 352 | else: 353 | raise ValueError(f"Unsupported integration type: {integration_type}") 354 | 355 | return self.client.post(endpoint, data=integration_data) 356 | 357 | def get_agent_integrations(self, agent_id): 358 | """ 359 | Get all integrations for a specific agent. 360 | 361 | Args: 362 | agent_id (int): ID of the agent to get integrations for. 363 | 364 | Returns: 365 | dict: Response containing the list of integrations for the agent. 366 | 367 | Raises: 368 | ValueError: If agent_id is not provided or invalid. 369 | """ 370 | if not isinstance(agent_id, int) or agent_id <= 0: 371 | raise ValueError("Agent ID must be a positive integer.") 372 | 373 | return self.client.get(f"agents/{agent_id}/integrations") 374 | 375 | def add_integration_to_agent(self, agent_id, integration_id): 376 | """ 377 | Add an existing integration to an agent. 378 | 379 | Args: 380 | agent_id (int): ID of the agent. 381 | integration_id (int): ID of the integration to add. 382 | 383 | Returns: 384 | dict: Response containing the result of the operation. 385 | 386 | Raises: 387 | ValueError: If required fields are missing or invalid. 388 | """ 389 | if not isinstance(agent_id, int) or agent_id <= 0: 390 | raise ValueError("Agent ID must be a positive integer.") 391 | 392 | if not isinstance(integration_id, int) or integration_id <= 0: 393 | raise ValueError("Integration ID must be a positive integer.") 394 | 395 | data = { 396 | "integration_id": integration_id 397 | } 398 | 399 | return self.client.post(f"agents/{agent_id}/integrations", data=data) 400 | 401 | def remove_integration_from_agent(self, agent_id, integration_id): 402 | """ 403 | Remove an integration from an agent. 404 | 405 | Args: 406 | agent_id (int): ID of the agent. 407 | integration_id (int): ID of the integration to remove. 408 | 409 | Returns: 410 | dict: Response containing the result of the operation. 411 | 412 | Raises: 413 | ValueError: If required fields are missing or invalid. 414 | """ 415 | if not isinstance(agent_id, int) or agent_id <= 0: 416 | raise ValueError("Agent ID must be a positive integer.") 417 | 418 | if not isinstance(integration_id, int) or integration_id <= 0: 419 | raise ValueError("Integration ID must be a positive integer.") 420 | 421 | return self.client.delete(f"agents/{agent_id}/integrations/{integration_id}") 422 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | import json 4 | from omnidimension import Client 5 | 6 | # Initialize the OmniDimension client 7 | api_key = os.environ.get('OMNIDIM_API_KEY', 'YBNmK6VxNLnPkIxwRNbZBKFR5C6_sRP0sSUFZeMr4p8') 8 | # production 9 | client = Client(api_key) 10 | # staging 11 | client = Client(api_key, base_url='https://dashboard.staging.omnidim.io/api/v1/') 12 | 13 | # ===== Example Usage Functions ===== 14 | 15 | def run_agent_examples(): 16 | """Run examples for agent operations""" 17 | print("\n===== RUNNING AGENT EXAMPLES =====\n") 18 | 19 | # List agents 20 | agents_data = list_agents() 21 | 22 | # Create a basic agent 23 | agent_data = create_agent_with_full_config( 24 | name="Example Agent", 25 | welcome_message="Hello! I'm an example agent created using the OmniDimension SDK.", 26 | context_breakdown=[ 27 | {"title": "Purpose", "body": "This agent demonstrates the SDK capabilities."} 28 | ], 29 | post_call_actions={ 30 | "email": { 31 | "enabled": True, 32 | "recipients": ["example@example.com"], 33 | "include": ["summary", "extracted_variables"] 34 | }, 35 | "extracted_variables": [ 36 | { 37 | "key": "caller_product_interest", 38 | "prompt": "Identify the products the caller is interested in..." 39 | }, 40 | ] 41 | } 42 | ) 43 | 44 | # Get the agent ID 45 | agent_id = agent_data.get('id') 46 | if agent_id: 47 | # Get agent details 48 | get_agent(agent_id) 49 | 50 | # Update the agent 51 | update_agent(agent_id, {"name": "Updated Example Agent"}) 52 | 53 | # Uncomment to delete the agent 54 | # delete_agent(agent_id) 55 | 56 | def run_call_log_examples(): 57 | """Run examples for call log operations""" 58 | print("\n===== RUNNING CALL LOG EXAMPLES =====\n") 59 | 60 | # Get call logs 61 | call_logs = get_call_logs() 62 | 63 | # Example: Get call logs filtered by status (uncomment to use) 64 | # call_logs_filtered = get_call_logs(call_status="completed") 65 | # call_logs_by_agent = get_call_logs(agent_id=123) 66 | 67 | # Get details of the first call log if available 68 | if call_logs.get('call_log_data') and len(call_logs['call_log_data']) > 0: 69 | call_log_id = call_logs['call_log_data'][0]['id'] 70 | get_call_log_details(call_log_id) 71 | else: 72 | print("No call logs found.") 73 | 74 | # Uncomment to dispatch a call 75 | # agents_data = list_agents() 76 | # if agents_data.get('bots') and len(agents_data['bots']) > 0: 77 | # agent_id = agents_data['bots'][0]['id'] 78 | # dispatch_call(agent_id, '+1234567890') 79 | 80 | def run_integration_examples(): 81 | """Run examples for integration operations""" 82 | print("\n===== RUNNING INTEGRATION EXAMPLES =====\n") 83 | 84 | # Get integration format examples 85 | get_integration_format_examples() 86 | 87 | # Get user integrations 88 | get_user_integrations() 89 | 90 | # Create a custom API integration 91 | api_integration = create_custom_api_integration( 92 | name="Weather API Integration", 93 | description="Integration with weather service", 94 | url="https://api.weatherapi.com/v1/current.json", 95 | method="GET", 96 | headers=[ 97 | {"key": "Content-Type", "value": "application/json"} 98 | ], 99 | query_params=[ 100 | 101 | { 102 | "key": "q", 103 | "description": "Location query", 104 | "type": "string", 105 | "required": True, 106 | "isLLMGenerated": True 107 | } 108 | ] 109 | ) 110 | 111 | # Create a Cal.com integration 112 | cal_integration = create_cal_integration( 113 | name="Meeting Scheduler Integration", 114 | description="Integration with Cal.com calendar", 115 | cal_api_key="cal_api_key_example", 116 | cal_id="cal_user_id_example", 117 | cal_timezone="America/New_York" 118 | ) 119 | 120 | # Create integration from JSON 121 | integration_data = { 122 | "name": "CRM API Integration", 123 | "description": "Integration with CRM service", 124 | "url": "https://api.crm-example.com/v1/contacts", 125 | "method": "POST", 126 | "integration_type": "custom_api", 127 | "headers": [ 128 | {"key": "Authorization", "value": "Bearer token123"}, 129 | {"key": "Content-Type", "value": "application/json"} 130 | ], 131 | "body_type": "json", 132 | "body_params": [ 133 | { 134 | "key": "name", 135 | "description": "Contact name", 136 | "type": "string", 137 | "required": True, 138 | "isLLMGenerated": True 139 | }, 140 | { 141 | "key": "email", 142 | "description": "Contact email", 143 | "type": "string", 144 | "required": True, 145 | "isLLMGenerated": True 146 | }, 147 | { 148 | "key": "phone", 149 | "description": "Contact phone number", 150 | "type": "string", 151 | "required": False, 152 | "isLLMGenerated": True 153 | } 154 | ] 155 | } 156 | json_integration = create_integration_from_json(integration_data) 157 | 158 | # Create an agent for integration examples 159 | agent_data = create_agent( 160 | name="Integration Test Agent", 161 | welcome_message="Hello! I'm an agent for testing integrations.", 162 | context_breakdown=[ 163 | {"title": "Purpose", "body": "This agent demonstrates integration capabilities."} 164 | ] 165 | ) 166 | 167 | # Get the agent ID and integration ID 168 | agent_id = agent_data.get('id') 169 | api_integration_id = api_integration.get('id') 170 | 171 | if agent_id and api_integration_id: 172 | # Add integration to agent 173 | add_integration_to_agent(agent_id, api_integration_id) 174 | 175 | # Get agent integrations 176 | get_agent_integrations(agent_id) 177 | 178 | # Remove integration from agent 179 | remove_integration_from_agent(agent_id, api_integration_id) 180 | 181 | def run_knowledge_base_examples(): 182 | """Run examples for knowledge base operations""" 183 | print("\n===== RUNNING KNOWLEDGE BASE EXAMPLES =====\n") 184 | 185 | # List all knowledge base files 186 | list_knowledge_base_files() 187 | 188 | # Check if a file can be uploaded (1MB file) 189 | check_file_upload_capability(1024 * 1024) 190 | 191 | # Get an agent ID for attaching files 192 | agents_data = list_agents() 193 | agent_id = agents_data.get('bots', [{}])[0].get('id') if agents_data.get('bots') and len(agents_data['bots']) > 0 else None 194 | 195 | # Upload a file if sample.pdf exists 196 | try: 197 | file_data = upload_file_to_knowledge_base("sample.pdf") 198 | file_id = file_data.get('file', {}).get('id') 199 | 200 | if file_id and agent_id: 201 | # Attach file to agent 202 | attach_files_to_agent([file_id], agent_id) 203 | 204 | # Detach file from agent 205 | detach_files_from_agent([file_id], agent_id) 206 | 207 | # Delete file from knowledge base 208 | delete_file_from_knowledge_base(file_id) 209 | except FileNotFoundError: 210 | print("sample.pdf not found. Skipping file upload examples.") 211 | 212 | def run_phone_number_examples(): 213 | """Run examples for phone number operations""" 214 | print("\n===== RUNNING PHONE NUMBER EXAMPLES =====\n") 215 | 216 | # List all phone numbers 217 | phone_numbers = list_phone_numbers() 218 | 219 | # Get an agent ID 220 | agents_data = list_agents() 221 | agent_id = agents_data.get('bots', [{}])[0].get('id') if agents_data.get('bots') and len(agents_data['bots']) > 0 else None 222 | 223 | # Get the first phone number ID if available 224 | phone_number_id = None 225 | if phone_numbers.get('phone_numbers') and len(phone_numbers['phone_numbers']) > 0: 226 | phone_number_id = phone_numbers['phone_numbers'][0]['id'] 227 | 228 | if phone_number_id and agent_id: 229 | # Attach phone number to agent 230 | attach_phone_number_to_agent(phone_number_id, agent_id) 231 | 232 | # Detach phone number 233 | detach_phone_number(phone_number_id) 234 | 235 | 236 | # Helper function to pretty print JSON responses 237 | def print_json_response(response, title=None): 238 | """Pretty print JSON responses""" 239 | if title: 240 | print(f"\n=== {title} ===\n") 241 | 242 | if isinstance(response, dict): 243 | # print status if available 244 | status = response.get('status') 245 | if status: 246 | print(f"Status: {status}") 247 | 248 | # handle direct JSON response structure 249 | if 'json' in response: 250 | # Old nested structure 251 | json_data = response.get('json') 252 | if json_data: 253 | print(json.dumps(json_data, indent=2)) 254 | else: 255 | print(json.dumps(response, indent=2)) 256 | else: 257 | # direct structure 258 | print(json.dumps(response, indent=2)) 259 | else: 260 | print(json.dumps(response, indent=2)) 261 | 262 | return response 263 | 264 | # ===== Agent Operations ===== 265 | 266 | def list_agents(page=1, page_size=10): 267 | """List all agents with pagination""" 268 | response = client.agent.list(page=page, page_size=page_size) 269 | return print_json_response(response, "Listing all agents") 270 | 271 | def create_agent(name, welcome_message, context_breakdown): 272 | """Create a new agent with basic configuration""" 273 | response = client.agent.create( 274 | name=name, 275 | welcome_message=welcome_message, 276 | context_breakdown=context_breakdown 277 | ) 278 | return print_json_response(response, f"Creating agent: {name}") 279 | 280 | def create_agent_with_full_config(name, welcome_message, context_breakdown, transcriber=None, model=None, 281 | voice=None, web_search=None, post_call_actions=None, filler=None): 282 | """Create a new agent with full configuration options""" 283 | # Set default configurations if not provided 284 | if transcriber is None: 285 | transcriber = { 286 | "provider": "deepgram_stream", 287 | "silence_timeout_ms": 400 288 | } 289 | 290 | if model is None: 291 | model = { 292 | "model": "gpt-4o-mini", 293 | "temperature": 0.7 294 | } 295 | 296 | if voice is None: 297 | voice = { 298 | "provider": "eleven_labs", 299 | "voice_id": "JBFqnCBsd6RMkjVDRZzb" 300 | } 301 | 302 | if web_search is None: 303 | web_search = { 304 | "enabled": True, 305 | "provider": "DuckDuckGo" 306 | } 307 | 308 | if filler is None: 309 | filler = { 310 | "enabled": True, 311 | "after_sec": 0, 312 | "fillers": ['let me check'], 313 | } 314 | 315 | response = client.agent.create( 316 | name=name, 317 | welcome_message=welcome_message, 318 | context_breakdown=context_breakdown, 319 | transcriber=transcriber, 320 | model=model, 321 | voice=voice, 322 | web_search=web_search, 323 | post_call_actions=post_call_actions, 324 | filler=filler 325 | ) 326 | return print_json_response(response, f"Creating agent with full config: {name}") 327 | 328 | def get_agent(agent_id): 329 | """Get details of a specific agent""" 330 | response = client.agent.get(agent_id) 331 | return print_json_response(response, f"Getting agent details (ID: {agent_id})") 332 | 333 | def update_agent(agent_id, update_data): 334 | """Update an existing agent""" 335 | response = client.agent.update(agent_id, update_data) 336 | return print_json_response(response, f"Updating agent (ID: {agent_id})") 337 | 338 | def delete_agent(agent_id): 339 | """Delete an agent""" 340 | response = client.agent.delete(agent_id) 341 | return print_json_response(response, f"Deleting agent (ID: {agent_id})") 342 | 343 | # ===== Call Log Operations ===== 344 | 345 | def get_call_logs(page=1, page_size=10, agent_id=None, call_status=None): 346 | """Get all call logs with pagination and optional filtering""" 347 | response = client.call.get_call_logs(page=page, page_size=page_size, agent_id=agent_id, call_status=call_status) 348 | return print_json_response(response, "Getting call logs") 349 | 350 | def get_call_log_details(call_log_id): 351 | """Get details of a specific call log""" 352 | response = client.call.get_call_log(call_log_id=call_log_id) 353 | return print_json_response(response, f"Getting call log details (ID: {call_log_id})") 354 | 355 | def dispatch_call(agent_id, to_number): 356 | """Dispatch a call to a specific number using an agent""" 357 | response = client.call.dispatch_call(agent_id=agent_id, to_number=to_number) 358 | return print_json_response(response, f"Dispatching call to {to_number} using agent {agent_id}") 359 | 360 | # ===== Integration Operations ===== 361 | 362 | def get_integration_format_examples(): 363 | """Get examples of integration formats""" 364 | response = client.integrations.get_integration_format_examples() 365 | return print_json_response(response, "Getting integration format examples") 366 | 367 | def get_user_integrations(): 368 | """Get all integrations for the current user""" 369 | response = client.integrations.get_user_integrations() 370 | return print_json_response(response, "Getting user integrations") 371 | 372 | def create_custom_api_integration(name, description, url, method, headers=None, query_params=None, body_params=None): 373 | """Create a custom API integration""" 374 | response = client.integrations.create_custom_api_integration( 375 | name=name, 376 | description=description, 377 | url=url, 378 | method=method, 379 | headers=headers or [], 380 | query_params=query_params or [], 381 | body_params=body_params or [] 382 | ) 383 | return print_json_response(response, f"Creating custom API integration: {name}") 384 | 385 | def create_cal_integration(name, description, cal_api_key, cal_id, cal_timezone): 386 | """Create a Cal.com integration""" 387 | response = client.integrations.create_cal_integration( 388 | name=name, 389 | description=description, 390 | cal_api_key=cal_api_key, 391 | cal_id=cal_id, 392 | cal_timezone=cal_timezone 393 | ) 394 | return print_json_response(response, f"Creating Cal.com integration: {name}") 395 | 396 | def create_integration_from_json(integration_data): 397 | """Create an integration from a JSON configuration""" 398 | response = client.integrations.create_integration_from_json(integration_data) 399 | return print_json_response(response, f"Creating integration from JSON: {integration_data.get('name')}") 400 | 401 | def add_integration_to_agent(agent_id, integration_id): 402 | """Add an integration to an agent""" 403 | response = client.integrations.add_integration_to_agent( 404 | agent_id=agent_id, 405 | integration_id=integration_id 406 | ) 407 | return print_json_response(response, f"Adding integration (ID: {integration_id}) to agent (ID: {agent_id})") 408 | 409 | def get_agent_integrations(agent_id): 410 | """Get all integrations for a specific agent""" 411 | response = client.integrations.get_agent_integrations(agent_id) 412 | return print_json_response(response, f"Getting integrations for agent (ID: {agent_id})") 413 | 414 | def remove_integration_from_agent(agent_id, integration_id): 415 | """Remove an integration from an agent""" 416 | response = client.integrations.remove_integration_from_agent( 417 | agent_id=agent_id, 418 | integration_id=integration_id 419 | ) 420 | return print_json_response(response, f"Removing integration (ID: {integration_id}) from agent (ID: {agent_id})") 421 | 422 | # ===== Knowledge Base Operations ===== 423 | 424 | def list_knowledge_base_files(): 425 | """List all knowledge base files""" 426 | response = client.knowledge_base.list() 427 | return print_json_response(response, "Listing all knowledge base files") 428 | 429 | def check_file_upload_capability(file_size): 430 | """Check if a file of the given size can be uploaded""" 431 | response = client.knowledge_base.can_upload(file_size) 432 | return print_json_response(response, f"Checking if a file of size {file_size} bytes can be uploaded") 433 | 434 | def upload_file_to_knowledge_base(file_path, file_name=None): 435 | """Upload a file to the knowledge base""" 436 | if file_name is None: 437 | file_name = os.path.basename(file_path) 438 | 439 | with open(file_path, "rb") as file: 440 | file_data = base64.b64encode(file.read()).decode('utf-8') 441 | 442 | response = client.knowledge_base.create(file_data, file_name) 443 | return print_json_response(response, f"Uploading file: {file_name}") 444 | 445 | def attach_files_to_agent(file_ids, agent_id): 446 | """Attach files to an agent""" 447 | response = client.knowledge_base.attach(file_ids, agent_id) 448 | return print_json_response(response, f"Attaching files to agent (ID: {agent_id})") 449 | 450 | def detach_files_from_agent(file_ids, agent_id): 451 | """Detach files from an agent""" 452 | response = client.knowledge_base.detach(file_ids, agent_id) 453 | return print_json_response(response, f"Detaching files from agent (ID: {agent_id})") 454 | 455 | def delete_file_from_knowledge_base(file_id): 456 | """Delete a file from the knowledge base""" 457 | response = client.knowledge_base.delete(file_id) 458 | return print_json_response(response, f"Deleting file from knowledge base (ID: {file_id})") 459 | 460 | # ===== Phone Number Operations ===== 461 | 462 | def list_phone_numbers(page=1, page_size=10): 463 | """List all phone numbers with pagination""" 464 | response = client.phone_number.list(page=page, page_size=page_size) 465 | return print_json_response(response, "Listing all phone numbers") 466 | 467 | def attach_phone_number_to_agent(phone_number_id, agent_id): 468 | """Attach a phone number to an agent""" 469 | response = client.phone_number.attach(phone_number_id, agent_id) 470 | return print_json_response(response, f"Attaching phone number (ID: {phone_number_id}) to agent (ID: {agent_id})") 471 | 472 | def detach_phone_number(phone_number_id): 473 | """Detach a phone number from its associated agent""" 474 | response = client.phone_number.detach(phone_number_id) 475 | return print_json_response(response, f"Detaching phone number (ID: {phone_number_id})") 476 | 477 | # ===== Bulk Call Operations ===== 478 | 479 | def run_bulk_call_examples(): 480 | """Run examples for bulk call operations""" 481 | print("\n===== RUNNING BULK CALL EXAMPLES =====\n") 482 | 483 | # Fetch all bulk calls 484 | bulk_calls = fetch_bulk_calls() 485 | 486 | # Get phone number ID for bulk calls 487 | phone_numbers = list_phone_numbers() 488 | phone_number_id = None 489 | if phone_numbers.get('phone_numbers') and len(phone_numbers['phone_numbers']) > 0: 490 | phone_number_id = phone_numbers['phone_numbers'][0]['id'] 491 | print(f"Using phone number ID: {phone_number_id}") 492 | else: 493 | print("No phone numbers found. Please add a phone number first.") 494 | return 495 | 496 | # Create a bulk call campaign 497 | contact_list = [ 498 | { 499 | "phone_number": "+1234567890", 500 | "customer_name": "John Doe", 501 | "product_interest": "Premium Plan" 502 | }, 503 | { 504 | "phone_number": "+1987654321", 505 | "customer_name": "Jane Smith", 506 | "product_interest": "Basic Plan" 507 | } 508 | ] 509 | 510 | # Create immediate bulk call 511 | bulk_call_data = create_bulk_calls( 512 | name="Marketing Campaign - Q4 2024", 513 | contact_list=contact_list, 514 | phone_number_id=phone_number_id 515 | ) 516 | 517 | bulk_call_id = bulk_call_data.get('id') 518 | 519 | if bulk_call_id: 520 | # Get bulk call details 521 | get_bulk_call_details(bulk_call_id) 522 | 523 | # Pause the bulk call 524 | pause_bulk_call(bulk_call_id) 525 | 526 | # Resume the bulk call 527 | resume_bulk_call(bulk_call_id) 528 | 529 | # Reschedule the bulk call 530 | reschedule_bulk_call(bulk_call_id, "2024-12-25 10:00:00", "America/New_York") 531 | 532 | # Cancel the bulk call 533 | cancel_bulk_call(bulk_call_id) 534 | 535 | # Create a scheduled bulk call 536 | scheduled_bulk_call = create_scheduled_bulk_calls( 537 | name="Scheduled Campaign - New Year", 538 | contact_list=contact_list, 539 | phone_number_id=phone_number_id, 540 | scheduled_datetime="2024-12-31 09:00:00", 541 | timezone="America/New_York" 542 | ) 543 | 544 | def fetch_bulk_calls(page=1, page_size=10, status=None): 545 | """Fetch all bulk calls with optional filtering and pagination""" 546 | response = client.bulk_call.fetch_bulk_calls(page=page, page_size=page_size, status=status) 547 | return print_json_response(response, "Fetching bulk calls") 548 | 549 | def create_bulk_calls(name, contact_list, phone_number_id, is_scheduled=False, scheduled_datetime=None, timezone='UTC'): 550 | """Create a new bulk call campaign""" 551 | response = client.bulk_call.create_bulk_calls( 552 | name=name, 553 | contact_list=contact_list, 554 | phone_number_id=phone_number_id, 555 | is_scheduled=is_scheduled, 556 | scheduled_datetime=scheduled_datetime, 557 | timezone=timezone 558 | ) 559 | return print_json_response(response, f"Creating bulk call: {name}") 560 | 561 | def create_scheduled_bulk_calls(name, contact_list, phone_number_id, scheduled_datetime, timezone='UTC'): 562 | """Create a scheduled bulk call campaign""" 563 | response = client.bulk_call.create_bulk_calls( 564 | name=name, 565 | contact_list=contact_list, 566 | phone_number_id=phone_number_id, 567 | is_scheduled=True, 568 | scheduled_datetime=scheduled_datetime, 569 | timezone=timezone 570 | ) 571 | return print_json_response(response, f"Creating scheduled bulk call: {name}") 572 | 573 | def get_bulk_call_details(bulk_call_id): 574 | """Get detailed information about a specific bulk call campaign""" 575 | response = client.bulk_call.detail_bulk_calls(bulk_call_id) 576 | return print_json_response(response, f"Getting bulk call details (ID: {bulk_call_id})") 577 | 578 | def pause_bulk_call(bulk_call_id): 579 | """Pause a bulk call campaign""" 580 | response = client.bulk_call.bulk_calls_actions(bulk_call_id, 'pause') 581 | return print_json_response(response, f"Pausing bulk call (ID: {bulk_call_id})") 582 | 583 | def resume_bulk_call(bulk_call_id): 584 | """Resume a bulk call campaign""" 585 | response = client.bulk_call.bulk_calls_actions(bulk_call_id, 'resume') 586 | return print_json_response(response, f"Resuming bulk call (ID: {bulk_call_id})") 587 | 588 | def reschedule_bulk_call(bulk_call_id, new_scheduled_datetime, new_timezone=None): 589 | """Reschedule a bulk call campaign""" 590 | response = client.bulk_call.bulk_calls_actions( 591 | bulk_call_id, 592 | 'reschedule', 593 | new_timezone=new_timezone, 594 | new_scheduled_datetime=new_scheduled_datetime 595 | ) 596 | return print_json_response(response, f"Rescheduling bulk call (ID: {bulk_call_id})") 597 | 598 | def cancel_bulk_call(bulk_call_id): 599 | """Cancel a bulk call campaign""" 600 | response = client.bulk_call.cancel_bulk_calls(bulk_call_id) 601 | return print_json_response(response, f"Cancelling bulk call (ID: {bulk_call_id})") 602 | 603 | # ===== Providers Operations ===== 604 | 605 | 606 | 607 | def run_providers_examples(): 608 | """run examples for providers operations""" 609 | print("\n===== RUNNING PROVIDERS EXAMPLES =====\n") 610 | 611 | # List all LLM providers 612 | list_all_llms() 613 | 614 | # List all voice providers with pagination 615 | list_all_voices() 616 | 617 | # List all STT providers 618 | list_all_stt() 619 | 620 | # List all TTS providers 621 | list_all_tts() 622 | 623 | # List all providers (comprehensive) 624 | list_all_providers() 625 | 626 | # Search ElevenLabs voices with filters 627 | search_elevenlabs_voices() 628 | 629 | # Filter voices by criteria 630 | filter_voices_by_criteria() 631 | 632 | # Get voice details 633 | get_specific_voice_details() 634 | 635 | # Test pagination and iteration 636 | test_pagination_iteration() 637 | 638 | # Test error handling 639 | test_error_handling_examples() 640 | 641 | 642 | 643 | def list_all_llms(): 644 | """List all available LLM providers""" 645 | response = client.providers.list_llms() 646 | return print_json_response(response, "Listing all LLM providers") 647 | 648 | def list_all_voices(page=1, page_size=30): 649 | """List all available voice providers with pagination""" 650 | response = client.providers.list_voices(page=page, page_size=page_size) 651 | return print_json_response(response, f"Listing all voices (page {page}, size {page_size})") 652 | 653 | def list_all_stt(): 654 | """List all available STT providers""" 655 | response = client.providers.list_stt() 656 | return print_json_response(response, "Listing all STT providers") 657 | 658 | def list_all_tts(): 659 | """List all available TTS providers""" 660 | response = client.providers.list_tts() 661 | return print_json_response(response, "Listing all TTS providers") 662 | 663 | def list_all_providers(): 664 | """List all providers (comprehensive)""" 665 | response = client.providers.list_all() 666 | return print_json_response(response, "Listing all providers (comprehensive)") 667 | 668 | def search_elevenlabs_voices(): 669 | """Search ElevenLabs voices with various filters""" 670 | print("\n--- Searching ElevenLabs voices by search term ---") 671 | response = client.providers.list_voices(provider='eleven_labs', search='excited', gender='male', page_size=10) 672 | print_json_response(response, "ElevenLabs excited male voices") 673 | 674 | print("\n--- Searching ElevenLabs voices by language ---") 675 | response = client.providers.list_voices(provider='eleven_labs', language='en', page_size=10) 676 | print_json_response(response, "ElevenLabs English voices") 677 | 678 | print("\n--- Searching ElevenLabs voices by accent ---") 679 | response = client.providers.list_voices(provider='eleven_labs', accent='american', gender='female', page_size=10) 680 | print_json_response(response, "ElevenLabs American female voices") 681 | 682 | def filter_voices_by_criteria(): 683 | """Demonstrate various voice filtering options""" 684 | print("\n--- Filtering voices by provider ---") 685 | response = client.providers.list_voices(provider='eleven_labs', page_size=20) 686 | print_json_response(response, "ElevenLabs voices only") 687 | 688 | print("\n--- Combined filters ---") 689 | response = client.providers.list_voices( 690 | provider='eleven_labs', 691 | search='professional', 692 | language='en', 693 | accent='british', 694 | gender='male', 695 | page_size=15 696 | ) 697 | print_json_response(response, "ElevenLabs professional British male voices") 698 | 699 | def get_specific_voice_details(): 700 | """Get details of a specific voice""" 701 | # first get some voices to pick an ID from 702 | 703 | voices_response = client.providers.list_voices(page_size=5) 704 | if voices_response.get('voices') and len(voices_response['voices']) > 0: 705 | voice_id = voices_response['voices'][0]['id'] 706 | print(f"\n--- Getting details for voice ID: {voice_id} ---") 707 | response = client.providers.get_voice(voice_id) 708 | print_json_response(response, f"Voice details for ID {voice_id}") 709 | else: 710 | print("No voices found to get details for.") 711 | 712 | def test_pagination_iteration(): 713 | """test pagination and iteration""" 714 | print("\n--- Testing pagination and iteration ---") 715 | 716 | print("\n--- Manual pagination ---") 717 | page = 1 718 | total_voices = 0 719 | while total_voices < 10 and page <= 3: # limit to 3 pages for demo 720 | response = client.providers.list_voices(page=page, page_size=3) 721 | voices = response.get('voices', []) 722 | print(f"Page {page}: {len(voices)} voices") 723 | total_voices += len(voices) 724 | page += 1 725 | 726 | print(f"\n--- Total voices retrieved: {total_voices} ---") 727 | 728 | def test_error_handling_examples(): 729 | """test error handling and edge cases""" 730 | print("\n--- Testing error handling ---") 731 | 732 | print("\n--- Testing invalid parameters ---") 733 | try: 734 | client.providers.list_voices(page=0) 735 | except ValueError as e: 736 | print(f"[ERROR] Invalid page handled: {e}") 737 | 738 | try: 739 | client.providers.list_voices(page_size=150) 740 | except ValueError as e: 741 | print(f"[ERROR] Invalid page_size handled: {e}") 742 | 743 | try: 744 | client.providers.list_voices(provider='eleven_labs', gender='invalid') 745 | except ValueError as e: 746 | print(f"[ERROR] Invalid gender handled: {e}") 747 | 748 | print("\n--- Testing unsupported filters on non-ElevenLabs provider ---") 749 | try: 750 | client.providers.list_voices(provider='google', search='test') 751 | except ValueError as e: 752 | print(f"[ERROR] Unsupported filter handled: {e}") 753 | 754 | print("\n--- Testing rate limiting ---") 755 | try: 756 | # make several rapid requests to potentially trigger rate limiting 757 | for i in range(5): 758 | response = client.providers.list_voices(provider='eleven_labs', page_size=1) 759 | print(f"[INFO] Request {i+1}: Success") 760 | except ValueError as e: 761 | if 'Rate limit exceeded' in str(e): 762 | print(f"[INFO] Rate limiting working: {e}") 763 | else: 764 | print(f"[ERROR] Unexpected error: {e}") 765 | except Exception as e: 766 | print(f"[ERROR] Error: {e}") 767 | 768 | 769 | if __name__ == "__main__": 770 | # Uncomment the function you want to run 771 | # run_agent_examples() 772 | # run_call_log_examples() 773 | # run_integration_examples() 774 | # run_knowledge_base_examples() 775 | # run_phone_number_examples() 776 | # run_bulk_call_examples() 777 | run_providers_examples() 778 | 779 | -------------------------------------------------------------------------------- /omnidim_mcp_server/uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 2 3 | requires-python = ">=3.12" 4 | 5 | [[package]] 6 | name = "annotated-types" 7 | version = "0.7.0" 8 | source = { registry = "https://pypi.org/simple" } 9 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } 10 | wheels = [ 11 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, 12 | ] 13 | 14 | [[package]] 15 | name = "anyio" 16 | version = "4.9.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | dependencies = [ 19 | { name = "idna" }, 20 | { name = "sniffio" }, 21 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 22 | ] 23 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } 24 | wheels = [ 25 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, 26 | ] 27 | 28 | [[package]] 29 | name = "certifi" 30 | version = "2025.4.26" 31 | source = { registry = "https://pypi.org/simple" } 32 | sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } 33 | wheels = [ 34 | { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, 35 | ] 36 | 37 | [[package]] 38 | name = "charset-normalizer" 39 | version = "3.4.2" 40 | source = { registry = "https://pypi.org/simple" } 41 | sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } 42 | wheels = [ 43 | { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, 44 | { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, 45 | { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, 46 | { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, 47 | { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, 48 | { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, 49 | { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, 50 | { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, 51 | { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, 52 | { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, 53 | { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, 54 | { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, 55 | { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, 56 | { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, 57 | { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, 58 | { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, 59 | { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, 60 | { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, 61 | { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, 62 | { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, 63 | { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, 64 | { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, 65 | { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, 66 | { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, 67 | { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, 68 | { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, 69 | { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, 70 | ] 71 | 72 | [[package]] 73 | name = "click" 74 | version = "8.2.0" 75 | source = { registry = "https://pypi.org/simple" } 76 | dependencies = [ 77 | { name = "colorama", marker = "sys_platform == 'win32'" }, 78 | ] 79 | sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" } 80 | wheels = [ 81 | { url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" }, 82 | ] 83 | 84 | [[package]] 85 | name = "colorama" 86 | version = "0.4.6" 87 | source = { registry = "https://pypi.org/simple" } 88 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 89 | wheels = [ 90 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 91 | ] 92 | 93 | [[package]] 94 | name = "exceptiongroup" 95 | version = "1.3.0" 96 | source = { registry = "https://pypi.org/simple" } 97 | dependencies = [ 98 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 99 | ] 100 | sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } 101 | wheels = [ 102 | { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, 103 | ] 104 | 105 | [[package]] 106 | name = "fastmcp" 107 | version = "2.3.3" 108 | source = { registry = "https://pypi.org/simple" } 109 | dependencies = [ 110 | { name = "exceptiongroup" }, 111 | { name = "httpx" }, 112 | { name = "mcp" }, 113 | { name = "openapi-pydantic" }, 114 | { name = "python-dotenv" }, 115 | { name = "rich" }, 116 | { name = "typer" }, 117 | { name = "websockets" }, 118 | ] 119 | sdist = { url = "https://files.pythonhosted.org/packages/ef/78/fd9f82886ecf04826ed3f5bbfe8c5e400ba77d2cee66258f8a19e3ea01c8/fastmcp-2.3.3.tar.gz", hash = "sha256:f13f87e180158e8176074c2a39a363bfd1ad4283a259377463c81c7735619106", size = 990228, upload-time = "2025-05-10T20:16:56.724Z" } 120 | wheels = [ 121 | { url = "https://files.pythonhosted.org/packages/d3/25/d49eb31b653f8f6fcd496e5abaedef9bdd5b71fac102cb9220d5df4d016e/fastmcp-2.3.3-py3-none-any.whl", hash = "sha256:c4dcd809f4e058aea3fd158952ff51294f2392871de3a870183d61cde032787f", size = 92749, upload-time = "2025-05-10T20:16:55.227Z" }, 122 | ] 123 | 124 | [[package]] 125 | name = "h11" 126 | version = "0.16.0" 127 | source = { registry = "https://pypi.org/simple" } 128 | sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } 129 | wheels = [ 130 | { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, 131 | ] 132 | 133 | [[package]] 134 | name = "httpcore" 135 | version = "1.0.9" 136 | source = { registry = "https://pypi.org/simple" } 137 | dependencies = [ 138 | { name = "certifi" }, 139 | { name = "h11" }, 140 | ] 141 | sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } 142 | wheels = [ 143 | { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, 144 | ] 145 | 146 | [[package]] 147 | name = "httpx" 148 | version = "0.28.1" 149 | source = { registry = "https://pypi.org/simple" } 150 | dependencies = [ 151 | { name = "anyio" }, 152 | { name = "certifi" }, 153 | { name = "httpcore" }, 154 | { name = "idna" }, 155 | ] 156 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } 157 | wheels = [ 158 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, 159 | ] 160 | 161 | [[package]] 162 | name = "httpx-sse" 163 | version = "0.4.0" 164 | source = { registry = "https://pypi.org/simple" } 165 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } 166 | wheels = [ 167 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, 168 | ] 169 | 170 | [[package]] 171 | name = "idna" 172 | version = "3.10" 173 | source = { registry = "https://pypi.org/simple" } 174 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } 175 | wheels = [ 176 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, 177 | ] 178 | 179 | [[package]] 180 | name = "markdown-it-py" 181 | version = "3.0.0" 182 | source = { registry = "https://pypi.org/simple" } 183 | dependencies = [ 184 | { name = "mdurl" }, 185 | ] 186 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } 187 | wheels = [ 188 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, 189 | ] 190 | 191 | [[package]] 192 | name = "mcp" 193 | version = "1.8.1" 194 | source = { registry = "https://pypi.org/simple" } 195 | dependencies = [ 196 | { name = "anyio" }, 197 | { name = "httpx" }, 198 | { name = "httpx-sse" }, 199 | { name = "pydantic" }, 200 | { name = "pydantic-settings" }, 201 | { name = "python-multipart" }, 202 | { name = "sse-starlette" }, 203 | { name = "starlette" }, 204 | { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, 205 | ] 206 | sdist = { url = "https://files.pythonhosted.org/packages/7c/13/16b712e8a3be6a736b411df2fc6b4e75eb1d3e99b1cd57a3a1decf17f612/mcp-1.8.1.tar.gz", hash = "sha256:ec0646271d93749f784d2316fb5fe6102fb0d1be788ec70a9e2517e8f2722c0e", size = 265605, upload-time = "2025-05-12T17:33:57.887Z" } 207 | wheels = [ 208 | { url = "https://files.pythonhosted.org/packages/1c/5d/91cf0d40e40ae9ecf8d4004e0f9611eea86085aa0b5505493e0ff53972da/mcp-1.8.1-py3-none-any.whl", hash = "sha256:948e03783859fa35abe05b9b6c0a1d5519be452fc079dc8d7f682549591c1770", size = 119761, upload-time = "2025-05-12T17:33:56.136Z" }, 209 | ] 210 | 211 | [package.optional-dependencies] 212 | cli = [ 213 | { name = "python-dotenv" }, 214 | { name = "typer" }, 215 | ] 216 | 217 | [[package]] 218 | name = "mcp-server-demo" 219 | version = "0.1.0" 220 | source = { virtual = "." } 221 | dependencies = [ 222 | { name = "fastmcp" }, 223 | { name = "httpx" }, 224 | { name = "mcp", extra = ["cli"] }, 225 | { name = "omnidimension" }, 226 | ] 227 | 228 | [package.metadata] 229 | requires-dist = [ 230 | { name = "fastmcp", specifier = ">=2.3.3" }, 231 | { name = "httpx", specifier = ">=0.28.1" }, 232 | { name = "mcp", extras = ["cli"], specifier = ">=1.8.1" }, 233 | { name = "omnidimension", specifier = ">=0.2.0" }, 234 | ] 235 | 236 | [[package]] 237 | name = "mdurl" 238 | version = "0.1.2" 239 | source = { registry = "https://pypi.org/simple" } 240 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } 241 | wheels = [ 242 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, 243 | ] 244 | 245 | [[package]] 246 | name = "omnidimension" 247 | version = "0.2.0" 248 | source = { registry = "https://pypi.org/simple" } 249 | dependencies = [ 250 | { name = "requests" }, 251 | ] 252 | sdist = { url = "https://files.pythonhosted.org/packages/4a/fd/8fd47ce55825c68f984b7a73b26d9acd9302d4d0ab906fcab62a2cfa258b/omnidimension-0.2.0.tar.gz", hash = "sha256:a9388fa85e796acd175912fa2bee76d5c9cad54867a162b7a744943e618ec429", size = 14367, upload-time = "2025-05-12T13:51:33.734Z" } 253 | wheels = [ 254 | { url = "https://files.pythonhosted.org/packages/f6/34/0a350278b57d46db22ec839ab72d16c1623243c441effeb5ae48f2cacc92/omnidimension-0.2.0-py3-none-any.whl", hash = "sha256:b544ba6905282041d74572e09c783ec998eb440946d0cc153d60416adcb0f153", size = 18288, upload-time = "2025-05-12T13:51:32.576Z" }, 255 | ] 256 | 257 | [[package]] 258 | name = "openapi-pydantic" 259 | version = "0.5.1" 260 | source = { registry = "https://pypi.org/simple" } 261 | dependencies = [ 262 | { name = "pydantic" }, 263 | ] 264 | sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" } 265 | wheels = [ 266 | { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" }, 267 | ] 268 | 269 | [[package]] 270 | name = "pydantic" 271 | version = "2.11.4" 272 | source = { registry = "https://pypi.org/simple" } 273 | dependencies = [ 274 | { name = "annotated-types" }, 275 | { name = "pydantic-core" }, 276 | { name = "typing-extensions" }, 277 | { name = "typing-inspection" }, 278 | ] 279 | sdist = { url = "https://files.pythonhosted.org/packages/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540, upload-time = "2025-04-29T20:38:55.02Z" } 280 | wheels = [ 281 | { url = "https://files.pythonhosted.org/packages/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900, upload-time = "2025-04-29T20:38:52.724Z" }, 282 | ] 283 | 284 | [[package]] 285 | name = "pydantic-core" 286 | version = "2.33.2" 287 | source = { registry = "https://pypi.org/simple" } 288 | dependencies = [ 289 | { name = "typing-extensions" }, 290 | ] 291 | sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } 292 | wheels = [ 293 | { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, 294 | { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, 295 | { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, 296 | { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, 297 | { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, 298 | { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, 299 | { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, 300 | { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, 301 | { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, 302 | { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, 303 | { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, 304 | { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, 305 | { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, 306 | { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, 307 | { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, 308 | { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, 309 | { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, 310 | { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, 311 | { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, 312 | { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, 313 | { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, 314 | { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, 315 | { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, 316 | { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, 317 | { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, 318 | { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, 319 | { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, 320 | { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, 321 | { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, 322 | { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, 323 | { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, 324 | ] 325 | 326 | [[package]] 327 | name = "pydantic-settings" 328 | version = "2.9.1" 329 | source = { registry = "https://pypi.org/simple" } 330 | dependencies = [ 331 | { name = "pydantic" }, 332 | { name = "python-dotenv" }, 333 | { name = "typing-inspection" }, 334 | ] 335 | sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" } 336 | wheels = [ 337 | { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, 338 | ] 339 | 340 | [[package]] 341 | name = "pygments" 342 | version = "2.19.1" 343 | source = { registry = "https://pypi.org/simple" } 344 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } 345 | wheels = [ 346 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, 347 | ] 348 | 349 | [[package]] 350 | name = "python-dotenv" 351 | version = "1.1.0" 352 | source = { registry = "https://pypi.org/simple" } 353 | sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } 354 | wheels = [ 355 | { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, 356 | ] 357 | 358 | [[package]] 359 | name = "python-multipart" 360 | version = "0.0.20" 361 | source = { registry = "https://pypi.org/simple" } 362 | sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } 363 | wheels = [ 364 | { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, 365 | ] 366 | 367 | [[package]] 368 | name = "requests" 369 | version = "2.32.3" 370 | source = { registry = "https://pypi.org/simple" } 371 | dependencies = [ 372 | { name = "certifi" }, 373 | { name = "charset-normalizer" }, 374 | { name = "idna" }, 375 | { name = "urllib3" }, 376 | ] 377 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } 378 | wheels = [ 379 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, 380 | ] 381 | 382 | [[package]] 383 | name = "rich" 384 | version = "14.0.0" 385 | source = { registry = "https://pypi.org/simple" } 386 | dependencies = [ 387 | { name = "markdown-it-py" }, 388 | { name = "pygments" }, 389 | ] 390 | sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } 391 | wheels = [ 392 | { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, 393 | ] 394 | 395 | [[package]] 396 | name = "shellingham" 397 | version = "1.5.4" 398 | source = { registry = "https://pypi.org/simple" } 399 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } 400 | wheels = [ 401 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, 402 | ] 403 | 404 | [[package]] 405 | name = "sniffio" 406 | version = "1.3.1" 407 | source = { registry = "https://pypi.org/simple" } 408 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } 409 | wheels = [ 410 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, 411 | ] 412 | 413 | [[package]] 414 | name = "sse-starlette" 415 | version = "2.3.5" 416 | source = { registry = "https://pypi.org/simple" } 417 | dependencies = [ 418 | { name = "anyio" }, 419 | { name = "starlette" }, 420 | ] 421 | sdist = { url = "https://files.pythonhosted.org/packages/10/5f/28f45b1ff14bee871bacafd0a97213f7ec70e389939a80c60c0fb72a9fc9/sse_starlette-2.3.5.tar.gz", hash = "sha256:228357b6e42dcc73a427990e2b4a03c023e2495ecee82e14f07ba15077e334b2", size = 17511, upload-time = "2025-05-12T18:23:52.601Z" } 422 | wheels = [ 423 | { url = "https://files.pythonhosted.org/packages/c8/48/3e49cf0f64961656402c0023edbc51844fe17afe53ab50e958a6dbbbd499/sse_starlette-2.3.5-py3-none-any.whl", hash = "sha256:251708539a335570f10eaaa21d1848a10c42ee6dc3a9cf37ef42266cdb1c52a8", size = 10233, upload-time = "2025-05-12T18:23:50.722Z" }, 424 | ] 425 | 426 | [[package]] 427 | name = "starlette" 428 | version = "0.46.2" 429 | source = { registry = "https://pypi.org/simple" } 430 | dependencies = [ 431 | { name = "anyio" }, 432 | ] 433 | sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } 434 | wheels = [ 435 | { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, 436 | ] 437 | 438 | [[package]] 439 | name = "typer" 440 | version = "0.15.3" 441 | source = { registry = "https://pypi.org/simple" } 442 | dependencies = [ 443 | { name = "click" }, 444 | { name = "rich" }, 445 | { name = "shellingham" }, 446 | { name = "typing-extensions" }, 447 | ] 448 | sdist = { url = "https://files.pythonhosted.org/packages/98/1a/5f36851f439884bcfe8539f6a20ff7516e7b60f319bbaf69a90dc35cc2eb/typer-0.15.3.tar.gz", hash = "sha256:818873625d0569653438316567861899f7e9972f2e6e0c16dab608345ced713c", size = 101641, upload-time = "2025-04-28T21:40:59.204Z" } 449 | wheels = [ 450 | { url = "https://files.pythonhosted.org/packages/48/20/9d953de6f4367163d23ec823200eb3ecb0050a2609691e512c8b95827a9b/typer-0.15.3-py3-none-any.whl", hash = "sha256:c86a65ad77ca531f03de08d1b9cb67cd09ad02ddddf4b34745b5008f43b239bd", size = 45253, upload-time = "2025-04-28T21:40:56.269Z" }, 451 | ] 452 | 453 | [[package]] 454 | name = "typing-extensions" 455 | version = "4.13.2" 456 | source = { registry = "https://pypi.org/simple" } 457 | sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } 458 | wheels = [ 459 | { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, 460 | ] 461 | 462 | [[package]] 463 | name = "typing-inspection" 464 | version = "0.4.0" 465 | source = { registry = "https://pypi.org/simple" } 466 | dependencies = [ 467 | { name = "typing-extensions" }, 468 | ] 469 | sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } 470 | wheels = [ 471 | { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, 472 | ] 473 | 474 | [[package]] 475 | name = "urllib3" 476 | version = "2.4.0" 477 | source = { registry = "https://pypi.org/simple" } 478 | sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } 479 | wheels = [ 480 | { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, 481 | ] 482 | 483 | [[package]] 484 | name = "uvicorn" 485 | version = "0.34.2" 486 | source = { registry = "https://pypi.org/simple" } 487 | dependencies = [ 488 | { name = "click" }, 489 | { name = "h11" }, 490 | ] 491 | sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" } 492 | wheels = [ 493 | { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" }, 494 | ] 495 | 496 | [[package]] 497 | name = "websockets" 498 | version = "15.0.1" 499 | source = { registry = "https://pypi.org/simple" } 500 | sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } 501 | wheels = [ 502 | { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, 503 | { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, 504 | { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, 505 | { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, 506 | { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, 507 | { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, 508 | { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, 509 | { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, 510 | { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, 511 | { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, 512 | { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, 513 | { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, 514 | { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, 515 | { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, 516 | { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, 517 | { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, 518 | { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, 519 | { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, 520 | { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, 521 | { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, 522 | { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, 523 | { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, 524 | { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, 525 | ] 526 | --------------------------------------------------------------------------------