pygeai 0.6.0b11__py3-none-any.whl → 0.6.0b13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. pygeai/_docs/source/content/ai_lab/cli.rst +4 -4
  2. pygeai/_docs/source/content/ai_lab/models.rst +169 -35
  3. pygeai/_docs/source/content/ai_lab/runner.rst +2 -2
  4. pygeai/_docs/source/content/ai_lab/spec.rst +9 -9
  5. pygeai/_docs/source/content/ai_lab/usage.rst +34 -34
  6. pygeai/_docs/source/content/ai_lab.rst +1 -1
  7. pygeai/_docs/source/content/analytics.rst +598 -0
  8. pygeai/_docs/source/content/api_reference/chat.rst +428 -2
  9. pygeai/_docs/source/content/api_reference/embeddings.rst +1 -1
  10. pygeai/_docs/source/content/api_reference/project.rst +184 -0
  11. pygeai/_docs/source/content/api_reference/rag.rst +2 -2
  12. pygeai/_docs/source/content/authentication.rst +295 -0
  13. pygeai/_docs/source/content/cli.rst +79 -2
  14. pygeai/_docs/source/content/debugger.rst +1 -1
  15. pygeai/_docs/source/content/migration.rst +19 -2
  16. pygeai/_docs/source/index.rst +2 -0
  17. pygeai/_docs/source/pygeai.analytics.rst +53 -0
  18. pygeai/_docs/source/pygeai.cli.commands.rst +8 -0
  19. pygeai/_docs/source/pygeai.rst +1 -0
  20. pygeai/_docs/source/pygeai.tests.analytics.rst +45 -0
  21. pygeai/_docs/source/pygeai.tests.auth.rst +8 -0
  22. pygeai/_docs/source/pygeai.tests.rst +1 -1
  23. pygeai/analytics/__init__.py +0 -0
  24. pygeai/analytics/clients.py +505 -0
  25. pygeai/analytics/endpoints.py +35 -0
  26. pygeai/analytics/managers.py +606 -0
  27. pygeai/analytics/mappers.py +207 -0
  28. pygeai/analytics/responses.py +240 -0
  29. pygeai/assistant/managers.py +1 -1
  30. pygeai/chat/managers.py +1 -1
  31. pygeai/cli/commands/analytics.py +525 -0
  32. pygeai/cli/commands/base.py +16 -0
  33. pygeai/cli/commands/common.py +28 -24
  34. pygeai/cli/commands/migrate.py +75 -6
  35. pygeai/cli/commands/organization.py +265 -0
  36. pygeai/cli/commands/validators.py +144 -1
  37. pygeai/cli/error_handler.py +41 -6
  38. pygeai/cli/geai.py +106 -18
  39. pygeai/cli/parsers.py +75 -31
  40. pygeai/cli/texts/help.py +75 -6
  41. pygeai/core/base/clients.py +18 -4
  42. pygeai/core/base/session.py +59 -7
  43. pygeai/core/common/config.py +25 -2
  44. pygeai/core/common/exceptions.py +64 -1
  45. pygeai/core/embeddings/managers.py +1 -1
  46. pygeai/core/files/managers.py +1 -1
  47. pygeai/core/rerank/managers.py +1 -1
  48. pygeai/core/services/rest.py +20 -2
  49. pygeai/evaluation/clients.py +5 -3
  50. pygeai/lab/agents/clients.py +3 -3
  51. pygeai/lab/agents/endpoints.py +2 -2
  52. pygeai/lab/agents/mappers.py +50 -2
  53. pygeai/lab/clients.py +5 -2
  54. pygeai/lab/managers.py +8 -10
  55. pygeai/lab/models.py +70 -2
  56. pygeai/lab/tools/clients.py +1 -59
  57. pygeai/migration/__init__.py +3 -1
  58. pygeai/migration/strategies.py +72 -3
  59. pygeai/organization/clients.py +110 -1
  60. pygeai/organization/endpoints.py +11 -7
  61. pygeai/organization/limits/managers.py +1 -1
  62. pygeai/organization/managers.py +135 -3
  63. pygeai/organization/mappers.py +28 -2
  64. pygeai/organization/responses.py +11 -1
  65. pygeai/tests/analytics/__init__.py +0 -0
  66. pygeai/tests/analytics/test_clients.py +86 -0
  67. pygeai/tests/analytics/test_managers.py +94 -0
  68. pygeai/tests/analytics/test_mappers.py +84 -0
  69. pygeai/tests/analytics/test_responses.py +73 -0
  70. pygeai/tests/auth/test_oauth.py +172 -0
  71. pygeai/tests/cli/commands/test_migrate.py +14 -1
  72. pygeai/tests/cli/commands/test_organization.py +69 -1
  73. pygeai/tests/cli/test_error_handler.py +4 -4
  74. pygeai/tests/cli/test_geai_driver.py +1 -1
  75. pygeai/tests/lab/agents/test_mappers.py +128 -1
  76. pygeai/tests/lab/test_models.py +2 -0
  77. pygeai/tests/lab/tools/test_clients.py +2 -31
  78. pygeai/tests/organization/test_clients.py +180 -1
  79. pygeai/tests/organization/test_managers.py +40 -0
  80. pygeai/tests/snippets/analytics/__init__.py +0 -0
  81. pygeai/tests/snippets/analytics/get_agent_usage_per_user.py +16 -0
  82. pygeai/tests/snippets/analytics/get_agents_created_and_modified.py +11 -0
  83. pygeai/tests/snippets/analytics/get_average_cost_per_request.py +10 -0
  84. pygeai/tests/snippets/analytics/get_overall_error_rate.py +10 -0
  85. pygeai/tests/snippets/analytics/get_top_10_agents_by_requests.py +12 -0
  86. pygeai/tests/snippets/analytics/get_total_active_users.py +10 -0
  87. pygeai/tests/snippets/analytics/get_total_cost.py +10 -0
  88. pygeai/tests/snippets/analytics/get_total_requests_per_day.py +12 -0
  89. pygeai/tests/snippets/analytics/get_total_tokens.py +12 -0
  90. pygeai/tests/snippets/chat/get_response_complete_example.py +67 -0
  91. pygeai/tests/snippets/chat/get_response_with_instructions.py +19 -0
  92. pygeai/tests/snippets/chat/get_response_with_metadata.py +24 -0
  93. pygeai/tests/snippets/chat/get_response_with_parallel_tools.py +58 -0
  94. pygeai/tests/snippets/chat/get_response_with_reasoning.py +21 -0
  95. pygeai/tests/snippets/chat/get_response_with_store.py +38 -0
  96. pygeai/tests/snippets/chat/get_response_with_truncation.py +24 -0
  97. pygeai/tests/snippets/lab/agents/create_agent_with_permissions.py +39 -0
  98. pygeai/tests/snippets/lab/agents/create_agent_with_properties.py +46 -0
  99. pygeai/tests/snippets/lab/agents/get_agent_with_new_fields.py +62 -0
  100. pygeai/tests/snippets/lab/agents/update_agent_properties.py +50 -0
  101. pygeai/tests/snippets/organization/add_project_member.py +10 -0
  102. pygeai/tests/snippets/organization/add_project_member_batch.py +44 -0
  103. {pygeai-0.6.0b11.dist-info → pygeai-0.6.0b13.dist-info}/METADATA +1 -1
  104. {pygeai-0.6.0b11.dist-info → pygeai-0.6.0b13.dist-info}/RECORD +108 -98
  105. pygeai/_docs/source/pygeai.tests.snippets.assistants.data_analyst.rst +0 -37
  106. pygeai/_docs/source/pygeai.tests.snippets.assistants.rag.rst +0 -85
  107. pygeai/_docs/source/pygeai.tests.snippets.assistants.rst +0 -78
  108. pygeai/_docs/source/pygeai.tests.snippets.auth.rst +0 -10
  109. pygeai/_docs/source/pygeai.tests.snippets.chat.rst +0 -125
  110. pygeai/_docs/source/pygeai.tests.snippets.dbg.rst +0 -45
  111. pygeai/_docs/source/pygeai.tests.snippets.embeddings.rst +0 -61
  112. pygeai/_docs/source/pygeai.tests.snippets.evaluation.dataset.rst +0 -197
  113. pygeai/_docs/source/pygeai.tests.snippets.evaluation.plan.rst +0 -133
  114. pygeai/_docs/source/pygeai.tests.snippets.evaluation.result.rst +0 -37
  115. pygeai/_docs/source/pygeai.tests.snippets.evaluation.rst +0 -20
  116. pygeai/_docs/source/pygeai.tests.snippets.extras.rst +0 -37
  117. pygeai/_docs/source/pygeai.tests.snippets.files.rst +0 -53
  118. pygeai/_docs/source/pygeai.tests.snippets.gam.rst +0 -21
  119. pygeai/_docs/source/pygeai.tests.snippets.lab.agents.rst +0 -93
  120. pygeai/_docs/source/pygeai.tests.snippets.lab.processes.jobs.rst +0 -21
  121. pygeai/_docs/source/pygeai.tests.snippets.lab.processes.kbs.rst +0 -45
  122. pygeai/_docs/source/pygeai.tests.snippets.lab.processes.rst +0 -46
  123. pygeai/_docs/source/pygeai.tests.snippets.lab.rst +0 -82
  124. pygeai/_docs/source/pygeai.tests.snippets.lab.samples.rst +0 -21
  125. pygeai/_docs/source/pygeai.tests.snippets.lab.strategies.rst +0 -45
  126. pygeai/_docs/source/pygeai.tests.snippets.lab.tools.rst +0 -85
  127. pygeai/_docs/source/pygeai.tests.snippets.lab.use_cases.rst +0 -117
  128. pygeai/_docs/source/pygeai.tests.snippets.migrate.rst +0 -10
  129. pygeai/_docs/source/pygeai.tests.snippets.organization.rst +0 -109
  130. pygeai/_docs/source/pygeai.tests.snippets.rag.rst +0 -85
  131. pygeai/_docs/source/pygeai.tests.snippets.rerank.rst +0 -21
  132. pygeai/_docs/source/pygeai.tests.snippets.rst +0 -32
  133. pygeai/_docs/source/pygeai.tests.snippets.secrets.rst +0 -10
  134. pygeai/_docs/source/pygeai.tests.snippets.usage_limit.rst +0 -77
  135. {pygeai-0.6.0b11.dist-info → pygeai-0.6.0b13.dist-info}/WHEEL +0 -0
  136. {pygeai-0.6.0b11.dist-info → pygeai-0.6.0b13.dist-info}/entry_points.txt +0 -0
  137. {pygeai-0.6.0b11.dist-info → pygeai-0.6.0b13.dist-info}/licenses/LICENSE +0 -0
  138. {pygeai-0.6.0b11.dist-info → pygeai-0.6.0b13.dist-info}/top_level.txt +0 -0
@@ -70,7 +70,7 @@ class TestErrorHandler(TestCase):
70
70
  def test_format_error_basic(self):
71
71
  """Test basic error formatting"""
72
72
  result = ErrorHandler.format_error("Test Error", "Something went wrong")
73
- self.assertIn("ERROR: Something went wrong", result)
73
+ self.assertIn("ERROR [Test Error]: Something went wrong", result)
74
74
  self.assertIn("Run 'geai help' for usage information.", result)
75
75
 
76
76
  def test_format_error_with_suggestion(self):
@@ -80,7 +80,7 @@ class TestErrorHandler(TestCase):
80
80
  "Command not found",
81
81
  suggestion="Try using 'help' command"
82
82
  )
83
- self.assertIn("ERROR: Command not found", result)
83
+ self.assertIn("ERROR [Test Error]: Command not found", result)
84
84
  self.assertIn("→ Try using 'help' command", result)
85
85
 
86
86
  def test_format_error_without_help(self):
@@ -90,7 +90,7 @@ class TestErrorHandler(TestCase):
90
90
  "Critical error",
91
91
  show_help=False
92
92
  )
93
- self.assertIn("ERROR: Critical error", result)
93
+ self.assertIn("ERROR [Test Error]: Critical error", result)
94
94
  self.assertNotIn("Run 'geai help'", result)
95
95
 
96
96
  def test_find_similar_items_exact_match(self):
@@ -217,7 +217,7 @@ class TestErrorHandler(TestCase):
217
217
  ]
218
218
 
219
219
  for result in results:
220
- self.assertIn("ERROR:", result)
220
+ self.assertIn("ERROR [", result)
221
221
  self.assertIn("→", result)
222
222
 
223
223
 
@@ -68,7 +68,7 @@ class TestCLIDriver(TestCase):
68
68
  output = mock_stderr.getvalue()
69
69
 
70
70
  # Check for standard error format
71
- self.assertIn("ERROR:", output)
71
+ self.assertIn("ERROR [", output)
72
72
  self.assertIn("→", output)
73
73
  self.assertIn("Run 'geai help' for usage information", output)
74
74
 
@@ -310,4 +310,131 @@ class TestAgentMapper(TestCase):
310
310
  sharing_link = AgentMapper.map_to_sharing_link(data)
311
311
  self.assertEqual(sharing_link.agent_id, data["agentId"])
312
312
  self.assertEqual(sharing_link.api_token, data["apiToken"])
313
- self.assertEqual(sharing_link.shared_link, data["sharedLink"])
313
+ self.assertEqual(sharing_link.shared_link, data["sharedLink"])
314
+
315
+ def test_map_to_agent_with_new_fields(self):
316
+ """Test mapping to Agent with new fields: sharing_scope, permissions, effective_permissions"""
317
+ data = {
318
+ "id": "agent123",
319
+ "name": "TestAgent",
320
+ "status": "active",
321
+ "sharingScope": "organization",
322
+ "permissions": {
323
+ "chatSharing": "organization",
324
+ "externalExecution": "none"
325
+ },
326
+ "effectivePermissions": {
327
+ "chatSharing": "organization",
328
+ "externalExecution": "none"
329
+ }
330
+ }
331
+ agent = AgentMapper.map_to_agent(data)
332
+ self.assertEqual(agent.id, "agent123")
333
+ self.assertEqual(agent.name, "TestAgent")
334
+ self.assertEqual(agent.sharing_scope, "organization")
335
+ self.assertIsNotNone(agent.permissions)
336
+ self.assertEqual(agent.permissions.chat_sharing, "organization")
337
+ self.assertEqual(agent.permissions.external_execution, "none")
338
+ self.assertIsNotNone(agent.effective_permissions)
339
+ self.assertEqual(agent.effective_permissions.chat_sharing, "organization")
340
+ self.assertEqual(agent.effective_permissions.external_execution, "none")
341
+
342
+ def test_map_to_agent_with_null_permissions(self):
343
+ """Test mapping to Agent with null permissions and effective_permissions"""
344
+ data = {
345
+ "id": "agent123",
346
+ "name": "TestAgent",
347
+ "status": "active",
348
+ "sharingScope": "private"
349
+ }
350
+ agent = AgentMapper.map_to_agent(data)
351
+ self.assertEqual(agent.id, "agent123")
352
+ self.assertEqual(agent.sharing_scope, "private")
353
+ self.assertIsNone(agent.permissions)
354
+ self.assertIsNone(agent.effective_permissions)
355
+
356
+ def test_map_to_permission(self):
357
+ """Test mapping to Permission"""
358
+ data = {
359
+ "chatSharing": "organization",
360
+ "externalExecution": "project"
361
+ }
362
+ permission = AgentMapper._map_to_permission(data)
363
+ self.assertEqual(permission.chat_sharing, "organization")
364
+ self.assertEqual(permission.external_execution, "project")
365
+
366
+ def test_map_to_permission_null(self):
367
+ """Test mapping to Permission with None"""
368
+ permission = AgentMapper._map_to_permission(None)
369
+ self.assertIsNone(permission)
370
+
371
+ def test_map_to_agent_data_with_properties(self):
372
+ """Test mapping to AgentData with properties field"""
373
+ data = {
374
+ "prompt": {"instructions": "Test"},
375
+ "llmConfig": {"maxTokens": 100},
376
+ "properties": [
377
+ {"dataType": "string", "key": "env", "value": "production"},
378
+ {"dataType": "number", "key": "max_retries", "value": "3"},
379
+ {"dataType": "boolean", "key": "enabled", "value": "true"}
380
+ ],
381
+ "strategyName": "Dynamic Prompting"
382
+ }
383
+ agent_data = AgentMapper._map_agent_data(data)
384
+ self.assertIsNotNone(agent_data.properties)
385
+ self.assertEqual(len(agent_data.properties), 3)
386
+ self.assertEqual(agent_data.properties[0].data_type, "string")
387
+ self.assertEqual(agent_data.properties[0].key, "env")
388
+ self.assertEqual(agent_data.properties[0].value, "production")
389
+ self.assertEqual(agent_data.properties[1].data_type, "number")
390
+ self.assertEqual(agent_data.properties[1].key, "max_retries")
391
+ self.assertEqual(agent_data.properties[1].value, "3")
392
+ self.assertEqual(agent_data.properties[2].data_type, "boolean")
393
+ self.assertEqual(agent_data.properties[2].key, "enabled")
394
+ self.assertEqual(agent_data.properties[2].value, "true")
395
+ self.assertEqual(agent_data.strategy_name, "Dynamic Prompting")
396
+
397
+ def test_map_to_agent_data_with_null_properties(self):
398
+ """Test mapping to AgentData with null properties"""
399
+ data = {
400
+ "prompt": {"instructions": "Test"},
401
+ "llmConfig": {"maxTokens": 100}
402
+ }
403
+ agent_data = AgentMapper._map_agent_data(data)
404
+ self.assertIsNone(agent_data.properties)
405
+ self.assertIsNone(agent_data.strategy_name)
406
+
407
+ def test_map_to_property(self):
408
+ """Test mapping to Property"""
409
+ data = {
410
+ "dataType": "string",
411
+ "key": "environment",
412
+ "value": "production"
413
+ }
414
+ property_obj = AgentMapper._map_to_property(data)
415
+ self.assertEqual(property_obj.data_type, "string")
416
+ self.assertEqual(property_obj.key, "environment")
417
+ self.assertEqual(property_obj.value, "production")
418
+
419
+ def test_map_to_property_list(self):
420
+ """Test mapping to Property list"""
421
+ data = [
422
+ {"dataType": "string", "key": "key1", "value": "value1"},
423
+ {"dataType": "number", "key": "key2", "value": "42"}
424
+ ]
425
+ properties = AgentMapper._map_to_property_list(data)
426
+ self.assertEqual(len(properties), 2)
427
+ self.assertEqual(properties[0].key, "key1")
428
+ self.assertEqual(properties[0].value, "value1")
429
+ self.assertEqual(properties[1].key, "key2")
430
+ self.assertEqual(properties[1].value, "42")
431
+
432
+ def test_map_to_property_list_null(self):
433
+ """Test mapping to Property list with None"""
434
+ properties = AgentMapper._map_to_property_list(None)
435
+ self.assertIsNone(properties)
436
+
437
+ def test_map_to_property_list_empty(self):
438
+ """Test mapping to Property list with empty list"""
439
+ properties = AgentMapper._map_to_property_list([])
440
+ self.assertEqual(len(properties), 0)
@@ -123,6 +123,7 @@ class TestLabModels(TestCase):
123
123
  agent_data_data = {
124
124
  "prompt": {"instructions": "Summarize", "inputs": ["text"], "outputs": [{"key": "summary", "description": "Summary"}]},
125
125
  "llmConfig": {"maxTokens": 1000, "timeout": 30, "sampling": {"temperature": 0.7, "topK": 40, "topP": 0.9}},
126
+ "strategyName": "Dynamic Prompting",
126
127
  "models": [{"name": "gpt-4"}]
127
128
  }
128
129
  agent_data = AgentData.model_validate(agent_data_data)
@@ -143,6 +144,7 @@ class TestLabModels(TestCase):
143
144
  "agentData": {
144
145
  "prompt": {"instructions": "Summarize", "inputs": ["text"], "outputs": [{"key": "summary", "description": "Summary"}]},
145
146
  "llmConfig": {"maxTokens": 1000, "timeout": 30, "sampling": {"temperature": 0.7, "topK": 40, "topP": 0.9}},
147
+ "strategyName": "Dynamic Prompting",
146
148
  "models": [{"name": "gpt-4"}]
147
149
  }
148
150
  }
@@ -68,8 +68,6 @@ class TestToolClient(unittest.TestCase):
68
68
  self.assertEqual(data['reportEvents'], report_events)
69
69
  self.assertEqual(data['parameters'], self.parameters)
70
70
  self.assertIn("automaticPublish=true", call_args[1]['endpoint'])
71
- headers = call_args[1]['headers']
72
- self.assertEqual(headers['ProjectId'], self.project_id)
73
71
 
74
72
  def test_create_tool_invalid_scope(self):
75
73
  with self.assertRaises(ValueError) as context:
@@ -130,7 +128,6 @@ class TestToolClient(unittest.TestCase):
130
128
  self.assertEqual(result, expected_response)
131
129
  mock_get.assert_called_once_with(
132
130
  endpoint=LIST_TOOLS_V2,
133
- headers=mock_get.call_args[1]['headers'],
134
131
  params={
135
132
  "id": "tool-1",
136
133
  "count": "50",
@@ -140,8 +137,6 @@ class TestToolClient(unittest.TestCase):
140
137
  "allowExternal": True
141
138
  }
142
139
  )
143
- headers = mock_get.call_args[1]['headers']
144
- self.assertEqual(headers['ProjectId'], self.project_id)
145
140
 
146
141
  def test_list_tools_invalid_scope(self):
147
142
  with self.assertRaises(ValueError) as context:
@@ -182,15 +177,12 @@ class TestToolClient(unittest.TestCase):
182
177
  self.assertEqual(result, expected_response)
183
178
  mock_get.assert_called_once_with(
184
179
  endpoint=GET_TOOL_V2.format(toolId=self.tool_id),
185
- headers=mock_get.call_args[1]['headers'],
186
180
  params={
187
181
  "revision": "1",
188
182
  "version": 0,
189
183
  "allowDrafts": True
190
184
  }
191
185
  )
192
- headers = mock_get.call_args[1]['headers']
193
- self.assertEqual(headers['ProjectId'], self.project_id)
194
186
 
195
187
  @patch("pygeai.core.services.rest.ApiService.get")
196
188
  def test_get_tool_json_decode_error(self, mock_get):
@@ -218,11 +210,8 @@ class TestToolClient(unittest.TestCase):
218
210
 
219
211
  self.assertEqual(result, {})
220
212
  mock_delete.assert_called_once_with(
221
- endpoint=DELETE_TOOL_V2.format(toolId=self.tool_id),
222
- headers=mock_delete.call_args[1]['headers']
213
+ endpoint=DELETE_TOOL_V2.format(toolId=self.tool_id)
223
214
  )
224
- headers = mock_delete.call_args[1]['headers']
225
- self.assertEqual(headers['ProjectId'], self.project_id)
226
215
 
227
216
  @patch("pygeai.core.services.rest.ApiService.delete")
228
217
  def test_delete_tool_success_with_name(self, mock_delete):
@@ -235,11 +224,8 @@ class TestToolClient(unittest.TestCase):
235
224
 
236
225
  self.assertEqual(result, {})
237
226
  mock_delete.assert_called_once_with(
238
- endpoint=DELETE_TOOL_V2.format(toolId=self.tool_name),
239
- headers=mock_delete.call_args[1]['headers']
227
+ endpoint=DELETE_TOOL_V2.format(toolId=self.tool_name)
240
228
  )
241
- headers = mock_delete.call_args[1]['headers']
242
- self.assertEqual(headers['ProjectId'], self.project_id)
243
229
 
244
230
  def test_delete_tool_invalid_input(self):
245
231
  with self.assertRaises(ValueError) as context:
@@ -298,7 +284,6 @@ class TestToolClient(unittest.TestCase):
298
284
  self.assertEqual(result, expected_response)
299
285
  mock_put.assert_called_once_with(
300
286
  endpoint=f"{UPDATE_TOOL_V2.format(toolId=self.tool_id)}?automaticPublish=true",
301
- headers=mock_put.call_args[1]['headers'],
302
287
  data=mock_put.call_args[1]['data']
303
288
  )
304
289
  call_args = mock_put.call_args
@@ -313,8 +298,6 @@ class TestToolClient(unittest.TestCase):
313
298
  self.assertIsInstance(data['openApiJson'], str)
314
299
  self.assertEqual(data['reportEvents'], report_events)
315
300
  self.assertEqual(data['parameters'], self.parameters)
316
- headers = call_args[1]['headers']
317
- self.assertEqual(headers['ProjectId'], self.project_id)
318
301
 
319
302
  @patch("pygeai.core.services.rest.ApiService.put")
320
303
  def test_update_tool_with_upsert(self, mock_put):
@@ -333,7 +316,6 @@ class TestToolClient(unittest.TestCase):
333
316
  self.assertEqual(result, expected_response)
334
317
  mock_put.assert_called_once_with(
335
318
  endpoint=UPSERT_TOOL_V2.format(toolId=self.tool_id),
336
- headers=mock_put.call_args[1]['headers'],
337
319
  data=mock_put.call_args[1]['data']
338
320
  )
339
321
 
@@ -393,11 +375,8 @@ class TestToolClient(unittest.TestCase):
393
375
  self.assertEqual(result, expected_response)
394
376
  mock_post.assert_called_once_with(
395
377
  endpoint=PUBLISH_TOOL_REVISION_V2.format(toolId=self.tool_id),
396
- headers=mock_post.call_args[1]['headers'],
397
378
  data={"revision": revision}
398
379
  )
399
- headers = mock_post.call_args[1]['headers']
400
- self.assertEqual(headers['ProjectId'], self.project_id)
401
380
 
402
381
  @patch("pygeai.core.services.rest.ApiService.post")
403
382
  def test_publish_tool_revision_json_decode_error(self, mock_post):
@@ -433,15 +412,12 @@ class TestToolClient(unittest.TestCase):
433
412
  self.assertEqual(result, expected_response)
434
413
  mock_get.assert_called_once_with(
435
414
  endpoint=GET_PARAMETER_V2.format(toolPublicName=self.tool_id),
436
- headers=mock_get.call_args[1]['headers'],
437
415
  params={
438
416
  "revision": "1",
439
417
  "version": 0,
440
418
  "allowDrafts": True
441
419
  }
442
420
  )
443
- headers = mock_get.call_args[1]['headers']
444
- self.assertEqual(headers['ProjectId'], self.project_id)
445
421
 
446
422
  @patch("pygeai.core.services.rest.ApiService.get")
447
423
  def test_get_parameter_success_with_public_name(self, mock_get):
@@ -460,7 +436,6 @@ class TestToolClient(unittest.TestCase):
460
436
  self.assertEqual(result, expected_response)
461
437
  mock_get.assert_called_once_with(
462
438
  endpoint=GET_PARAMETER_V2.format(toolPublicName=self.tool_public_name),
463
- headers=mock_get.call_args[1]['headers'],
464
439
  params={
465
440
  "revision": "1",
466
441
  "version": 0,
@@ -501,11 +476,8 @@ class TestToolClient(unittest.TestCase):
501
476
  self.assertEqual(result, {})
502
477
  mock_post.assert_called_once_with(
503
478
  endpoint=SET_PARAMETER_V2.format(toolPublicName=self.tool_id),
504
- headers=mock_post.call_args[1]['headers'],
505
479
  data={"parameterDefinition": {"parameters": self.parameters}}
506
480
  )
507
- headers = mock_post.call_args[1]['headers']
508
- self.assertEqual(headers['ProjectId'], self.project_id)
509
481
 
510
482
  @patch("pygeai.core.services.rest.ApiService.post")
511
483
  def test_set_parameter_success_with_public_name(self, mock_post):
@@ -520,7 +492,6 @@ class TestToolClient(unittest.TestCase):
520
492
  self.assertEqual(result, {})
521
493
  mock_post.assert_called_once_with(
522
494
  endpoint=SET_PARAMETER_V2.format(toolPublicName=self.tool_public_name),
523
- headers=mock_post.call_args[1]['headers'],
524
495
  data={"parameterDefinition": {"parameters": self.parameters}}
525
496
  )
526
497
 
@@ -6,7 +6,8 @@ from pygeai.organization.clients import OrganizationClient
6
6
  from pygeai.core.common.exceptions import InvalidAPIResponseException
7
7
  from pygeai.organization.endpoints import GET_ASSISTANT_LIST_V1, GET_PROJECT_LIST_V1, GET_PROJECT_V1, CREATE_PROJECT_V1, \
8
8
  UPDATE_PROJECT_V1, DELETE_PROJECT_V1, GET_PROJECT_TOKENS_V1, GET_REQUEST_DATA_V1, GET_MEMBERSHIPS_V2, \
9
- GET_PROJECT_MEMBERSHIPS_V2, GET_PROJECT_ROLES_V2, GET_PROJECT_MEMBERS_V2, GET_ORGANIZATION_MEMBERS_V2
9
+ GET_PROJECT_MEMBERSHIPS_V2, GET_PROJECT_ROLES_V2, GET_PROJECT_MEMBERS_V2, GET_ORGANIZATION_MEMBERS_V2, \
10
+ ADD_PROJECT_MEMBER_V2, CREATE_ORGANIZATION_V2, GET_ORGANIZATION_LIST_V2, DELETE_ORGANIZATION_V2
10
11
 
11
12
 
12
13
  class TestOrganizationClient(unittest.TestCase):
@@ -498,3 +499,181 @@ class TestOrganizationClient(unittest.TestCase):
498
499
 
499
500
  self.assertEqual(str(context.exception), "Unable to get organization members for organization 'org-123': Invalid JSON response")
500
501
 
502
+ @patch("pygeai.core.services.rest.ApiService.post")
503
+ def test_add_project_member_success(self, mock_post):
504
+ mock_response = mock_post.return_value
505
+ mock_response.json.return_value = {"status": "invitation sent"}
506
+ mock_response.status_code = 201
507
+
508
+ result = self.client.add_project_member(
509
+ project_id="proj-123",
510
+ user_email="newuser@example.com",
511
+ roles=["Project member", "Project administrator"]
512
+ )
513
+
514
+ mock_post.assert_called_once_with(
515
+ endpoint=ADD_PROJECT_MEMBER_V2,
516
+ data={
517
+ "userEmail": "newuser@example.com",
518
+ "roles": ["Project member", "Project administrator"]
519
+ },
520
+ headers={"project-id": "proj-123"}
521
+ )
522
+ self.assertIsNotNone(result)
523
+ self.assertEqual(result['status'], "invitation sent")
524
+
525
+ @patch("pygeai.core.services.rest.ApiService.post")
526
+ def test_add_project_member_with_role_guids(self, mock_post):
527
+ mock_response = mock_post.return_value
528
+ mock_response.json.return_value = {"status": "invitation sent"}
529
+ mock_response.status_code = 201
530
+
531
+ result = self.client.add_project_member(
532
+ project_id="proj-123",
533
+ user_email="newuser@example.com",
534
+ roles=["guid-1", "guid-2"]
535
+ )
536
+
537
+ mock_post.assert_called_once_with(
538
+ endpoint=ADD_PROJECT_MEMBER_V2,
539
+ data={
540
+ "userEmail": "newuser@example.com",
541
+ "roles": ["guid-1", "guid-2"]
542
+ },
543
+ headers={"project-id": "proj-123"}
544
+ )
545
+ self.assertIsNotNone(result)
546
+
547
+ @patch("pygeai.core.services.rest.ApiService.post")
548
+ def test_add_project_member_json_decode_error(self, mock_post):
549
+ mock_response = mock_post.return_value
550
+ mock_response.status_code = 201
551
+ mock_response.json.side_effect = JSONDecodeError("Invalid JSON", "", 0)
552
+ mock_response.text = "Invalid JSON response"
553
+
554
+ with self.assertRaises(InvalidAPIResponseException) as context:
555
+ self.client.add_project_member(
556
+ project_id="proj-123",
557
+ user_email="newuser@example.com",
558
+ roles=["Project member"]
559
+ )
560
+
561
+ self.assertEqual(str(context.exception), "Unable to add project member 'newuser@example.com': Invalid JSON response")
562
+
563
+ @patch("pygeai.core.services.rest.ApiService.post")
564
+ def test_create_organization_success(self, mock_post):
565
+ mock_response = mock_post.return_value
566
+ mock_response.json.return_value = {"organizationId": "org-123", "organizationName": "Test Org"}
567
+ mock_response.status_code = 201
568
+
569
+ result = self.client.create_organization(name="Test Org", administrator_user_email="admin@example.com")
570
+
571
+ mock_post.assert_called_once_with(
572
+ endpoint=CREATE_ORGANIZATION_V2,
573
+ data={
574
+ "name": "Test Org",
575
+ "administratorUserEmail": "admin@example.com"
576
+ }
577
+ )
578
+ self.assertIsNotNone(result)
579
+ self.assertEqual(result['organizationName'], "Test Org")
580
+
581
+ @patch("pygeai.core.services.rest.ApiService.post")
582
+ def test_create_organization_json_decode_error(self, mock_post):
583
+ mock_response = mock_post.return_value
584
+ mock_response.status_code = 201
585
+ mock_response.json.side_effect = JSONDecodeError("Invalid JSON", "", 0)
586
+ mock_response.text = "Invalid JSON response"
587
+
588
+ with self.assertRaises(InvalidAPIResponseException) as context:
589
+ self.client.create_organization(name="Test Org", administrator_user_email="admin@example.com")
590
+
591
+ self.assertEqual(str(context.exception), "Unable to create organization with name 'Test Org': Invalid JSON response")
592
+
593
+ @patch("pygeai.core.services.rest.ApiService.get")
594
+ def test_get_organization_list_success(self, mock_get):
595
+ mock_response = mock_get.return_value
596
+ mock_response.json.return_value = {
597
+ "count": 2,
598
+ "pages": 1,
599
+ "organizations": [
600
+ {"organizationId": "org-1", "organizationName": "Org 1"},
601
+ {"organizationId": "org-2", "organizationName": "Org 2"}
602
+ ]
603
+ }
604
+ mock_response.status_code = 200
605
+
606
+ result = self.client.get_organization_list()
607
+
608
+ mock_get.assert_called_once_with(
609
+ endpoint=GET_ORGANIZATION_LIST_V2,
610
+ params={"orderDirection": "desc"}
611
+ )
612
+ self.assertIsNotNone(result)
613
+ self.assertEqual(result['count'], 2)
614
+ self.assertEqual(len(result['organizations']), 2)
615
+
616
+ @patch("pygeai.core.services.rest.ApiService.get")
617
+ def test_get_organization_list_with_filters(self, mock_get):
618
+ mock_response = mock_get.return_value
619
+ mock_response.json.return_value = {"count": 1, "pages": 1, "organizations": [{"organizationId": "org-1", "organizationName": "Test"}]}
620
+ mock_response.status_code = 200
621
+
622
+ result = self.client.get_organization_list(
623
+ start_page=1,
624
+ page_size=10,
625
+ order_key="name",
626
+ order_direction="asc",
627
+ filter_key="name",
628
+ filter_value="Test"
629
+ )
630
+
631
+ mock_get.assert_called_once_with(
632
+ endpoint=GET_ORGANIZATION_LIST_V2,
633
+ params={
634
+ "startPage": 1,
635
+ "pageSize": 10,
636
+ "orderKey": "name",
637
+ "orderDirection": "asc",
638
+ "filterKey": "name",
639
+ "filterValue": "Test"
640
+ }
641
+ )
642
+ self.assertIsNotNone(result)
643
+
644
+ @patch("pygeai.core.services.rest.ApiService.get")
645
+ def test_get_organization_list_json_decode_error(self, mock_get):
646
+ mock_response = mock_get.return_value
647
+ mock_response.status_code = 200
648
+ mock_response.json.side_effect = JSONDecodeError("Invalid JSON", "", 0)
649
+ mock_response.text = "Invalid JSON response"
650
+
651
+ with self.assertRaises(InvalidAPIResponseException) as context:
652
+ self.client.get_organization_list()
653
+
654
+ self.assertEqual(str(context.exception), "Unable to get organization list: Invalid JSON response")
655
+
656
+ @patch("pygeai.core.services.rest.ApiService.delete")
657
+ def test_delete_organization_success(self, mock_delete):
658
+ mock_response = mock_delete.return_value
659
+ mock_response.json.return_value = {"status": "deleted"}
660
+ mock_response.status_code = 200
661
+
662
+ result = self.client.delete_organization(organization_id="org-123")
663
+
664
+ mock_delete.assert_called_once_with(endpoint=DELETE_ORGANIZATION_V2.format(organizationId="org-123"))
665
+ self.assertIsNotNone(result)
666
+ self.assertEqual(result['status'], "deleted")
667
+
668
+ @patch("pygeai.core.services.rest.ApiService.delete")
669
+ def test_delete_organization_json_decode_error(self, mock_delete):
670
+ mock_response = mock_delete.return_value
671
+ mock_response.status_code = 200
672
+ mock_response.json.side_effect = JSONDecodeError("Invalid JSON", "", 0)
673
+ mock_response.text = "Invalid JSON response"
674
+
675
+ with self.assertRaises(InvalidAPIResponseException) as context:
676
+ self.client.delete_organization(organization_id="org-123")
677
+
678
+ self.assertEqual(str(context.exception), "Unable to delete organization with ID 'org-123': Invalid JSON response")
679
+
@@ -382,3 +382,43 @@ class TestOrganizationManager(unittest.TestCase):
382
382
 
383
383
  self.assertIn("Error received while retrieving organization members", str(context.exception))
384
384
  mock_get_organization_members.assert_called_once_with(organization_id="org-123")
385
+
386
+ @patch("pygeai.organization.clients.OrganizationClient.add_project_member")
387
+ def test_add_project_member(self, mock_add_project_member):
388
+ mock_response = EmptyResponse(content="Invitation sent successfully")
389
+ mock_add_project_member.return_value = {}
390
+
391
+ with patch.object(ResponseMapper, 'map_to_empty_response', return_value=mock_response):
392
+ response = self.manager.add_project_member(
393
+ project_id="proj-123",
394
+ user_email="newuser@example.com",
395
+ roles=["Project member"]
396
+ )
397
+
398
+ self.assertIsInstance(response, EmptyResponse)
399
+ self.assertEqual(response.content, "Invitation sent successfully")
400
+ mock_add_project_member.assert_called_once_with(
401
+ project_id="proj-123",
402
+ user_email="newuser@example.com",
403
+ roles=["Project member"]
404
+ )
405
+
406
+ @patch("pygeai.organization.clients.OrganizationClient.add_project_member")
407
+ def test_add_project_member_error(self, mock_add_project_member):
408
+ mock_add_project_member.return_value = self.error_response
409
+
410
+ with patch.object(ErrorHandler, 'has_errors', return_value=True):
411
+ with patch.object(ErrorHandler, 'extract_error', return_value="Invalid role"):
412
+ with self.assertRaises(APIError) as context:
413
+ self.manager.add_project_member(
414
+ project_id="proj-123",
415
+ user_email="newuser@example.com",
416
+ roles=["Invalid role"]
417
+ )
418
+
419
+ self.assertIn("Error received while adding project member", str(context.exception))
420
+ mock_add_project_member.assert_called_once_with(
421
+ project_id="proj-123",
422
+ user_email="newuser@example.com",
423
+ roles=["Invalid role"]
424
+ )
File without changes
@@ -0,0 +1,16 @@
1
+ from pygeai.analytics.managers import AnalyticsManager
2
+
3
+ manager = AnalyticsManager()
4
+
5
+ response = manager.get_agent_usage_per_user(
6
+ start_date="2025-01-01",
7
+ end_date="2026-01-31"
8
+ )
9
+
10
+ print("Agent usage per user:")
11
+ for user in response.agentUsagePerUser:
12
+ print(f"User: {user.userName or user.userId}")
13
+ print(f" Requests: {user.totalRequests}")
14
+ print(f" Tokens: {user.totalTokens}")
15
+ print(f" Cost: ${user.totalCost:.2f}")
16
+ print()
@@ -0,0 +1,11 @@
1
+ from pygeai.analytics.managers import AnalyticsManager
2
+
3
+ manager = AnalyticsManager()
4
+
5
+ response = manager.get_agents_created_and_modified(
6
+ start_date="2025-01-01",
7
+ end_date="2026-01-31"
8
+ )
9
+
10
+ print(f"Created agents: {response.createdAgents}")
11
+ print(f"Modified agents: {response.modifiedAgents}")
@@ -0,0 +1,10 @@
1
+ from pygeai.analytics.managers import AnalyticsManager
2
+
3
+ manager = AnalyticsManager()
4
+
5
+ response = manager.get_average_cost_per_request(
6
+ start_date="2025-01-01",
7
+ end_date="2026-01-31"
8
+ )
9
+
10
+ print(f"Average cost per request: ${response.averageCost:.4f}")
@@ -0,0 +1,10 @@
1
+ from pygeai.analytics.managers import AnalyticsManager
2
+
3
+ manager = AnalyticsManager()
4
+
5
+ response = manager.get_overall_error_rate(
6
+ start_date="2025-01-01",
7
+ end_date="2026-01-31"
8
+ )
9
+
10
+ print(f"Overall error rate: {response.errorRate:.2%}")
@@ -0,0 +1,12 @@
1
+ from pygeai.analytics.managers import AnalyticsManager
2
+
3
+ manager = AnalyticsManager()
4
+
5
+ response = manager.get_top_10_agents_by_requests(
6
+ start_date="2025-01-01",
7
+ end_date="2026-01-31"
8
+ )
9
+
10
+ print("Top 10 agents by requests:")
11
+ for idx, agent in enumerate(response.topAgents, 1):
12
+ print(f"{idx}. {agent.agentName}: {agent.totalRequests} requests")
@@ -0,0 +1,10 @@
1
+ from pygeai.analytics.managers import AnalyticsManager
2
+
3
+ manager = AnalyticsManager()
4
+
5
+ response = manager.get_total_active_users(
6
+ start_date="2025-01-01",
7
+ end_date="2026-01-31"
8
+ )
9
+
10
+ print(f"Total active users: {response.totalActiveUsers}")
@@ -0,0 +1,10 @@
1
+ from pygeai.analytics.managers import AnalyticsManager
2
+
3
+ manager = AnalyticsManager()
4
+
5
+ response = manager.get_total_cost(
6
+ start_date="2025-01-01",
7
+ end_date="2026-01-31"
8
+ )
9
+
10
+ print(f"Total cost: ${response.totalCost:.2f}")