agentx-python 0.8.28__tar.gz → 0.8.29__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 (105) hide show
  1. {agentx_python-0.8.28 → agentx_python-0.8.29}/PKG-INFO +1 -1
  2. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/client.py +43 -1
  3. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/tool_schemas.py +29 -5
  4. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/client.py +10 -0
  5. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/version.py +2 -2
  6. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx_python.egg-info/PKG-INFO +1 -1
  7. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx_python.egg-info/SOURCES.txt +1 -0
  8. agentx_python-0.8.29/tests/test_tool_schemas.py +81 -0
  9. {agentx_python-0.8.28 → agentx_python-0.8.29}/LICENSE +0 -0
  10. {agentx_python-0.8.28 → agentx_python-0.8.29}/README.md +0 -0
  11. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/__init__.py +0 -0
  12. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/agentx.py +0 -0
  13. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/cli.py +0 -0
  14. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/__init__.py +0 -0
  15. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/_term.py +0 -0
  16. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/adapters/__init__.py +0 -0
  17. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/adapters/http_endpoint.py +0 -0
  18. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/adapters/precomputed.py +0 -0
  19. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/adapters/raw.py +0 -0
  20. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/datasets.py +0 -0
  21. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/evaluation_settings.py +0 -0
  22. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/models.py +0 -0
  23. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/prompts.py +0 -0
  24. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/reporting.py +0 -0
  25. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/results.py +0 -0
  26. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/runner.py +0 -0
  27. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/evaluations/tracing.py +0 -0
  28. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/exceptions.py +0 -0
  29. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/export.py +0 -0
  30. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/feedback.py +0 -0
  31. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/__init__.py +0 -0
  32. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/_traced_call.py +0 -0
  33. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/anthropic.py +0 -0
  34. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/autogen.py +0 -0
  35. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/crewai.py +0 -0
  36. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/databricks.py +0 -0
  37. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/google_adk.py +0 -0
  38. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/google_genai.py +0 -0
  39. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/langchain.py +0 -0
  40. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/litellm.py +0 -0
  41. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/llamaindex.py +0 -0
  42. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/moveworks.py +0 -0
  43. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/nvidia_nim.py +0 -0
  44. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/openai.py +0 -0
  45. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/integrations/openai_agents.py +0 -0
  46. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/__init__.py +0 -0
  47. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/_transport.py +0 -0
  48. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/agents.py +0 -0
  49. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/alert_rules.py +0 -0
  50. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/improvement_groups.py +0 -0
  51. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/judge_scorers.py +0 -0
  52. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/models.py +0 -0
  53. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/online_evaluators.py +0 -0
  54. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/patterns.py +0 -0
  55. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/profile.py +0 -0
  56. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/review_queue.py +0 -0
  57. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/rules.py +0 -0
  58. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/scorer_groups.py +0 -0
  59. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/scorers.py +0 -0
  60. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/sessions.py +0 -0
  61. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/monitor/signals.py +0 -0
  62. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/outcomes.py +0 -0
  63. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/projects.py +0 -0
  64. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/py.typed +0 -0
  65. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/resources/__init__.py +0 -0
  66. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/resources/agent.py +0 -0
  67. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/resources/conversation.py +0 -0
  68. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/resources/workforce.py +0 -0
  69. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/testing.py +0 -0
  70. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/traces.py +0 -0
  71. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/tracing/__init__.py +0 -0
  72. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/tracing/ci_types.py +0 -0
  73. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/tracing/eval_scope.py +0 -0
  74. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/tracing/framework_detect.py +0 -0
  75. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/tracing/ingest_client.py +0 -0
  76. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/tracing/tracer.py +0 -0
  77. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx/util.py +0 -0
  78. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx_python.egg-info/dependency_links.txt +0 -0
  79. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx_python.egg-info/entry_points.txt +0 -0
  80. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx_python.egg-info/not-zip-safe +0 -0
  81. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx_python.egg-info/requires.txt +0 -0
  82. {agentx_python-0.8.28 → agentx_python-0.8.29}/agentx_python.egg-info/top_level.txt +0 -0
  83. {agentx_python-0.8.28 → agentx_python-0.8.29}/setup.cfg +0 -0
  84. {agentx_python-0.8.28 → agentx_python-0.8.29}/setup.py +0 -0
  85. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_alert_rules.py +0 -0
  86. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_cli_launcher.py +0 -0
  87. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_deep_dive_fixes.py +0 -0
  88. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_docs_match_sdk.py +0 -0
  89. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_error_taxonomy.py +0 -0
  90. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_eval_scope.py +0 -0
  91. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_framework_detect.py +0 -0
  92. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_integration.py +0 -0
  93. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_integrations.py +0 -0
  94. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_judge_scorers.py +0 -0
  95. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_multi_judge.py +0 -0
  96. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_pairwise.py +0 -0
  97. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_pattern_update_merge.py +0 -0
  98. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_review_queue.py +0 -0
  99. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_runner_features.py +0 -0
  100. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_selfhost_analysis_fallback.py +0 -0
  101. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_selfhost_compat.py +0 -0
  102. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_span_tree.py +0 -0
  103. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_testing.py +0 -0
  104. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_wire_models.py +0 -0
  105. {agentx_python-0.8.28 → agentx_python-0.8.29}/tests/test_workforce_binding.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-python
3
- Version: 0.8.28
3
+ Version: 0.8.29
4
4
  Summary: Official Python SDK for AgentX (https://www.agentx.so/)
5
5
  Home-page: https://github.com/AgentX-ai/AgentX-python
6
6
  Author: Robin Wang and AgentX Team
@@ -75,6 +75,22 @@ def _resolve_scorer_id(scorer_id: Optional[str], evaluation_settings_id: Optiona
75
75
  return scorer_id or evaluation_settings_id
76
76
 
77
77
 
78
+ # "Argument not given" for update_tool_schema_meta, where None is itself a meaningful value
79
+ # (clear the price).
80
+ _UNSET: object = object()
81
+
82
+
83
+ def _validated_tool_price(value: float) -> float:
84
+ """Local mirror of the engine's check: USD per call, finite and non-negative."""
85
+ try:
86
+ price = float(value)
87
+ except (TypeError, ValueError):
88
+ raise ValueError(f"price_per_call_usd must be a number of USD per call, got {value!r}") from None
89
+ if price != price or price in (float("inf"), float("-inf")) or price < 0:
90
+ raise ValueError(f"price_per_call_usd must be a finite, non-negative number, got {value!r}")
91
+ return price
92
+
93
+
78
94
  class EvaluationsClient:
79
95
  def __init__(
80
96
  self,
@@ -723,13 +739,39 @@ class EvaluationsClient:
723
739
  data = self._request("GET", "/evaluate/tool-schemas", base=self._api_root)
724
740
  return data.get("toolSchemas", []) if isinstance(data, dict) else data
725
741
 
726
- def create_tool_schema(self, *, name: str, definition: str, description: Optional[str] = None) -> dict:
742
+ def create_tool_schema(
743
+ self, *, name: str, definition: str, description: Optional[str] = None,
744
+ price_per_call_usd: Optional[float] = None,
745
+ ) -> dict:
727
746
  payload: dict = {"name": name, "definition": definition}
728
747
  if description is not None:
729
748
  payload["description"] = description
749
+ if price_per_call_usd is not None:
750
+ payload["pricePerCallUsd"] = _validated_tool_price(price_per_call_usd)
730
751
  # Server-side write - no transport retry (see init_run's comment).
731
752
  return self._request("POST", "/evaluate/tool-schemas", base=self._api_root, json=payload, retry=False)
732
753
 
754
+ def update_tool_schema_meta(
755
+ self, tool_schema_id: str, *, description: Optional[str] = None,
756
+ price_per_call_usd: Optional[float] = _UNSET, test_endpoint_url: Optional[str] = _UNSET, # type: ignore[assignment]
757
+ ) -> dict:
758
+ """Metadata-only edit (PATCH): description, per-call price, test endpoint. Never touches
759
+ the version log. ``price_per_call_usd=None`` clears the price (the tool becomes free)."""
760
+ payload: dict = {}
761
+ if description is not None:
762
+ payload["description"] = description
763
+ if price_per_call_usd is not _UNSET:
764
+ payload["pricePerCallUsd"] = None if price_per_call_usd is None else _validated_tool_price(price_per_call_usd)
765
+ if test_endpoint_url is not _UNSET:
766
+ payload["testEndpointUrl"] = test_endpoint_url
767
+ return self._request(
768
+ "PATCH", f"/evaluate/tool-schemas/{tool_schema_id}", base=self._api_root, json=payload, retry=False,
769
+ )
770
+
771
+ def delete_tool_schema(self, tool_schema_id: str) -> None:
772
+ """Deletes the tool schema and its version history."""
773
+ self._request("DELETE", f"/evaluate/tool-schemas/{tool_schema_id}", base=self._api_root, retry=False)
774
+
733
775
  def get_tool_schema_examples(self, tool_schema_id: str, window: Optional[str] = None) -> dict:
734
776
  params = {"window": window} if window else None
735
777
  return self._request(
@@ -19,15 +19,39 @@ class ToolSchemaClient:
19
19
  def list(self) -> List[dict]:
20
20
  return self._client.list_tool_schemas()
21
21
 
22
- def create(self, *, name: str, definition: str, description: Optional[str] = None) -> dict:
23
- return self._client.create_tool_schema(name=name, definition=definition, description=description)
22
+ def create(
23
+ self, *, name: str, definition: str, description: Optional[str] = None,
24
+ price_per_call_usd: Optional[float] = None,
25
+ ) -> dict:
26
+ """``price_per_call_usd`` is what one call of this tool costs you (a paid external API
27
+ billed per request). Every recorded call is charged, failed ones included; the spend
28
+ shows as the "Tool calls" segment of the Overview cost chart and counts toward the
29
+ ``estimatedCostUsd`` alert metric. Leave it unset for a free tool."""
30
+ return self._client.create_tool_schema(
31
+ name=name, definition=definition, description=description, price_per_call_usd=price_per_call_usd,
32
+ )
24
33
 
25
- def get_or_create(self, *, name: str, definition: str, description: Optional[str] = None) -> dict:
26
- """Idempotent register: returns the existing schema of this name if present."""
34
+ def get_or_create(
35
+ self, *, name: str, definition: str, description: Optional[str] = None,
36
+ price_per_call_usd: Optional[float] = None,
37
+ ) -> dict:
38
+ """Idempotent register: returns the existing schema of this name if present (its stored
39
+ price wins - use :meth:`set_price` to change it)."""
27
40
  existing = next((t for t in self.list() if t.get("name") == name), None)
28
41
  if existing:
29
42
  return existing
30
- return self.create(name=name, definition=definition, description=description)
43
+ return self.create(
44
+ name=name, definition=definition, description=description, price_per_call_usd=price_per_call_usd,
45
+ )
46
+
47
+ def set_price(self, tool_schema_id: str, price_per_call_usd: Optional[float]) -> dict:
48
+ """Set (or clear with ``None``) a registered tool's USD price per call. Metadata only -
49
+ the definition's version log is untouched."""
50
+ return self._client.update_tool_schema_meta(tool_schema_id, price_per_call_usd=price_per_call_usd)
51
+
52
+ def delete(self, tool_schema_id: str) -> None:
53
+ """Deletes the tool schema and its version history."""
54
+ self._client.delete_tool_schema(tool_schema_id)
31
55
 
32
56
  def examples(self, tool_schema_id: str, window: Optional[str] = None) -> dict:
33
57
  """Failure evidence recorded against this tool (agent-tool-failure signals, low-rated
@@ -360,6 +360,16 @@ class MonitorClient:
360
360
  params[key] = value
361
361
  return self._request("GET", "/metrics", params=params)
362
362
 
363
+ def cost_trend(self, window: str = "7d") -> dict:
364
+ """Overview's "Total cost" chart over a window ("24h", "7d", "30d"): priced spend per
365
+ bucket and in total, stacked by model, with two reserved segments - ``"eval runs"``
366
+ (offline-evaluation spend) and ``"tool calls"`` (registered tools' per-call prices, split
367
+ per tool in ``totalsByTool``). Unpriced models and tools contribute $0."""
368
+ return self._request(
369
+ "GET", "/agent-monitoring/cost-trend",
370
+ base=self._api_root(), params={"window": window},
371
+ )
372
+
363
373
  def topics(self, window: str = "7d") -> dict:
364
374
  """The Topics view's data over a window ("24h", "7d", "30d"): LLM-classified themes of
365
375
  sampled production traffic with per-topic counts and sentiment. Empty until Topics is
@@ -1,7 +1,7 @@
1
- VERSION = "0.8.28"
1
+ VERSION = "0.8.29"
2
2
 
3
3
  # The AgentX-trace-eval release this SDK version is tested against - what `agentx-trace-eval`
4
4
  # installs and converges to (see agentx/cli.py). Bump together with VERSION when releasing, so
5
5
  # every published SDK names a known-good engine+dashboard pair. Users can override with
6
6
  # AGENTX_TRACE_EVAL_VERSION=<tag|latest>.
7
- ENGINE_VERSION = "v0.3.31"
7
+ ENGINE_VERSION = "v0.3.32"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-python
3
- Version: 0.8.28
3
+ Version: 0.8.29
4
4
  Summary: Official Python SDK for AgentX (https://www.agentx.so/)
5
5
  Home-page: https://github.com/AgentX-ai/AgentX-python
6
6
  Author: Robin Wang and AgentX Team
@@ -98,5 +98,6 @@ tests/test_selfhost_analysis_fallback.py
98
98
  tests/test_selfhost_compat.py
99
99
  tests/test_span_tree.py
100
100
  tests/test_testing.py
101
+ tests/test_tool_schemas.py
101
102
  tests/test_wire_models.py
102
103
  tests/test_workforce_binding.py
@@ -0,0 +1,81 @@
1
+ """Unit tests for the tool schema registry client - wire-level, no engine required: the
2
+ per-call price rides the camelCase wire on create, PATCH edits are metadata-only and can clear
3
+ the price with None, and a bad price is refused locally before any request."""
4
+
5
+ from typing import Any, Dict, List
6
+
7
+ import pytest
8
+
9
+ from agentx.evaluations.client import EvaluationsClient
10
+ from agentx.evaluations.tool_schemas import ToolSchemaClient
11
+
12
+
13
+ class RecordingClient(EvaluationsClient):
14
+ def __init__(self, responses: List[Any]):
15
+ super().__init__(api_key="agtx_test", base_url="http://engine:4700/api/v1")
16
+ self.calls: List[Dict[str, Any]] = []
17
+ self._responses = responses
18
+
19
+ def _request(self, method: str, path: str, timeout: int = 30, base=None, retry: bool = True, **kwargs: Any) -> Any: # type: ignore[override]
20
+ self.calls.append({"method": method, "path": path, "retry": retry, **kwargs})
21
+ return self._responses.pop(0) if self._responses else {}
22
+
23
+
24
+ def test_create_sends_the_price_on_the_camelcase_wire_without_retry():
25
+ client = RecordingClient([{"_id": "t1", "name": "geocode", "pricePerCallUsd": 0.25}])
26
+ created = ToolSchemaClient(client).create(name="geocode", definition="{}", price_per_call_usd=0.25)
27
+ call = client.calls[0]
28
+ assert call["method"] == "POST"
29
+ assert call["path"] == "/evaluate/tool-schemas"
30
+ assert call["retry"] is False
31
+ assert call["json"] == {"name": "geocode", "definition": "{}", "pricePerCallUsd": 0.25}
32
+ assert created["pricePerCallUsd"] == 0.25
33
+
34
+
35
+ def test_create_omits_the_price_when_not_given():
36
+ client = RecordingClient([{}])
37
+ ToolSchemaClient(client).create(name="clock", definition="{}")
38
+ assert "pricePerCallUsd" not in client.calls[0]["json"]
39
+
40
+
41
+ def test_set_price_patches_metadata_and_none_clears_it():
42
+ client = RecordingClient([{"ok": True}, {"ok": True}])
43
+ tools = ToolSchemaClient(client)
44
+ tools.set_price("t1", 0.01)
45
+ tools.set_price("t1", None)
46
+ assert [c["method"] for c in client.calls] == ["PATCH", "PATCH"]
47
+ assert client.calls[0]["path"] == "/evaluate/tool-schemas/t1"
48
+ assert client.calls[0]["json"] == {"pricePerCallUsd": 0.01}
49
+ assert client.calls[1]["json"] == {"pricePerCallUsd": None}
50
+ assert all(c["retry"] is False for c in client.calls)
51
+
52
+
53
+ def test_update_meta_sends_only_the_fields_given():
54
+ client = RecordingClient([{"ok": True}])
55
+ client.update_tool_schema_meta("t1", description="Order lookup")
56
+ assert client.calls[0]["json"] == {"description": "Order lookup"}
57
+
58
+
59
+ def test_a_bad_price_is_refused_before_any_request():
60
+ client = RecordingClient([])
61
+ tools = ToolSchemaClient(client)
62
+ with pytest.raises(ValueError, match="non-negative"):
63
+ tools.create(name="x", definition="{}", price_per_call_usd=-1)
64
+ with pytest.raises(ValueError, match="number"):
65
+ tools.set_price("t1", "cheap") # type: ignore[arg-type]
66
+ assert client.calls == []
67
+
68
+
69
+ def test_delete_never_retries():
70
+ client = RecordingClient([{}])
71
+ ToolSchemaClient(client).delete("t1")
72
+ assert client.calls[0]["method"] == "DELETE"
73
+ assert client.calls[0]["path"] == "/evaluate/tool-schemas/t1"
74
+ assert client.calls[0]["retry"] is False
75
+
76
+
77
+ def test_get_or_create_keeps_the_existing_row_and_its_price():
78
+ client = RecordingClient([{"toolSchemas": [{"_id": "t1", "name": "geocode", "pricePerCallUsd": 0.25}]}])
79
+ existing = ToolSchemaClient(client).get_or_create(name="geocode", definition="{}", price_per_call_usd=9)
80
+ assert existing["pricePerCallUsd"] == 0.25
81
+ assert [c["method"] for c in client.calls] == ["GET"]
File without changes
File without changes
File without changes
File without changes