dimo-python-sdk 1.6.0__tar.gz → 1.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/PKG-INFO +24 -2
  2. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/README.md +23 -1
  3. dimo_python_sdk-1.7.1/dimo/api/conversations.py +343 -0
  4. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/dimo.py +2 -0
  5. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/environments.py +2 -0
  6. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo_python_sdk.egg-info/PKG-INFO +24 -2
  7. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo_python_sdk.egg-info/SOURCES.txt +2 -0
  8. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/pyproject.toml +1 -1
  9. dimo_python_sdk-1.7.1/tests/test_conversations.py +693 -0
  10. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/LICENSE +0 -0
  11. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/__init__.py +0 -0
  12. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/api/__init__.py +0 -0
  13. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/api/attestation.py +0 -0
  14. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/api/auth.py +0 -0
  15. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/api/device_definitions.py +0 -0
  16. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/api/token_exchange.py +0 -0
  17. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/api/trips.py +0 -0
  18. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/api/valuations.py +0 -0
  19. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/api/vehicle_triggers.py +0 -0
  20. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/constants.py +0 -0
  21. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/errors.py +0 -0
  22. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/eth_signer.py +0 -0
  23. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/graphql/__init__.py +0 -0
  24. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/graphql/identity.py +0 -0
  25. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/graphql/telemetry.py +0 -0
  26. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/permission_decoder.py +0 -0
  27. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo/request.py +0 -0
  28. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo_python_sdk.egg-info/dependency_links.txt +0 -0
  29. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo_python_sdk.egg-info/requires.txt +0 -0
  30. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/dimo_python_sdk.egg-info/top_level.txt +0 -0
  31. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/setup.cfg +0 -0
  32. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/tests/test_dimo.py +0 -0
  33. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/tests/test_errors.py +0 -0
  34. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/tests/test_permission_decoder.py +0 -0
  35. {dimo_python_sdk-1.6.0 → dimo_python_sdk-1.7.1}/tests/test_request.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dimo-python-sdk
3
- Version: 1.6.0
3
+ Version: 1.7.1
4
4
  Summary: DIMO SDK in Python
5
5
  Author-email: Barrett Kowalsky <barrettkowalsky@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/DIMO-Network/dimo-python-sdk
@@ -28,7 +28,29 @@ pip install dimo-python-sdk
28
28
 
29
29
  ## Unit Testing
30
30
 
31
- Coming Soon
31
+ The SDK includes comprehensive unit tests to ensure reliability and correctness. To run the tests:
32
+
33
+ 1. **Install dependencies:**
34
+ ```bash
35
+ pip install -r requirements.txt
36
+ ```
37
+
38
+ 2. **Run all tests:**
39
+ ```bash
40
+ pytest
41
+ ```
42
+
43
+ 3. **Run tests with verbose output:**
44
+ ```bash
45
+ pytest -v
46
+ ```
47
+
48
+ 4. **Run specific test files:**
49
+ ```bash
50
+ pytest tests/test_conversations.py -v
51
+ ```
52
+
53
+ The test suite uses `pytest` and includes tests for all major SDK functionality including authentication, API endpoints, GraphQL queries, and error handling
32
54
 
33
55
  ## API Documentation
34
56
 
@@ -10,7 +10,29 @@ pip install dimo-python-sdk
10
10
 
11
11
  ## Unit Testing
12
12
 
13
- Coming Soon
13
+ The SDK includes comprehensive unit tests to ensure reliability and correctness. To run the tests:
14
+
15
+ 1. **Install dependencies:**
16
+ ```bash
17
+ pip install -r requirements.txt
18
+ ```
19
+
20
+ 2. **Run all tests:**
21
+ ```bash
22
+ pytest
23
+ ```
24
+
25
+ 3. **Run tests with verbose output:**
26
+ ```bash
27
+ pytest -v
28
+ ```
29
+
30
+ 4. **Run specific test files:**
31
+ ```bash
32
+ pytest tests/test_conversations.py -v
33
+ ```
34
+
35
+ The test suite uses `pytest` and includes tests for all major SDK functionality including authentication, API endpoints, GraphQL queries, and error handling
14
36
 
15
37
  ## API Documentation
16
38
 
@@ -0,0 +1,343 @@
1
+ from dimo.errors import check_type, check_optional_type, HTTPError
2
+ from typing import Dict, List, Optional, Any, Generator
3
+ from requests import Session, RequestException
4
+ import json
5
+
6
+
7
+ class Conversations:
8
+ """
9
+ Client for the DIMO Conversations API.
10
+
11
+ This API enables developers to create conversational AI agents that can query
12
+ vehicle data, telemetry data, and perform web searches on behalf of users.
13
+
14
+ Key Features:
15
+ - Create AI agents with access to specific vehicles
16
+ - Query vehicle identity (make, model, owner) via GraphQL
17
+ - Query real-time telemetry (speed, fuel, location) via GraphQL
18
+ - Perform location-based web searches
19
+ - Stream responses in real-time using Server-Sent Events (SSE)
20
+ - Multi-agent delegation architecture with specialized subagents
21
+ """
22
+
23
+ def __init__(self, request_method, get_auth_headers, get_full_path, session: Session):
24
+ self._request = request_method
25
+ self._get_auth_headers = get_auth_headers
26
+ self._get_full_path = get_full_path
27
+ self._session = session
28
+
29
+ def health_check(self) -> Dict:
30
+ """
31
+ Check the service status and configuration.
32
+
33
+ Returns:
34
+ dict: Service health information including status, version, proxy, and default_model
35
+
36
+ Example:
37
+ >>> dimo = DIMO("Production")
38
+ >>> health = dimo.conversations.health_check()
39
+ >>> print(health['status'])
40
+ """
41
+ response = self._request("GET", "Conversations", "/")
42
+ return response
43
+
44
+ def create_agent(
45
+ self,
46
+ developer_jwt: str,
47
+ api_key: str,
48
+ user_wallet: str,
49
+ agent_type: str,
50
+ vehicle_ids: Optional[str] = None,
51
+ personality: str = "uncle_mechanic",
52
+ ) -> Dict:
53
+ """
54
+ Create a new conversational agent with the specified configuration.
55
+
56
+ Args:
57
+ developer_jwt (str): Developer JWT token for authentication
58
+ api_key (str): DIMO API key for the agent to access vehicle data
59
+ user_wallet (str): User's wallet address (e.g., "0x2345...")
60
+ agent_type (str): The type of agent to create (e.g., "driver_agent_v1")
61
+ vehicle_ids (str, optional): JSON array string of vehicle token IDs (e.g., "[1, 2, 3]").
62
+ If not provided, agent will have access to all vehicles owned by the user.
63
+ personality (str, optional): Personality preset for the agent. Defaults to "uncle_mechanic"
64
+
65
+ Returns:
66
+ dict: Agent information including agentId and configuration details
67
+
68
+ Behavior:
69
+ - Creates a new agent with the specified type and configuration
70
+ - Validates configuration and mode detection
71
+ - Creates/reuses shared identity subagent
72
+ - Creates per-vehicle telemetry subagents with token exchange
73
+ - Creates shared websearch subagent if enabled
74
+
75
+ Example:
76
+ >>> dimo = DIMO("Production")
77
+ >>> dev_jwt = "your_developer_jwt"
78
+ >>> agent = dimo.conversations.create_agent(
79
+ ... developer_jwt=dev_jwt,
80
+ ... api_key="0x1234567890abcdef...",
81
+ ... user_wallet="0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605",
82
+ ... agent_type="driver_agent_v1",
83
+ ... vehicle_ids="[1, 2, 3]",
84
+ ... )
85
+ >>> print(agent['agentId'])
86
+ """
87
+ check_type("developer_jwt", developer_jwt, str)
88
+ check_type("api_key", api_key, str)
89
+ check_type("user_wallet", user_wallet, str)
90
+ check_optional_type("vehicle_ids", vehicle_ids, str)
91
+ check_type("agent_type", agent_type, str)
92
+ check_type("personality", personality, str)
93
+
94
+ # Build variables dict
95
+ variables = {"USER_WALLET": user_wallet}
96
+ if vehicle_ids is not None:
97
+ variables["VEHICLE_IDS"] = vehicle_ids
98
+
99
+ # Build request body
100
+ body = {
101
+ "personality": personality,
102
+ "secrets": {"DIMO_API_KEY": api_key},
103
+ "type": agent_type,
104
+ "variables": variables,
105
+ }
106
+
107
+ response = self._request(
108
+ "POST",
109
+ "Conversations",
110
+ "/agents",
111
+ headers=self._get_auth_headers(developer_jwt),
112
+ data=body,
113
+ )
114
+ return response
115
+
116
+ def delete_agent(self, developer_jwt: str, agent_id: str) -> Dict:
117
+ """
118
+ Delete an agent and all associated resources.
119
+
120
+ Args:
121
+ developer_jwt (str): Developer JWT token for authentication
122
+ agent_id (str): The agent ID to delete
123
+
124
+ Returns:
125
+ dict: Confirmation message
126
+
127
+ Behavior:
128
+ - Deletes Letta agent from server
129
+ - Removes metadata from AgentManager
130
+ - Cleanup errors are logged but don't fail the request
131
+
132
+ Example:
133
+ >>> dimo = DIMO("Production")
134
+ >>> dev_jwt = "your_developer_jwt"
135
+ >>> result = dimo.conversations.delete_agent(
136
+ ... developer_jwt=dev_jwt,
137
+ ... agent_id="agent-abc123"
138
+ ... )
139
+ >>> print(result['message'])
140
+ """
141
+ check_type("developer_jwt", developer_jwt, str)
142
+ check_type("agent_id", agent_id, str)
143
+
144
+ response = self._request(
145
+ "DELETE",
146
+ "Conversations",
147
+ f"/agents/{agent_id}",
148
+ headers=self._get_auth_headers(developer_jwt),
149
+ )
150
+ return response
151
+
152
+ def send_message(
153
+ self,
154
+ developer_jwt: str,
155
+ agent_id: str,
156
+ message: str,
157
+ vehicle_ids: Optional[List[int]] = None,
158
+ user: Optional[str] = None,
159
+ ) -> Dict:
160
+ """
161
+ Send a message to an agent and receive the complete response (synchronous).
162
+
163
+ Args:
164
+ developer_jwt (str): Developer JWT token for authentication
165
+ agent_id (str): The agent ID to send the message to
166
+ message (str): The message to send to the agent
167
+ vehicle_ids (list[int], optional): Optional vehicle IDs override
168
+ user (str, optional): Optional user override
169
+
170
+ Returns:
171
+ dict: Response including agentId, message, response, vehiclesQueried, and timestamp
172
+
173
+ Behavior:
174
+ - Synchronous request/response
175
+ - Agent delegates to subagents as needed
176
+ - Returns full response after agent completes reasoning
177
+ - Timeout: 120 seconds for complex queries
178
+
179
+ Example:
180
+ >>> dimo = DIMO("Production")
181
+ >>> dev_jwt = "your_developer_jwt"
182
+ >>> response = dimo.conversations.send_message(
183
+ ... developer_jwt=dev_jwt,
184
+ ... agent_id="agent-abc123",
185
+ ... message="What's the make and model of my vehicle?"
186
+ ... )
187
+ >>> print(response['response'])
188
+ """
189
+ check_type("developer_jwt", developer_jwt, str)
190
+ check_type("agent_id", agent_id, str)
191
+ check_type("message", message, str)
192
+ check_optional_type("vehicle_ids", vehicle_ids, list)
193
+ check_optional_type("user", user, str)
194
+
195
+ body = {"message": message}
196
+ if vehicle_ids is not None:
197
+ body["vehicleIds"] = vehicle_ids
198
+ if user is not None:
199
+ body["user"] = user
200
+
201
+ response = self._request(
202
+ "POST",
203
+ "Conversations",
204
+ f"/agents/{agent_id}/message",
205
+ headers=self._get_auth_headers(developer_jwt),
206
+ data=body,
207
+ )
208
+ return response
209
+
210
+ def stream_message(
211
+ self,
212
+ developer_jwt: str,
213
+ agent_id: str,
214
+ message: str,
215
+ vehicle_ids: Optional[List[int]] = None,
216
+ user: Optional[str] = None,
217
+ ) -> Generator[Dict[str, Any], None, None]:
218
+ """
219
+ Send a message and receive real-time token-by-token streaming response via SSE.
220
+
221
+ Args:
222
+ developer_jwt (str): Developer JWT token for authentication
223
+ agent_id (str): The agent ID to send the message to
224
+ message (str): The message to send to the agent
225
+ vehicle_ids (list[int], optional): Optional vehicle IDs override
226
+ user (str, optional): Optional user override
227
+
228
+ Yields:
229
+ dict: SSE events with either {"content": "token"} or {"done": true, ...metadata}
230
+
231
+ Behavior:
232
+ - Real-time streaming for better UX
233
+ - Token-by-token generation from LLM
234
+ - Final message includes metadata (agentId, vehiclesQueried)
235
+
236
+ Example:
237
+ >>> dimo = DIMO("Production")
238
+ >>> dev_jwt = "your_developer_jwt"
239
+ >>> for chunk in dimo.conversations.stream_message(
240
+ ... developer_jwt=dev_jwt,
241
+ ... agent_id="agent-abc123",
242
+ ... message="What's my current speed?"
243
+ ... ):
244
+ ... if "content" in chunk:
245
+ ... print(chunk["content"], end="", flush=True)
246
+ ... elif "done" in chunk:
247
+ ... print(f"\\nVehicles queried: {chunk['vehiclesQueried']}")
248
+ """
249
+ check_type("developer_jwt", developer_jwt, str)
250
+ check_type("agent_id", agent_id, str)
251
+ check_type("message", message, str)
252
+ check_optional_type("vehicle_ids", vehicle_ids, list)
253
+ check_optional_type("user", user, str)
254
+
255
+ body = {"message": message}
256
+ if vehicle_ids is not None:
257
+ body["vehicleIds"] = vehicle_ids
258
+ if user is not None:
259
+ body["user"] = user
260
+
261
+ headers = self._get_auth_headers(developer_jwt)
262
+ headers["Accept"] = "text/event-stream"
263
+ headers["Content-Type"] = "application/json"
264
+
265
+ # Build full URL
266
+ url = self._get_full_path("Conversations", f"/agents/{agent_id}/stream")
267
+
268
+ # Make streaming request directly with session
269
+ try:
270
+ response = self._session.request(
271
+ method="POST",
272
+ url=url,
273
+ headers=headers,
274
+ data=json.dumps(body),
275
+ stream=True,
276
+ )
277
+ response.raise_for_status()
278
+ except RequestException as exc:
279
+ status = getattr(exc.response, "status_code", None)
280
+ body_error = None
281
+ try:
282
+ body_error = exc.response.json()
283
+ except Exception:
284
+ body_error = exc.response.text if exc.response else None
285
+ raise HTTPError(status=status or -1, message=str(exc), body=body_error)
286
+
287
+ # Parse SSE stream
288
+ for line in response.iter_lines():
289
+ if line:
290
+ line = line.decode("utf-8")
291
+ if line.startswith("data: "):
292
+ data = line[6:] # Remove "data: " prefix
293
+ try:
294
+ yield json.loads(data)
295
+ except json.JSONDecodeError:
296
+ # Skip malformed JSON
297
+ continue
298
+
299
+ def get_history(
300
+ self,
301
+ developer_jwt: str,
302
+ agent_id: str,
303
+ limit: int = 100,
304
+ ) -> Dict:
305
+ """
306
+ Retrieve all messages in a conversation.
307
+
308
+ Args:
309
+ developer_jwt (str): Developer JWT token for authentication
310
+ agent_id (str): The agent ID to get history for
311
+ limit (int): Maximum number of messages to return (default: 100)
312
+
313
+ Returns:
314
+ dict: Conversation history including agentId, messages array, and total count
315
+
316
+ Behavior:
317
+ - Retrieves from Letta server
318
+ - Includes all message roles (user, agent, system)
319
+ - Reverse chronological order (newest first)
320
+
321
+ Example:
322
+ >>> dimo = DIMO("Production")
323
+ >>> dev_jwt = "your_developer_jwt"
324
+ >>> history = dimo.conversations.get_history(
325
+ ... developer_jwt=dev_jwt,
326
+ ... agent_id="agent-abc123",
327
+ ... limit=50
328
+ ... )
329
+ >>> for msg in history['messages']:
330
+ ... print(f"{msg['role']}: {msg['content']}")
331
+ """
332
+ check_type("developer_jwt", developer_jwt, str)
333
+ check_type("agent_id", agent_id, str)
334
+ check_type("limit", limit, int)
335
+
336
+ response = self._request(
337
+ "GET",
338
+ "Conversations",
339
+ f"/agents/{agent_id}/history",
340
+ headers=self._get_auth_headers(developer_jwt),
341
+ params={"limit": limit},
342
+ )
343
+ return response
@@ -2,6 +2,7 @@ from requests import Session
2
2
 
3
3
  from .api.attestation import Attestation
4
4
  from .api.auth import Auth
5
+ from .api.conversations import Conversations
5
6
  from .api.device_definitions import DeviceDefinitions
6
7
  from .api.token_exchange import TokenExchange
7
8
  from .api.trips import Trips
@@ -75,6 +76,7 @@ class DIMO:
75
76
  mapping = {
76
77
  "attestation": (Attestation, ("request", "_get_auth_headers")),
77
78
  "auth": (Auth, ("request", "_get_auth_headers", "env", "self")),
79
+ "conversations": (Conversations, ("request", "_get_auth_headers", "_get_full_path", "session")),
78
80
  "device_definitions": (DeviceDefinitions, ("request", "_get_auth_headers")),
79
81
  "token_exchange": (
80
82
  TokenExchange,
@@ -2,6 +2,7 @@ dimo_environment = {
2
2
  "Production": {
3
3
  "Attestation": "https://attestation-api.dimo.zone",
4
4
  "Auth": "https://auth.dimo.zone",
5
+ "Conversations": "https://conversations-api.dimo.zone",
5
6
  "Identity": "https://identity-api.dimo.zone/query",
6
7
  "DeviceDefinitions": "https://device-definitions-api.dimo.zone",
7
8
  "Telemetry": "https://telemetry-api.dimo.zone/query",
@@ -14,6 +15,7 @@ dimo_environment = {
14
15
  "Dev": {
15
16
  "Attestation": "https://attestation-api.dev.dimo.zone",
16
17
  "Auth": "https://auth.dev.dimo.zone",
18
+ "Conversations": "https://conversations-api.dev.dimo.zone",
17
19
  "Identity": "https://identity-api.dev.dimo.zone/query",
18
20
  "DeviceDefinitions": "https://device-definitions-api.dev.dimo.zone",
19
21
  "Telemetry": "https://telemetry-api.dev.dimo.zone/query",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dimo-python-sdk
3
- Version: 1.6.0
3
+ Version: 1.7.1
4
4
  Summary: DIMO SDK in Python
5
5
  Author-email: Barrett Kowalsky <barrettkowalsky@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/DIMO-Network/dimo-python-sdk
@@ -28,7 +28,29 @@ pip install dimo-python-sdk
28
28
 
29
29
  ## Unit Testing
30
30
 
31
- Coming Soon
31
+ The SDK includes comprehensive unit tests to ensure reliability and correctness. To run the tests:
32
+
33
+ 1. **Install dependencies:**
34
+ ```bash
35
+ pip install -r requirements.txt
36
+ ```
37
+
38
+ 2. **Run all tests:**
39
+ ```bash
40
+ pytest
41
+ ```
42
+
43
+ 3. **Run tests with verbose output:**
44
+ ```bash
45
+ pytest -v
46
+ ```
47
+
48
+ 4. **Run specific test files:**
49
+ ```bash
50
+ pytest tests/test_conversations.py -v
51
+ ```
52
+
53
+ The test suite uses `pytest` and includes tests for all major SDK functionality including authentication, API endpoints, GraphQL queries, and error handling
32
54
 
33
55
  ## API Documentation
34
56
 
@@ -12,6 +12,7 @@ dimo/request.py
12
12
  dimo/api/__init__.py
13
13
  dimo/api/attestation.py
14
14
  dimo/api/auth.py
15
+ dimo/api/conversations.py
15
16
  dimo/api/device_definitions.py
16
17
  dimo/api/token_exchange.py
17
18
  dimo/api/trips.py
@@ -25,6 +26,7 @@ dimo_python_sdk.egg-info/SOURCES.txt
25
26
  dimo_python_sdk.egg-info/dependency_links.txt
26
27
  dimo_python_sdk.egg-info/requires.txt
27
28
  dimo_python_sdk.egg-info/top_level.txt
29
+ tests/test_conversations.py
28
30
  tests/test_dimo.py
29
31
  tests/test_errors.py
30
32
  tests/test_permission_decoder.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dimo-python-sdk"
7
- version = "1.6.0"
7
+ version = "1.7.1"
8
8
  authors = [
9
9
  { name="Barrett Kowalsky", email="barrettkowalsky@gmail.com" },
10
10
  ]
@@ -0,0 +1,693 @@
1
+ """
2
+ Tests for the DIMO Conversations API.
3
+
4
+ These tests verify the functionality of the Conversations client including:
5
+ - Agent creation and deletion
6
+ - Synchronous and streaming message sending
7
+ - Conversation history retrieval
8
+ - Health checks
9
+ - Error handling
10
+ """
11
+
12
+ import json
13
+ from unittest.mock import MagicMock, Mock, patch
14
+ import pytest
15
+ from dimo.dimo import DIMO
16
+ from dimo.errors import HTTPError, DimoTypeError
17
+
18
+
19
+ class TestConversationsHealthCheck:
20
+ """Test the health_check endpoint."""
21
+
22
+ def test_health_check_success(self, monkeypatch):
23
+ """Test successful health check returns service status."""
24
+ client = DIMO(env="Dev")
25
+
26
+ # Mock the request method to return health data
27
+ fake_request = MagicMock(return_value={
28
+ "status": "healthy",
29
+ "version": "1.0.0",
30
+ "proxy": "active",
31
+ "default_model": "gpt-4"
32
+ })
33
+ monkeypatch.setattr(client, "request", fake_request)
34
+
35
+ result = client.conversations.health_check()
36
+
37
+ # Verify the request was called correctly
38
+ fake_request.assert_called_once_with("GET", "Conversations", "/")
39
+
40
+ # Verify the response
41
+ assert result["status"] == "healthy"
42
+ assert result["version"] == "1.0.0"
43
+ assert "default_model" in result
44
+
45
+
46
+ class TestConversationsCreateAgent:
47
+ """Test the create_agent endpoint."""
48
+
49
+ def test_create_agent_minimal(self, monkeypatch):
50
+ """Test creating an agent with minimal required parameters (no vehicle_ids)."""
51
+ client = DIMO(env="Dev")
52
+
53
+ # Mock the request method
54
+ fake_request = MagicMock(return_value={
55
+ "agentId": "agent-abc123",
56
+ "type": "driver_agent_v1",
57
+ "personality": "uncle_mechanic",
58
+ "createdAt": "2024-01-01T00:00:00Z"
59
+ })
60
+ monkeypatch.setattr(client, "request", fake_request)
61
+
62
+ dev_jwt = "test_developer_jwt"
63
+ api_key = "0x1234567890abcdef"
64
+ user_wallet = "0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605"
65
+
66
+ result = client.conversations.create_agent(
67
+ developer_jwt=dev_jwt,
68
+ api_key=api_key,
69
+ user_wallet=user_wallet,
70
+ agent_type="driver_agent_v1"
71
+ )
72
+
73
+ # Verify the request was called correctly
74
+ fake_request.assert_called_once()
75
+ args, kwargs = fake_request.call_args
76
+
77
+ assert args[0] == "POST"
78
+ assert args[1] == "Conversations"
79
+ assert args[2] == "/agents"
80
+ assert kwargs["data"]["type"] == "driver_agent_v1"
81
+ assert kwargs["data"]["personality"] == "uncle_mechanic"
82
+ assert kwargs["data"]["secrets"]["DIMO_API_KEY"] == api_key
83
+ assert kwargs["data"]["variables"]["USER_WALLET"] == user_wallet
84
+ assert "VEHICLE_IDS" not in kwargs["data"]["variables"]
85
+
86
+ # Verify the response
87
+ assert result["agentId"] == "agent-abc123"
88
+ assert result["type"] == "driver_agent_v1"
89
+
90
+ def test_create_agent_with_vehicle_ids(self, monkeypatch):
91
+ """Test creating an agent with specific vehicle IDs."""
92
+ client = DIMO(env="Dev")
93
+
94
+ fake_request = MagicMock(return_value={
95
+ "agentId": "agent-def456",
96
+ "type": "driver_agent_v1",
97
+ "createdAt": "2024-01-01T00:00:00Z"
98
+ })
99
+ monkeypatch.setattr(client, "request", fake_request)
100
+
101
+ dev_jwt = "test_developer_jwt"
102
+ api_key = "0xabcdef123456"
103
+ user_wallet = "0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605"
104
+ vehicle_ids = "[872, 1234]"
105
+
106
+ result = client.conversations.create_agent(
107
+ developer_jwt=dev_jwt,
108
+ api_key=api_key,
109
+ user_wallet=user_wallet,
110
+ agent_type="driver_agent_v1",
111
+ vehicle_ids=vehicle_ids
112
+ )
113
+
114
+ # Verify the request
115
+ args, kwargs = fake_request.call_args
116
+ assert kwargs["data"]["secrets"]["DIMO_API_KEY"] == api_key
117
+ assert kwargs["data"]["variables"]["USER_WALLET"] == user_wallet
118
+ assert kwargs["data"]["variables"]["VEHICLE_IDS"] == vehicle_ids
119
+
120
+ # Verify the response
121
+ assert result["agentId"] == "agent-def456"
122
+
123
+ def test_create_agent_with_custom_personality(self, monkeypatch):
124
+ """Test creating an agent with custom personality preset."""
125
+ client = DIMO(env="Dev")
126
+
127
+ fake_request = MagicMock(return_value={
128
+ "agentId": "agent-ghi789",
129
+ "type": "driver_agent_v1",
130
+ "personality": "helpful_assistant",
131
+ "createdAt": "2024-01-01T00:00:00Z"
132
+ })
133
+ monkeypatch.setattr(client, "request", fake_request)
134
+
135
+ result = client.conversations.create_agent(
136
+ developer_jwt="test_jwt",
137
+ api_key="0xapikey",
138
+ user_wallet="0xwallet",
139
+ agent_type="driver_agent_v1",
140
+ personality="helpful_assistant"
141
+ )
142
+
143
+ # Verify the request
144
+ args, kwargs = fake_request.call_args
145
+ assert kwargs["data"]["personality"] == "helpful_assistant"
146
+
147
+ # Verify the response
148
+ assert result["personality"] == "helpful_assistant"
149
+
150
+ def test_create_agent_full_config(self, monkeypatch):
151
+ """Test creating an agent with all configuration options."""
152
+ client = DIMO(env="Dev")
153
+
154
+ fake_request = MagicMock(return_value={
155
+ "agentId": "agent-full123",
156
+ "type": "driver_agent_v1",
157
+ "personality": "uncle_mechanic",
158
+ "createdAt": "2024-01-01T00:00:00Z"
159
+ })
160
+ monkeypatch.setattr(client, "request", fake_request)
161
+
162
+ result = client.conversations.create_agent(
163
+ developer_jwt="test_jwt",
164
+ api_key="0x1234567890abcdef",
165
+ user_wallet="0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605",
166
+ vehicle_ids="[1, 2, 3]",
167
+ agent_type="driver_agent_v1",
168
+ personality="uncle_mechanic"
169
+ )
170
+
171
+ # Verify all fields are in request
172
+ args, kwargs = fake_request.call_args
173
+ assert kwargs["data"]["type"] == "driver_agent_v1"
174
+ assert kwargs["data"]["personality"] == "uncle_mechanic"
175
+ assert kwargs["data"]["secrets"]["DIMO_API_KEY"] == "0x1234567890abcdef"
176
+ assert kwargs["data"]["variables"]["USER_WALLET"] == "0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605"
177
+ assert kwargs["data"]["variables"]["VEHICLE_IDS"] == "[1, 2, 3]"
178
+
179
+ def test_create_agent_invalid_types(self):
180
+ """Test that type checking is enforced for parameters."""
181
+ client = DIMO(env="Dev")
182
+
183
+ # Test invalid developer_jwt type
184
+ with pytest.raises(DimoTypeError):
185
+ client.conversations.create_agent(
186
+ developer_jwt=123, # Should be string
187
+ api_key="0xapikey",
188
+ user_wallet="0xwallet",
189
+ agent_type="driver_agent_v1"
190
+ )
191
+
192
+ # Test invalid api_key type
193
+ with pytest.raises(DimoTypeError):
194
+ client.conversations.create_agent(
195
+ developer_jwt="test_jwt",
196
+ api_key=123, # Should be string
197
+ user_wallet="0xwallet",
198
+ agent_type="driver_agent_v1"
199
+ )
200
+
201
+ # Test invalid user_wallet type
202
+ with pytest.raises(DimoTypeError):
203
+ client.conversations.create_agent(
204
+ developer_jwt="test_jwt",
205
+ api_key="0xapikey",
206
+ user_wallet=123, # Should be string
207
+ agent_type="driver_agent_v1"
208
+ )
209
+
210
+ # Test invalid agent_type type
211
+ with pytest.raises(DimoTypeError):
212
+ client.conversations.create_agent(
213
+ developer_jwt="test_jwt",
214
+ api_key="0xapikey",
215
+ user_wallet="0xwallet",
216
+ agent_type=123 # Should be string
217
+ )
218
+
219
+ # Test invalid vehicle_ids type
220
+ with pytest.raises(DimoTypeError):
221
+ client.conversations.create_agent(
222
+ developer_jwt="test_jwt",
223
+ api_key="0xapikey",
224
+ user_wallet="0xwallet",
225
+ agent_type="driver_agent_v1",
226
+ vehicle_ids=123 # Should be string or None
227
+ )
228
+
229
+ # Test invalid personality type
230
+ with pytest.raises(DimoTypeError):
231
+ client.conversations.create_agent(
232
+ developer_jwt="test_jwt",
233
+ api_key="0xapikey",
234
+ user_wallet="0xwallet",
235
+ agent_type="driver_agent_v1",
236
+ personality=123 # Should be string
237
+ )
238
+
239
+
240
+ class TestConversationsDeleteAgent:
241
+ """Test the delete_agent endpoint."""
242
+
243
+ def test_delete_agent_success(self, monkeypatch):
244
+ """Test successful agent deletion."""
245
+ client = DIMO(env="Dev")
246
+
247
+ fake_request = MagicMock(return_value={
248
+ "message": "Agent deleted successfully",
249
+ "agentId": "agent-abc123"
250
+ })
251
+ monkeypatch.setattr(client, "request", fake_request)
252
+
253
+ dev_jwt = "test_developer_jwt"
254
+ agent_id = "agent-abc123"
255
+
256
+ result = client.conversations.delete_agent(
257
+ developer_jwt=dev_jwt,
258
+ agent_id=agent_id
259
+ )
260
+
261
+ # Verify the request
262
+ fake_request.assert_called_once()
263
+ args, kwargs = fake_request.call_args
264
+
265
+ assert args[0] == "DELETE"
266
+ assert args[1] == "Conversations"
267
+ assert args[2] == "/agents/agent-abc123"
268
+
269
+ # Verify the response
270
+ assert result["message"] == "Agent deleted successfully"
271
+ assert result["agentId"] == agent_id
272
+
273
+ def test_delete_agent_invalid_types(self):
274
+ """Test that type checking is enforced."""
275
+ client = DIMO(env="Dev")
276
+
277
+ with pytest.raises(DimoTypeError):
278
+ client.conversations.delete_agent(
279
+ developer_jwt=123, # Should be string
280
+ agent_id="agent-abc123"
281
+ )
282
+
283
+ with pytest.raises(DimoTypeError):
284
+ client.conversations.delete_agent(
285
+ developer_jwt="test_jwt",
286
+ agent_id=123 # Should be string
287
+ )
288
+
289
+
290
+ class TestConversationsSendMessage:
291
+ """Test the send_message endpoint (synchronous)."""
292
+
293
+ def test_send_message_basic(self, monkeypatch):
294
+ """Test sending a basic message and receiving a response."""
295
+ client = DIMO(env="Dev")
296
+
297
+ fake_request = MagicMock(return_value={
298
+ "agentId": "agent-abc123",
299
+ "message": "What's my car's make and model?",
300
+ "response": "Your vehicle is a 2020 Tesla Model 3.",
301
+ "vehiclesQueried": [872],
302
+ "timestamp": "2024-01-01T00:00:00Z"
303
+ })
304
+ monkeypatch.setattr(client, "request", fake_request)
305
+
306
+ dev_jwt = "test_developer_jwt"
307
+ agent_id = "agent-abc123"
308
+ message = "What's my car's make and model?"
309
+
310
+ result = client.conversations.send_message(
311
+ developer_jwt=dev_jwt,
312
+ agent_id=agent_id,
313
+ message=message
314
+ )
315
+
316
+ # Verify the request
317
+ args, kwargs = fake_request.call_args
318
+
319
+ assert args[0] == "POST"
320
+ assert args[1] == "Conversations"
321
+ assert args[2] == "/agents/agent-abc123/message"
322
+ assert kwargs["data"]["message"] == message
323
+
324
+ # Verify the response
325
+ assert result["agentId"] == agent_id
326
+ assert result["response"] == "Your vehicle is a 2020 Tesla Model 3."
327
+ assert result["vehiclesQueried"] == [872]
328
+
329
+ def test_send_message_with_vehicle_ids_override(self, monkeypatch):
330
+ """Test sending a message with vehicle IDs override."""
331
+ client = DIMO(env="Dev")
332
+
333
+ fake_request = MagicMock(return_value={
334
+ "agentId": "agent-abc123",
335
+ "message": "What's the speed?",
336
+ "response": "Current speed is 65 mph.",
337
+ "vehiclesQueried": [1234],
338
+ "timestamp": "2024-01-01T00:00:00Z"
339
+ })
340
+ monkeypatch.setattr(client, "request", fake_request)
341
+
342
+ result = client.conversations.send_message(
343
+ developer_jwt="test_jwt",
344
+ agent_id="agent-abc123",
345
+ message="What's the speed?",
346
+ vehicle_ids=[1234]
347
+ )
348
+
349
+ # Verify vehicle_ids was included in request body
350
+ args, kwargs = fake_request.call_args
351
+ assert kwargs["data"]["vehicleIds"] == [1234]
352
+ assert result["vehiclesQueried"] == [1234]
353
+
354
+ def test_send_message_with_user_override(self, monkeypatch):
355
+ """Test sending a message with user override."""
356
+ client = DIMO(env="Dev")
357
+
358
+ fake_request = MagicMock(return_value={
359
+ "agentId": "agent-abc123",
360
+ "message": "Hello",
361
+ "response": "Hi there!",
362
+ "vehiclesQueried": [],
363
+ "timestamp": "2024-01-01T00:00:00Z"
364
+ })
365
+ monkeypatch.setattr(client, "request", fake_request)
366
+
367
+ result = client.conversations.send_message(
368
+ developer_jwt="test_jwt",
369
+ agent_id="agent-abc123",
370
+ message="Hello",
371
+ user="0xnewuser"
372
+ )
373
+
374
+ # Verify user was included in request body
375
+ args, kwargs = fake_request.call_args
376
+ assert kwargs["data"]["user"] == "0xnewuser"
377
+
378
+ def test_send_message_invalid_types(self):
379
+ """Test that type checking is enforced."""
380
+ client = DIMO(env="Dev")
381
+
382
+ with pytest.raises(DimoTypeError):
383
+ client.conversations.send_message(
384
+ developer_jwt=123, # Should be string
385
+ agent_id="agent-abc123",
386
+ message="Hello"
387
+ )
388
+
389
+ with pytest.raises(DimoTypeError):
390
+ client.conversations.send_message(
391
+ developer_jwt="test_jwt",
392
+ agent_id="agent-abc123",
393
+ message=123 # Should be string
394
+ )
395
+
396
+
397
+ class TestConversationsStreamMessage:
398
+ """Test the stream_message endpoint (SSE streaming)."""
399
+
400
+ def test_stream_message_success(self, monkeypatch):
401
+ """Test streaming a message and receiving token-by-token response."""
402
+ client = DIMO(env="Dev")
403
+
404
+ # Mock SSE response data
405
+ sse_lines = [
406
+ b"data: {\"content\": \"Your\"}",
407
+ b"data: {\"content\": \" vehicle\"}",
408
+ b"data: {\"content\": \" is\"}",
409
+ b"data: {\"content\": \" a\"}",
410
+ b"data: {\"content\": \" Tesla.\"}",
411
+ b"data: {\"done\": true, \"agentId\": \"agent-abc123\", \"vehiclesQueried\": [872]}"
412
+ ]
413
+
414
+ # Mock response object
415
+ mock_response = Mock()
416
+ mock_response.status_code = 200
417
+ mock_response.iter_lines = Mock(return_value=sse_lines)
418
+ mock_response.raise_for_status = Mock()
419
+
420
+ # Mock session.request
421
+ mock_session = Mock()
422
+ mock_session.request = Mock(return_value=mock_response)
423
+ monkeypatch.setattr(client.conversations, "_session", mock_session)
424
+
425
+ dev_jwt = "test_developer_jwt"
426
+ agent_id = "agent-abc123"
427
+ message = "What's my car?"
428
+
429
+ # Collect streamed chunks
430
+ chunks = list(client.conversations.stream_message(
431
+ developer_jwt=dev_jwt,
432
+ agent_id=agent_id,
433
+ message=message
434
+ ))
435
+
436
+ # Verify we got all chunks
437
+ assert len(chunks) == 6
438
+ assert chunks[0] == {"content": "Your"}
439
+ assert chunks[1] == {"content": " vehicle"}
440
+ assert chunks[-1]["done"] is True
441
+ assert chunks[-1]["agentId"] == "agent-abc123"
442
+ assert chunks[-1]["vehiclesQueried"] == [872]
443
+
444
+ # Verify the request was made correctly
445
+ mock_session.request.assert_called_once()
446
+ call_args = mock_session.request.call_args
447
+ assert call_args[1]["method"] == "POST"
448
+ assert "/agents/agent-abc123/stream" in call_args[1]["url"]
449
+ assert call_args[1]["stream"] is True
450
+ assert call_args[1]["headers"]["Accept"] == "text/event-stream"
451
+
452
+ def test_stream_message_with_overrides(self, monkeypatch):
453
+ """Test streaming with vehicle_ids and user overrides."""
454
+ client = DIMO(env="Dev")
455
+
456
+ # Mock minimal SSE response
457
+ sse_lines = [
458
+ b"data: {\"content\": \"Hello\"}",
459
+ b"data: {\"done\": true, \"agentId\": \"agent-abc123\", \"vehiclesQueried\": [1234]}"
460
+ ]
461
+
462
+ mock_response = Mock()
463
+ mock_response.status_code = 200
464
+ mock_response.iter_lines = Mock(return_value=sse_lines)
465
+ mock_response.raise_for_status = Mock()
466
+
467
+ mock_session = Mock()
468
+ mock_session.request = Mock(return_value=mock_response)
469
+ monkeypatch.setattr(client.conversations, "_session", mock_session)
470
+
471
+ # Call with overrides
472
+ chunks = list(client.conversations.stream_message(
473
+ developer_jwt="test_jwt",
474
+ agent_id="agent-abc123",
475
+ message="Hello",
476
+ vehicle_ids=[1234],
477
+ user="0xnewuser"
478
+ ))
479
+
480
+ # Verify the request included overrides in body
481
+ call_args = mock_session.request.call_args
482
+ body = json.loads(call_args[1]["data"])
483
+ assert body["message"] == "Hello"
484
+ assert body["vehicleIds"] == [1234]
485
+ assert body["user"] == "0xnewuser"
486
+
487
+ def test_stream_message_handles_malformed_json(self, monkeypatch):
488
+ """Test that malformed JSON is skipped gracefully."""
489
+ client = DIMO(env="Dev")
490
+
491
+ # Mock SSE with malformed data
492
+ sse_lines = [
493
+ b"data: {\"content\": \"Good\"}",
494
+ b"data: {malformed json here", # This should be skipped
495
+ b"data: {\"content\": \"data\"}",
496
+ b"data: {\"done\": true, \"agentId\": \"agent-abc123\", \"vehiclesQueried\": []}"
497
+ ]
498
+
499
+ mock_response = Mock()
500
+ mock_response.status_code = 200
501
+ mock_response.iter_lines = Mock(return_value=sse_lines)
502
+ mock_response.raise_for_status = Mock()
503
+
504
+ mock_session = Mock()
505
+ mock_session.request = Mock(return_value=mock_response)
506
+ monkeypatch.setattr(client.conversations, "_session", mock_session)
507
+
508
+ # Collect chunks - malformed one should be skipped
509
+ chunks = list(client.conversations.stream_message(
510
+ developer_jwt="test_jwt",
511
+ agent_id="agent-abc123",
512
+ message="Test"
513
+ ))
514
+
515
+ # Should have 3 valid chunks (malformed one skipped)
516
+ assert len(chunks) == 3
517
+ assert chunks[0] == {"content": "Good"}
518
+ assert chunks[1] == {"content": "data"}
519
+ assert chunks[2]["done"] is True
520
+
521
+ def test_stream_message_http_error(self, monkeypatch):
522
+ """Test that HTTP errors are properly raised."""
523
+ from requests import RequestException
524
+
525
+ client = DIMO(env="Dev")
526
+
527
+ # Mock a failed request
528
+ mock_response = Mock()
529
+ mock_response.status_code = 404
530
+ mock_response.json = Mock(return_value={"error": "Agent not found"})
531
+
532
+ mock_exception = RequestException("Not found")
533
+ mock_exception.response = mock_response
534
+
535
+ mock_session = Mock()
536
+ mock_session.request = Mock(side_effect=mock_exception)
537
+ monkeypatch.setattr(client.conversations, "_session", mock_session)
538
+
539
+ # Verify HTTPError is raised
540
+ with pytest.raises(HTTPError) as exc_info:
541
+ list(client.conversations.stream_message(
542
+ developer_jwt="test_jwt",
543
+ agent_id="bad-agent-id",
544
+ message="Test"
545
+ ))
546
+
547
+ assert exc_info.value.status == 404
548
+
549
+
550
+ class TestConversationsGetHistory:
551
+ """Test the get_history endpoint."""
552
+
553
+ def test_get_history_default_limit(self, monkeypatch):
554
+ """Test retrieving conversation history with default limit."""
555
+ client = DIMO(env="Dev")
556
+
557
+ fake_request = MagicMock(return_value={
558
+ "agentId": "agent-abc123",
559
+ "messages": [
560
+ {"role": "user", "content": "Hello", "timestamp": "2024-01-01T00:00:00Z"},
561
+ {"role": "agent", "content": "Hi there!", "timestamp": "2024-01-01T00:00:01Z"}
562
+ ],
563
+ "total": 2
564
+ })
565
+ monkeypatch.setattr(client, "request", fake_request)
566
+
567
+ dev_jwt = "test_developer_jwt"
568
+ agent_id = "agent-abc123"
569
+
570
+ result = client.conversations.get_history(
571
+ developer_jwt=dev_jwt,
572
+ agent_id=agent_id
573
+ )
574
+
575
+ # Verify the request
576
+ args, kwargs = fake_request.call_args
577
+
578
+ assert args[0] == "GET"
579
+ assert args[1] == "Conversations"
580
+ assert args[2] == "/agents/agent-abc123/history"
581
+ assert kwargs["params"]["limit"] == 100 # Default limit
582
+
583
+ # Verify the response
584
+ assert result["agentId"] == agent_id
585
+ assert len(result["messages"]) == 2
586
+ assert result["total"] == 2
587
+
588
+ def test_get_history_custom_limit(self, monkeypatch):
589
+ """Test retrieving conversation history with custom limit."""
590
+ client = DIMO(env="Dev")
591
+
592
+ fake_request = MagicMock(return_value={
593
+ "agentId": "agent-abc123",
594
+ "messages": [
595
+ {"role": "user", "content": "Test", "timestamp": "2024-01-01T00:00:00Z"}
596
+ ],
597
+ "total": 1
598
+ })
599
+ monkeypatch.setattr(client, "request", fake_request)
600
+
601
+ result = client.conversations.get_history(
602
+ developer_jwt="test_jwt",
603
+ agent_id="agent-abc123",
604
+ limit=50
605
+ )
606
+
607
+ # Verify custom limit was used
608
+ args, kwargs = fake_request.call_args
609
+ assert kwargs["params"]["limit"] == 50
610
+
611
+ def test_get_history_invalid_types(self):
612
+ """Test that type checking is enforced."""
613
+ client = DIMO(env="Dev")
614
+
615
+ with pytest.raises(DimoTypeError):
616
+ client.conversations.get_history(
617
+ developer_jwt=123, # Should be string
618
+ agent_id="agent-abc123"
619
+ )
620
+
621
+ with pytest.raises(DimoTypeError):
622
+ client.conversations.get_history(
623
+ developer_jwt="test_jwt",
624
+ agent_id=123 # Should be string
625
+ )
626
+
627
+ with pytest.raises(DimoTypeError):
628
+ client.conversations.get_history(
629
+ developer_jwt="test_jwt",
630
+ agent_id="agent-abc123",
631
+ limit="not_an_int" # Should be int
632
+ )
633
+
634
+
635
+ class TestConversationsIntegration:
636
+ """Integration tests demonstrating complete workflows."""
637
+
638
+ def test_full_agent_lifecycle(self, monkeypatch):
639
+ """Test creating an agent, sending messages, and deleting it."""
640
+ client = DIMO(env="Dev")
641
+
642
+ # Track which endpoints are called
643
+ calls_made = []
644
+
645
+ def fake_request(*args, **kwargs):
646
+ calls_made.append((args[0], args[2]))
647
+ if args[0] == "POST" and args[2] == "/agents":
648
+ return {
649
+ "agentId": "agent-test123",
650
+ "type": "driver_agent_v1",
651
+ "personality": "uncle_mechanic",
652
+ "createdAt": "2024-01-01T00:00:00Z"
653
+ }
654
+ elif args[0] == "POST" and "/message" in args[2]:
655
+ return {
656
+ "agentId": "agent-test123",
657
+ "response": "Your vehicle is a Tesla.",
658
+ "vehiclesQueried": [872]
659
+ }
660
+ elif args[0] == "DELETE":
661
+ return {"message": "Agent deleted successfully"}
662
+ return {}
663
+
664
+ monkeypatch.setattr(client, "request", fake_request)
665
+
666
+ # 1. Create agent
667
+ agent = client.conversations.create_agent(
668
+ developer_jwt="test_jwt",
669
+ api_key="0x1234567890abcdef",
670
+ user_wallet="0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605",
671
+ agent_type="driver_agent_v1",
672
+ vehicle_ids="[872]"
673
+ )
674
+ assert agent["agentId"] == "agent-test123"
675
+ assert ("POST", "/agents") in calls_made
676
+
677
+ # 2. Send message
678
+ response = client.conversations.send_message(
679
+ developer_jwt="test_jwt",
680
+ agent_id=agent["agentId"],
681
+ message="What's my vehicle?"
682
+ )
683
+ assert response["agentId"] == "agent-test123"
684
+ assert "Tesla" in response["response"]
685
+
686
+ # 3. Delete agent
687
+ delete_result = client.conversations.delete_agent(
688
+ developer_jwt="test_jwt",
689
+ agent_id=agent["agentId"]
690
+ )
691
+ assert "deleted" in delete_result["message"].lower()
692
+ assert len(calls_made) == 3 # Verify all 3 operations were called
693
+
File without changes