diff --git a/README.md b/README.md index f8235d9..48cbdc5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,29 @@ pip install dimo-python-sdk ## Unit Testing -Coming Soon +The SDK includes comprehensive unit tests to ensure reliability and correctness. To run the tests: + +1. **Install dependencies:** + ```bash + pip install -r requirements.txt + ``` + +2. **Run all tests:** + ```bash + pytest + ``` + +3. **Run tests with verbose output:** + ```bash + pytest -v + ``` + +4. **Run specific test files:** + ```bash + pytest tests/test_conversations.py -v + ``` + +The test suite uses `pytest` and includes tests for all major SDK functionality including authentication, API endpoints, GraphQL queries, and error handling ## API Documentation diff --git a/dimo/api/conversations.py b/dimo/api/conversations.py index 1f0ec59..66fef6f 100644 --- a/dimo/api/conversations.py +++ b/dimo/api/conversations.py @@ -44,25 +44,29 @@ def health_check(self) -> Dict: def create_agent( self, developer_jwt: str, - user: str, - vehicle_ids: Optional[List[int]] = None, + api_key: str, + user_wallet: str, + agent_type: str, + vehicle_ids: Optional[str] = None, + personality: str = "uncle_mechanic", ) -> Dict: """ - Create a new conversational agent for a user with optional vehicle access. + Create a new conversational agent with the specified configuration. Args: developer_jwt (str): Developer JWT token for authentication - user (str): Wallet address (0x...) or email identifying the user - vehicle_ids (list[int], optional): List of vehicle token IDs this agent can access. - - None (default): Unrestricted access, ownership validated at runtime - - []: Empty list means no vehicle access (identity queries only) - - [872, 1234]: Explicit list of allowed vehicles + api_key (str): DIMO API key for the agent to access vehicle data + user_wallet (str): User's wallet address (e.g., "0x2345...") + agent_type (str): The type of agent to create (e.g., "driver_agent_v1") + vehicle_ids (str, optional): JSON array string of vehicle token IDs (e.g., "[1, 2, 3]"). + If not provided, agent will have access to all vehicles owned by the user. + personality (str, optional): Personality preset for the agent. Defaults to "uncle_mechanic" Returns: - dict: Agent information including agentId, mode, user, vehicleIds, and createdAt + dict: Agent information including agentId and configuration details Behavior: - - One agent per user (idempotent creation) + - Creates a new agent with the specified type and configuration - Validates configuration and mode detection - Creates/reuses shared identity subagent - Creates per-vehicle telemetry subagents with token exchange @@ -73,20 +77,31 @@ def create_agent( >>> dev_jwt = "your_developer_jwt" >>> agent = dimo.conversations.create_agent( ... developer_jwt=dev_jwt, - ... user="0x1234567890abcdef1234567890abcdef12345678", - ... vehicle_ids=[872, 1234], + ... api_key="0x1234567890abcdef...", + ... user_wallet="0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605", + ... agent_type="driver_agent_v1", + ... vehicle_ids="[1, 2, 3]", ... ) >>> print(agent['agentId']) """ check_type("developer_jwt", developer_jwt, str) - check_type("user", user, str) - check_optional_type("vehicle_ids", vehicle_ids, list) - # check_type("enable_websearch", enable_websearch, bool) + check_type("api_key", api_key, str) + check_type("user_wallet", user_wallet, str) + check_optional_type("vehicle_ids", vehicle_ids, str) + check_type("agent_type", agent_type, str) + check_type("personality", personality, str) + + # Build variables dict + variables = {"USER_WALLET": user_wallet} + if vehicle_ids is not None: + variables["VEHICLE_IDS"] = vehicle_ids + # Build request body body = { - "user": user, - "vehicleIds": vehicle_ids, - # "enableWebsearch": enable_websearch, + "personality": personality, + "secrets": {"DIMO_API_KEY": api_key}, + "type": agent_type, + "variables": variables, } response = self._request( diff --git a/pyproject.toml b/pyproject.toml index 756107d..74c22d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "dimo-python-sdk" -version = "1.7.0" +version = "1.7.1" authors = [ { name="Barrett Kowalsky", email="barrettkowalsky@gmail.com" }, ] diff --git a/tests/test_conversations.py b/tests/test_conversations.py index 186a42c..0cafc31 100644 --- a/tests/test_conversations.py +++ b/tests/test_conversations.py @@ -47,25 +47,27 @@ class TestConversationsCreateAgent: """Test the create_agent endpoint.""" def test_create_agent_minimal(self, monkeypatch): - """Test creating an agent with minimal parameters.""" + """Test creating an agent with minimal required parameters (no vehicle_ids).""" client = DIMO(env="Dev") # Mock the request method fake_request = MagicMock(return_value={ "agentId": "agent-abc123", - "user": "0x1234567890abcdef1234567890abcdef12345678", - "vehicleIds": None, - "mode": "unrestricted", + "type": "driver_agent_v1", + "personality": "uncle_mechanic", "createdAt": "2024-01-01T00:00:00Z" }) monkeypatch.setattr(client, "request", fake_request) dev_jwt = "test_developer_jwt" - user = "0x1234567890abcdef1234567890abcdef12345678" + api_key = "0x1234567890abcdef" + user_wallet = "0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605" result = client.conversations.create_agent( developer_jwt=dev_jwt, - user=user + api_key=api_key, + user_wallet=user_wallet, + agent_type="driver_agent_v1" ) # Verify the request was called correctly @@ -75,13 +77,15 @@ def test_create_agent_minimal(self, monkeypatch): assert args[0] == "POST" assert args[1] == "Conversations" assert args[2] == "/agents" - assert kwargs["data"]["user"] == user - assert kwargs["data"]["vehicleIds"] is None + assert kwargs["data"]["type"] == "driver_agent_v1" + assert kwargs["data"]["personality"] == "uncle_mechanic" + assert kwargs["data"]["secrets"]["DIMO_API_KEY"] == api_key + assert kwargs["data"]["variables"]["USER_WALLET"] == user_wallet + assert "VEHICLE_IDS" not in kwargs["data"]["variables"] # Verify the response assert result["agentId"] == "agent-abc123" - assert result["user"] == user - assert result["mode"] == "unrestricted" + assert result["type"] == "driver_agent_v1" def test_create_agent_with_vehicle_ids(self, monkeypatch): """Test creating an agent with specific vehicle IDs.""" @@ -89,52 +93,88 @@ def test_create_agent_with_vehicle_ids(self, monkeypatch): fake_request = MagicMock(return_value={ "agentId": "agent-def456", - "user": "user@example.com", - "vehicleIds": [872, 1234], - "mode": "restricted", + "type": "driver_agent_v1", "createdAt": "2024-01-01T00:00:00Z" }) monkeypatch.setattr(client, "request", fake_request) dev_jwt = "test_developer_jwt" - user = "user@example.com" - vehicle_ids = [872, 1234] + api_key = "0xabcdef123456" + user_wallet = "0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605" + vehicle_ids = "[872, 1234]" result = client.conversations.create_agent( developer_jwt=dev_jwt, - user=user, + api_key=api_key, + user_wallet=user_wallet, + agent_type="driver_agent_v1", vehicle_ids=vehicle_ids ) # Verify the request args, kwargs = fake_request.call_args - assert kwargs["data"]["vehicleIds"] == [872, 1234] + assert kwargs["data"]["secrets"]["DIMO_API_KEY"] == api_key + assert kwargs["data"]["variables"]["USER_WALLET"] == user_wallet + assert kwargs["data"]["variables"]["VEHICLE_IDS"] == vehicle_ids # Verify the response - assert result["vehicleIds"] == vehicle_ids - assert result["mode"] == "restricted" + assert result["agentId"] == "agent-def456" - def test_create_agent_with_empty_vehicle_list(self, monkeypatch): - """Test creating an agent with empty vehicle list (identity only).""" + def test_create_agent_with_custom_personality(self, monkeypatch): + """Test creating an agent with custom personality preset.""" client = DIMO(env="Dev") fake_request = MagicMock(return_value={ "agentId": "agent-ghi789", - "user": "0xabcdef", - "vehicleIds": [], - "mode": "identity_only", + "type": "driver_agent_v1", + "personality": "helpful_assistant", "createdAt": "2024-01-01T00:00:00Z" }) monkeypatch.setattr(client, "request", fake_request) result = client.conversations.create_agent( developer_jwt="test_jwt", - user="0xabcdef", - vehicle_ids=[] + api_key="0xapikey", + user_wallet="0xwallet", + agent_type="driver_agent_v1", + personality="helpful_assistant" ) - assert result["vehicleIds"] == [] - assert result["mode"] == "identity_only" + # Verify the request + args, kwargs = fake_request.call_args + assert kwargs["data"]["personality"] == "helpful_assistant" + + # Verify the response + assert result["personality"] == "helpful_assistant" + + def test_create_agent_full_config(self, monkeypatch): + """Test creating an agent with all configuration options.""" + client = DIMO(env="Dev") + + fake_request = MagicMock(return_value={ + "agentId": "agent-full123", + "type": "driver_agent_v1", + "personality": "uncle_mechanic", + "createdAt": "2024-01-01T00:00:00Z" + }) + monkeypatch.setattr(client, "request", fake_request) + + result = client.conversations.create_agent( + developer_jwt="test_jwt", + api_key="0x1234567890abcdef", + user_wallet="0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605", + vehicle_ids="[1, 2, 3]", + agent_type="driver_agent_v1", + personality="uncle_mechanic" + ) + + # Verify all fields are in request + args, kwargs = fake_request.call_args + assert kwargs["data"]["type"] == "driver_agent_v1" + assert kwargs["data"]["personality"] == "uncle_mechanic" + assert kwargs["data"]["secrets"]["DIMO_API_KEY"] == "0x1234567890abcdef" + assert kwargs["data"]["variables"]["USER_WALLET"] == "0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605" + assert kwargs["data"]["variables"]["VEHICLE_IDS"] == "[1, 2, 3]" def test_create_agent_invalid_types(self): """Test that type checking is enforced for parameters.""" @@ -144,22 +184,56 @@ def test_create_agent_invalid_types(self): with pytest.raises(DimoTypeError): client.conversations.create_agent( developer_jwt=123, # Should be string - user="0xabcdef" + api_key="0xapikey", + user_wallet="0xwallet", + agent_type="driver_agent_v1" + ) + + # Test invalid api_key type + with pytest.raises(DimoTypeError): + client.conversations.create_agent( + developer_jwt="test_jwt", + api_key=123, # Should be string + user_wallet="0xwallet", + agent_type="driver_agent_v1" + ) + + # Test invalid user_wallet type + with pytest.raises(DimoTypeError): + client.conversations.create_agent( + developer_jwt="test_jwt", + api_key="0xapikey", + user_wallet=123, # Should be string + agent_type="driver_agent_v1" ) - # Test invalid user type + # Test invalid agent_type type with pytest.raises(DimoTypeError): client.conversations.create_agent( developer_jwt="test_jwt", - user=123 # Should be string + api_key="0xapikey", + user_wallet="0xwallet", + agent_type=123 # Should be string ) # Test invalid vehicle_ids type with pytest.raises(DimoTypeError): client.conversations.create_agent( developer_jwt="test_jwt", - user="0xabcdef", - vehicle_ids="not_a_list" # Should be list or None + api_key="0xapikey", + user_wallet="0xwallet", + agent_type="driver_agent_v1", + vehicle_ids=123 # Should be string or None + ) + + # Test invalid personality type + with pytest.raises(DimoTypeError): + client.conversations.create_agent( + developer_jwt="test_jwt", + api_key="0xapikey", + user_wallet="0xwallet", + agent_type="driver_agent_v1", + personality=123 # Should be string ) @@ -573,8 +647,8 @@ def fake_request(*args, **kwargs): if args[0] == "POST" and args[2] == "/agents": return { "agentId": "agent-test123", - "user": "0xuser", - "vehicleIds": [872], + "type": "driver_agent_v1", + "personality": "uncle_mechanic", "createdAt": "2024-01-01T00:00:00Z" } elif args[0] == "POST" and "/message" in args[2]: @@ -592,8 +666,10 @@ def fake_request(*args, **kwargs): # 1. Create agent agent = client.conversations.create_agent( developer_jwt="test_jwt", - user="0xuser", - vehicle_ids=[872] + api_key="0x1234567890abcdef", + user_wallet="0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605", + agent_type="driver_agent_v1", + vehicle_ids="[872]" ) assert agent["agentId"] == "agent-test123" assert ("POST", "/agents") in calls_made