veris-ai 1.11.1__tar.gz → 1.12.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.
Potentially problematic release.
This version of veris-ai might be problematic. Click here for more details.
- {veris_ai-1.11.1 → veris_ai-1.12.1}/PKG-INFO +17 -1
- {veris_ai-1.11.1 → veris_ai-1.12.1}/pyproject.toml +2 -2
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/tool_mock.py +133 -11
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/conftest.py +6 -2
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/test_agents_wrapper_simple.py +4 -2
- veris_ai-1.12.1/tests/test_helpers.py +23 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/test_mcp_protocol_server_mocked.py +6 -3
- veris_ai-1.12.1/tests/test_token_decoding.py +132 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/test_tool_mock.py +45 -19
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/test_veris_runner_tool_options.py +4 -2
- {veris_ai-1.11.1 → veris_ai-1.12.1}/uv.lock +24 -29
- {veris_ai-1.11.1 → veris_ai-1.12.1}/.cursor/rules/documentation-management.mdc +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/.github/workflows/release.yml +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/.github/workflows/test.yml +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/.gitignore +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/.pre-commit-config.yaml +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/CHANGELOG.md +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/CLAUDE.md +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/LICENSE +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/README.md +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/examples/README.md +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/examples/__init__.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/examples/import_options.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/examples/openai_agents_example.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/README.md +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/__init__.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/agents_wrapper.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/api_client.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/jaeger_interface/README.md +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/jaeger_interface/__init__.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/jaeger_interface/client.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/jaeger_interface/models.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/models.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/observability.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/src/veris_ai/utils.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/README.md +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/__init__.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/fixtures/__init__.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/fixtures/http_server.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/fixtures/simple_app.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/test_agents_wrapper_extract.py +0 -0
- {veris_ai-1.11.1 → veris_ai-1.12.1}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: veris-ai
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.1
|
|
4
4
|
Summary: A Python package for Veris AI tools
|
|
5
5
|
Project-URL: Homepage, https://github.com/veris-ai/veris-python-sdk
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/veris-ai/veris-python-sdk/issues
|
|
@@ -21,6 +21,22 @@ Requires-Dist: opentelemetry-sdk>=1.34.1
|
|
|
21
21
|
Requires-Dist: pydantic>=2.0.0
|
|
22
22
|
Requires-Dist: requests>=2.31.0
|
|
23
23
|
Requires-Dist: tenacity>=9.1.2
|
|
24
|
+
Provides-Extra: agents
|
|
25
|
+
Requires-Dist: openai-agents>=0.2.5; extra == 'agents'
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: mypy>=1.17.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pre-commit>=3.3.3; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=1.1.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov>=6.2.1; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.4.1; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.12.4; extra == 'dev'
|
|
33
|
+
Requires-Dist: types-requests>=2.32.4.20250611; extra == 'dev'
|
|
34
|
+
Requires-Dist: uvicorn>=0.30.0; extra == 'dev'
|
|
35
|
+
Provides-Extra: fastapi
|
|
36
|
+
Requires-Dist: fastapi-mcp>=0.4.0; extra == 'fastapi'
|
|
37
|
+
Requires-Dist: fastapi>=0.116.1; extra == 'fastapi'
|
|
38
|
+
Provides-Extra: instrument
|
|
39
|
+
Requires-Dist: wrapt>=1.14.0; extra == 'instrument'
|
|
24
40
|
Description-Content-Type: text/markdown
|
|
25
41
|
|
|
26
42
|
# Veris AI Python SDK
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "veris-ai"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.12.1"
|
|
8
8
|
description = "A Python package for Veris AI tools"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -156,7 +156,7 @@ tag_format = "v{version}"
|
|
|
156
156
|
[tool.uv.sources]
|
|
157
157
|
veris-ai = { workspace = true }
|
|
158
158
|
|
|
159
|
-
[
|
|
159
|
+
[project.optional-dependencies]
|
|
160
160
|
dev = [
|
|
161
161
|
"ruff>=0.12.4",
|
|
162
162
|
"pytest>=8.4.1",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import base64
|
|
1
2
|
import inspect
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
@@ -22,8 +23,9 @@ logger = logging.getLogger(__name__)
|
|
|
22
23
|
|
|
23
24
|
T = TypeVar("T")
|
|
24
25
|
|
|
25
|
-
# Context
|
|
26
|
+
# Context variables to store session_id and thread_id for each call
|
|
26
27
|
_session_id_context: ContextVar[str | None] = ContextVar("veris_session_id", default=None)
|
|
28
|
+
_thread_id_context: ContextVar[str | None] = ContextVar("veris_thread_id", default=None)
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
class VerisSDK:
|
|
@@ -38,15 +40,111 @@ class VerisSDK:
|
|
|
38
40
|
"""Get the session_id from context variable."""
|
|
39
41
|
return _session_id_context.get()
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
@property
|
|
44
|
+
def thread_id(self) -> str | None:
|
|
45
|
+
"""Get the thread_id from context variable."""
|
|
46
|
+
return _thread_id_context.get()
|
|
47
|
+
|
|
48
|
+
def _set_session_id(self, session_id: str) -> None:
|
|
49
|
+
"""Set the session_id in context variable (private method)."""
|
|
43
50
|
_session_id_context.set(session_id)
|
|
44
|
-
logger.info(f"Session ID set to {session_id}")
|
|
45
51
|
|
|
46
|
-
def
|
|
47
|
-
"""
|
|
52
|
+
def _set_thread_id(self, thread_id: str) -> None:
|
|
53
|
+
"""Set the thread_id in context variable (private method)."""
|
|
54
|
+
_thread_id_context.set(thread_id)
|
|
55
|
+
|
|
56
|
+
def parse_token(self, token: str) -> None:
|
|
57
|
+
"""Parse and set session_id and thread_id from a base64-encoded token.
|
|
58
|
+
|
|
59
|
+
The token must be a base64-encoded JSON object containing:
|
|
60
|
+
{"session_id": "...", "thread_id": "..."}
|
|
61
|
+
|
|
62
|
+
Both session_id and thread_id are required fields.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
token: Base64-encoded JSON token
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
ValueError: If token is not valid or missing required fields
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
# Decode base64 JSON token
|
|
72
|
+
decoded = base64.b64decode(token.encode("utf-8")).decode("utf-8")
|
|
73
|
+
token_data = json.loads(decoded)
|
|
74
|
+
|
|
75
|
+
if not isinstance(token_data, dict):
|
|
76
|
+
raise ValueError("Token must decode to a JSON object")
|
|
77
|
+
|
|
78
|
+
if "session_id" not in token_data:
|
|
79
|
+
raise ValueError("Token must contain 'session_id' field")
|
|
80
|
+
|
|
81
|
+
if "thread_id" not in token_data:
|
|
82
|
+
raise ValueError("Token must contain 'thread_id' field")
|
|
83
|
+
|
|
84
|
+
self._set_session_id(token_data["session_id"])
|
|
85
|
+
self._set_thread_id(token_data["thread_id"])
|
|
86
|
+
logger.info(
|
|
87
|
+
f"Session ID set to {token_data['session_id']}, "
|
|
88
|
+
f"Thread ID set to {token_data['thread_id']} - mocking enabled"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
except (ValueError, json.JSONDecodeError) as e:
|
|
92
|
+
error_msg = f"Invalid token format: {e}"
|
|
93
|
+
raise ValueError(error_msg) from e
|
|
94
|
+
|
|
95
|
+
def set_session_id(self, token: str) -> None:
|
|
96
|
+
"""DEPRECATED: Use parse_token() instead.
|
|
97
|
+
|
|
98
|
+
Set the session_id and thread_id from a base64-encoded token.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
token: Base64-encoded JSON token
|
|
102
|
+
"""
|
|
103
|
+
logger.warning(
|
|
104
|
+
"set_session_id() is deprecated. Use parse_token() instead. "
|
|
105
|
+
"This method will be removed in a future version."
|
|
106
|
+
)
|
|
107
|
+
# For backwards compatibility, allow tokens without thread_id
|
|
108
|
+
try:
|
|
109
|
+
decoded = base64.b64decode(token.encode("utf-8")).decode("utf-8")
|
|
110
|
+
token_data = json.loads(decoded)
|
|
111
|
+
|
|
112
|
+
if not isinstance(token_data, dict):
|
|
113
|
+
raise ValueError("Token must decode to a JSON object")
|
|
114
|
+
|
|
115
|
+
if "session_id" not in token_data:
|
|
116
|
+
raise ValueError("Token must contain 'session_id' field")
|
|
117
|
+
|
|
118
|
+
self._set_session_id(token_data["session_id"])
|
|
119
|
+
if "thread_id" in token_data:
|
|
120
|
+
self._set_thread_id(token_data["thread_id"])
|
|
121
|
+
logger.info(f"Session ID set to {token_data['session_id']}")
|
|
122
|
+
|
|
123
|
+
except (ValueError, json.JSONDecodeError) as e:
|
|
124
|
+
error_msg = f"Invalid token format: {e}"
|
|
125
|
+
raise ValueError(error_msg) from e
|
|
126
|
+
|
|
127
|
+
def _clear_session_id(self) -> None:
|
|
128
|
+
"""Clear the session_id from context variable (private method)."""
|
|
48
129
|
_session_id_context.set(None)
|
|
49
|
-
|
|
130
|
+
|
|
131
|
+
def _clear_thread_id(self) -> None:
|
|
132
|
+
"""Clear the thread_id from context variable (private method)."""
|
|
133
|
+
_thread_id_context.set(None)
|
|
134
|
+
|
|
135
|
+
def clear_context(self) -> None:
|
|
136
|
+
"""Clear the session_id and thread_id from context variables."""
|
|
137
|
+
self._clear_session_id()
|
|
138
|
+
self._clear_thread_id()
|
|
139
|
+
logger.info("Session ID and Thread ID cleared - mocking disabled")
|
|
140
|
+
|
|
141
|
+
def clear_session_id(self) -> None:
|
|
142
|
+
"""DEPRECATED: Use clear_context() instead."""
|
|
143
|
+
logger.warning(
|
|
144
|
+
"clear_session_id() is deprecated. Use clear_context() instead. "
|
|
145
|
+
"This method will be removed in a future version."
|
|
146
|
+
)
|
|
147
|
+
self.clear_context()
|
|
50
148
|
|
|
51
149
|
@property
|
|
52
150
|
def fastapi_mcp(self) -> Any | None: # noqa: ANN401
|
|
@@ -69,7 +167,7 @@ class VerisSDK:
|
|
|
69
167
|
token: str | None = Depends(oauth2_scheme),
|
|
70
168
|
) -> None:
|
|
71
169
|
if token:
|
|
72
|
-
self.
|
|
170
|
+
self.parse_token(token)
|
|
73
171
|
|
|
74
172
|
# Create auth config with dependencies
|
|
75
173
|
auth_config = AuthConfig(
|
|
@@ -176,11 +274,13 @@ class VerisSDK:
|
|
|
176
274
|
)
|
|
177
275
|
return await func(*args, **kwargs)
|
|
178
276
|
parameters = get_function_parameters(func, args, kwargs)
|
|
277
|
+
thread_id = _thread_id_context.get()
|
|
179
278
|
return mock_tool_call(
|
|
180
279
|
func,
|
|
181
280
|
session_id,
|
|
182
281
|
parameters,
|
|
183
282
|
options,
|
|
283
|
+
thread_id,
|
|
184
284
|
)
|
|
185
285
|
|
|
186
286
|
@wraps(func)
|
|
@@ -196,11 +296,13 @@ class VerisSDK:
|
|
|
196
296
|
)
|
|
197
297
|
return func(*args, **kwargs)
|
|
198
298
|
parameters = get_function_parameters(func, args, kwargs)
|
|
299
|
+
thread_id = _thread_id_context.get()
|
|
199
300
|
return mock_tool_call(
|
|
200
301
|
func,
|
|
201
302
|
session_id,
|
|
202
303
|
parameters,
|
|
203
304
|
options,
|
|
305
|
+
thread_id,
|
|
204
306
|
)
|
|
205
307
|
|
|
206
308
|
# Return the appropriate wrapper based on whether the function is async
|
|
@@ -251,11 +353,29 @@ class VerisSDK:
|
|
|
251
353
|
)
|
|
252
354
|
def mock_tool_call(
|
|
253
355
|
func: Callable,
|
|
254
|
-
session_id: str,
|
|
356
|
+
session_id: str, # noqa: ARG001
|
|
255
357
|
parameters: dict[str, dict[str, str]],
|
|
256
358
|
options: ToolCallOptions | None = None,
|
|
359
|
+
thread_id: str | None = None,
|
|
257
360
|
) -> object:
|
|
258
|
-
"""Mock tool call.
|
|
361
|
+
"""Mock tool call.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
func: Function being mocked
|
|
365
|
+
session_id: Session ID (kept for backwards compatibility, not used)
|
|
366
|
+
parameters: Function parameters
|
|
367
|
+
options: Tool call options
|
|
368
|
+
thread_id: Thread ID to use as session_id in API request (required)
|
|
369
|
+
|
|
370
|
+
Raises:
|
|
371
|
+
ValueError: If thread_id is not provided
|
|
372
|
+
"""
|
|
373
|
+
if thread_id is None:
|
|
374
|
+
raise ValueError(
|
|
375
|
+
"thread_id is required for mocking. "
|
|
376
|
+
"Use parse_token() to set both session_id and thread_id."
|
|
377
|
+
)
|
|
378
|
+
|
|
259
379
|
options = options or ToolCallOptions()
|
|
260
380
|
api_client = get_api_client()
|
|
261
381
|
endpoint = api_client.tool_mock_endpoint
|
|
@@ -269,6 +389,8 @@ def mock_tool_call(
|
|
|
269
389
|
# Get function docstring
|
|
270
390
|
docstring = inspect.getdoc(func) or ""
|
|
271
391
|
|
|
392
|
+
# Use thread_id as session_id in the payload
|
|
393
|
+
payload_session_id = thread_id
|
|
272
394
|
# Clean up parameters for V3 - just send values, not the nested dict
|
|
273
395
|
clean_params: dict[str, Any] = {}
|
|
274
396
|
for key, value in parameters.items():
|
|
@@ -281,7 +403,7 @@ def mock_tool_call(
|
|
|
281
403
|
|
|
282
404
|
# Determine response expectation
|
|
283
405
|
payload = {
|
|
284
|
-
"session_id":
|
|
406
|
+
"session_id": payload_session_id,
|
|
285
407
|
"response_expectation": options.response_expectation.value,
|
|
286
408
|
"cache_response": bool(options.cache_response),
|
|
287
409
|
"tool_call": {
|
|
@@ -27,10 +27,14 @@ def mock_context():
|
|
|
27
27
|
|
|
28
28
|
@pytest.fixture
|
|
29
29
|
def simulation_env():
|
|
30
|
+
import base64
|
|
31
|
+
import json
|
|
30
32
|
from veris_ai import veris
|
|
31
33
|
|
|
32
|
-
# Set session_id to enable simulation mode
|
|
33
|
-
|
|
34
|
+
# Set session_id to enable simulation mode using proper token format
|
|
35
|
+
token_data = {"session_id": "test-session-123", "thread_id": "test-thread-123"}
|
|
36
|
+
token = base64.b64encode(json.dumps(token_data).encode("utf-8")).decode("utf-8")
|
|
37
|
+
veris.set_session_id(token)
|
|
34
38
|
|
|
35
39
|
with patch.dict(
|
|
36
40
|
os.environ,
|
|
@@ -17,6 +17,7 @@ if sys.version_info >= (3, 13):
|
|
|
17
17
|
from agents import Agent, FunctionTool
|
|
18
18
|
|
|
19
19
|
from veris_ai import veris, Runner
|
|
20
|
+
from tests.test_helpers import create_test_token
|
|
20
21
|
|
|
21
22
|
# Skip tests if no API key is available
|
|
22
23
|
if not os.environ.get("OPENAI_API_KEY"):
|
|
@@ -110,8 +111,9 @@ def simulation_env_with_session():
|
|
|
110
111
|
# Set up simulation environment
|
|
111
112
|
os.environ["VERIS_ENDPOINT_URL"] = "http://localhost:8000"
|
|
112
113
|
|
|
113
|
-
# Set session ID
|
|
114
|
-
|
|
114
|
+
# Set session ID using proper token format
|
|
115
|
+
token = create_test_token("test-session-123")
|
|
116
|
+
veris.set_session_id(token)
|
|
115
117
|
|
|
116
118
|
yield
|
|
117
119
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Helper functions for tests."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_test_token(
|
|
8
|
+
session_id: str = "test-session", thread_id: str | None = "test-thread"
|
|
9
|
+
) -> str:
|
|
10
|
+
"""Create a base64-encoded test token.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
session_id: Session ID to include in token
|
|
14
|
+
thread_id: Thread ID to include in token (optional)
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Base64-encoded JSON token
|
|
18
|
+
"""
|
|
19
|
+
token_data = {"session_id": session_id}
|
|
20
|
+
if thread_id is not None:
|
|
21
|
+
token_data["thread_id"] = thread_id
|
|
22
|
+
|
|
23
|
+
return base64.b64encode(json.dumps(token_data).encode("utf-8")).decode("utf-8")
|
|
@@ -14,6 +14,7 @@ from mcp.client.streamable_http import streamablehttp_client
|
|
|
14
14
|
from mcp.types import CallToolResult
|
|
15
15
|
|
|
16
16
|
from veris_ai import veris
|
|
17
|
+
from tests.test_helpers import create_test_token
|
|
17
18
|
|
|
18
19
|
from .fixtures.simple_app import make_simple_fastapi_app
|
|
19
20
|
|
|
@@ -145,11 +146,13 @@ def server_mocked(server_port_mocked: int, simulation_env: None) -> Generator[No
|
|
|
145
146
|
async def test_http_tool_call_mocked(server_mocked: None, server_url_mocked: str) -> None:
|
|
146
147
|
"""Test HTTP tool call with mocked HTTP endpoint."""
|
|
147
148
|
session_id = "test-session-id"
|
|
149
|
+
# Create a proper base64-encoded token
|
|
150
|
+
token = create_test_token(session_id, "test-thread-id")
|
|
148
151
|
|
|
149
152
|
async with (
|
|
150
153
|
streamablehttp_client(
|
|
151
154
|
server_url_mocked + "/mcp",
|
|
152
|
-
headers={"Authorization": f"Bearer {
|
|
155
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
153
156
|
) as (read_stream, write_stream, _),
|
|
154
157
|
ClientSession(read_stream, write_stream) as session,
|
|
155
158
|
):
|
|
@@ -175,8 +178,8 @@ async def test_http_tool_call_mocked(server_mocked: None, server_url_mocked: str
|
|
|
175
178
|
payload_str = payloads[0].strip()
|
|
176
179
|
payload = json.loads(payload_str)
|
|
177
180
|
|
|
178
|
-
# Verify the session_id was passed correctly
|
|
179
|
-
assert payload["session_id"] ==
|
|
181
|
+
# Verify the session_id was passed correctly (should use thread_id as value)
|
|
182
|
+
assert payload["session_id"] == "test-thread-id"
|
|
180
183
|
assert payload["tool_call"]["function_name"] == "get_item"
|
|
181
184
|
|
|
182
185
|
# Handle both direct value and nested value structure
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Tests for token decoding functionality."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from veris_ai import veris
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestTokenDecoding:
|
|
12
|
+
"""Test token decoding and session/thread ID extraction."""
|
|
13
|
+
|
|
14
|
+
def test_decode_valid_token_with_both_ids(self):
|
|
15
|
+
"""Test decoding a valid token with both session_id and thread_id."""
|
|
16
|
+
session_id = "test-session-123"
|
|
17
|
+
thread_id = "thread-456"
|
|
18
|
+
token_data = {"session_id": session_id, "thread_id": thread_id}
|
|
19
|
+
token = base64.b64encode(json.dumps(token_data).encode("utf-8")).decode("utf-8")
|
|
20
|
+
|
|
21
|
+
veris.set_session_id(token)
|
|
22
|
+
|
|
23
|
+
assert veris.session_id == session_id
|
|
24
|
+
assert veris.thread_id == thread_id
|
|
25
|
+
|
|
26
|
+
veris.clear_session_id()
|
|
27
|
+
|
|
28
|
+
def test_decode_valid_token_with_only_session_id(self):
|
|
29
|
+
"""Test decoding a token with only session_id (no thread_id)."""
|
|
30
|
+
session_id = "test-session-789"
|
|
31
|
+
token_data = {"session_id": session_id}
|
|
32
|
+
token = base64.b64encode(json.dumps(token_data).encode("utf-8")).decode("utf-8")
|
|
33
|
+
|
|
34
|
+
veris.set_session_id(token)
|
|
35
|
+
|
|
36
|
+
assert veris.session_id == session_id
|
|
37
|
+
assert veris.thread_id is None
|
|
38
|
+
|
|
39
|
+
veris.clear_session_id()
|
|
40
|
+
|
|
41
|
+
def test_decode_token_with_uuid_format(self):
|
|
42
|
+
"""Test decoding token with UUID-formatted IDs."""
|
|
43
|
+
session_id = "550e8400-e29b-41d4-a716-446655440000"
|
|
44
|
+
thread_id = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
|
45
|
+
token_data = {"session_id": session_id, "thread_id": thread_id}
|
|
46
|
+
token = base64.b64encode(json.dumps(token_data).encode("utf-8")).decode("utf-8")
|
|
47
|
+
|
|
48
|
+
veris.set_session_id(token)
|
|
49
|
+
|
|
50
|
+
assert veris.session_id == session_id
|
|
51
|
+
assert veris.thread_id == thread_id
|
|
52
|
+
|
|
53
|
+
veris.clear_session_id()
|
|
54
|
+
|
|
55
|
+
def test_decode_invalid_base64_raises_error(self):
|
|
56
|
+
"""Test that invalid base64 raises ValueError."""
|
|
57
|
+
invalid_token = "not-valid-base64!!!"
|
|
58
|
+
|
|
59
|
+
with pytest.raises(ValueError, match="Invalid token format"):
|
|
60
|
+
veris.set_session_id(invalid_token)
|
|
61
|
+
|
|
62
|
+
def test_decode_invalid_json_raises_error(self):
|
|
63
|
+
"""Test that valid base64 but invalid JSON raises ValueError."""
|
|
64
|
+
invalid_json = base64.b64encode(b"not valid json").decode("utf-8")
|
|
65
|
+
|
|
66
|
+
with pytest.raises(ValueError, match="Invalid token format"):
|
|
67
|
+
veris.set_session_id(invalid_json)
|
|
68
|
+
|
|
69
|
+
def test_decode_non_object_json_raises_error(self):
|
|
70
|
+
"""Test that JSON array instead of object raises ValueError."""
|
|
71
|
+
json_array = base64.b64encode(
|
|
72
|
+
json.dumps(["array", "not", "object"]).encode("utf-8")
|
|
73
|
+
).decode("utf-8")
|
|
74
|
+
|
|
75
|
+
with pytest.raises(ValueError, match="Token must decode to a JSON object"):
|
|
76
|
+
veris.set_session_id(json_array)
|
|
77
|
+
|
|
78
|
+
def test_decode_missing_session_id_raises_error(self):
|
|
79
|
+
"""Test that token without session_id raises ValueError."""
|
|
80
|
+
token_data = {"thread_id": "thread-only"}
|
|
81
|
+
token = base64.b64encode(json.dumps(token_data).encode("utf-8")).decode("utf-8")
|
|
82
|
+
|
|
83
|
+
with pytest.raises(ValueError, match="Token must contain 'session_id' field"):
|
|
84
|
+
veris.set_session_id(token)
|
|
85
|
+
|
|
86
|
+
def test_clear_session_id_clears_both_values(self):
|
|
87
|
+
"""Test that clear_session_id clears both session_id and thread_id."""
|
|
88
|
+
session_id = "test-session"
|
|
89
|
+
thread_id = "test-thread"
|
|
90
|
+
token_data = {"session_id": session_id, "thread_id": thread_id}
|
|
91
|
+
token = base64.b64encode(json.dumps(token_data).encode("utf-8")).decode("utf-8")
|
|
92
|
+
|
|
93
|
+
veris.set_session_id(token)
|
|
94
|
+
assert veris.session_id == session_id
|
|
95
|
+
assert veris.thread_id == thread_id
|
|
96
|
+
|
|
97
|
+
veris.clear_session_id()
|
|
98
|
+
assert veris.session_id is None
|
|
99
|
+
assert veris.thread_id is None
|
|
100
|
+
|
|
101
|
+
def test_decode_token_with_additional_fields(self):
|
|
102
|
+
"""Test that additional fields in token are ignored."""
|
|
103
|
+
session_id = "test-session"
|
|
104
|
+
thread_id = "test-thread"
|
|
105
|
+
token_data = {
|
|
106
|
+
"session_id": session_id,
|
|
107
|
+
"thread_id": thread_id,
|
|
108
|
+
"extra_field": "ignored",
|
|
109
|
+
"another": 123,
|
|
110
|
+
}
|
|
111
|
+
token = base64.b64encode(json.dumps(token_data).encode("utf-8")).decode("utf-8")
|
|
112
|
+
|
|
113
|
+
veris.set_session_id(token)
|
|
114
|
+
|
|
115
|
+
assert veris.session_id == session_id
|
|
116
|
+
assert veris.thread_id == thread_id
|
|
117
|
+
|
|
118
|
+
veris.clear_session_id()
|
|
119
|
+
|
|
120
|
+
def test_decode_token_with_empty_strings(self):
|
|
121
|
+
"""Test decoding token with empty string values."""
|
|
122
|
+
session_id = ""
|
|
123
|
+
thread_id = ""
|
|
124
|
+
token_data = {"session_id": session_id, "thread_id": thread_id}
|
|
125
|
+
token = base64.b64encode(json.dumps(token_data).encode("utf-8")).decode("utf-8")
|
|
126
|
+
|
|
127
|
+
veris.set_session_id(token)
|
|
128
|
+
|
|
129
|
+
assert veris.session_id == session_id
|
|
130
|
+
assert veris.thread_id == thread_id
|
|
131
|
+
|
|
132
|
+
veris.clear_session_id()
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import base64
|
|
3
|
+
import json
|
|
2
4
|
import os
|
|
3
5
|
from typing import Any
|
|
4
6
|
from unittest.mock import AsyncMock, Mock, patch
|
|
@@ -11,6 +13,16 @@ from veris_ai.api_client import get_api_client
|
|
|
11
13
|
from veris_ai.tool_mock import _session_id_context
|
|
12
14
|
|
|
13
15
|
|
|
16
|
+
def create_test_token(
|
|
17
|
+
session_id: str = "test-session", thread_id: str | None = "test-thread"
|
|
18
|
+
) -> str:
|
|
19
|
+
"""Create a base64-encoded test token."""
|
|
20
|
+
token_data = {"session_id": session_id}
|
|
21
|
+
if thread_id is not None:
|
|
22
|
+
token_data["thread_id"] = thread_id
|
|
23
|
+
return base64.b64encode(json.dumps(token_data).encode("utf-8")).decode("utf-8")
|
|
24
|
+
|
|
25
|
+
|
|
14
26
|
@pytest.fixture
|
|
15
27
|
def tool_mock():
|
|
16
28
|
return veris
|
|
@@ -50,12 +62,15 @@ async def test_mock_with_context(simulation_env):
|
|
|
50
62
|
|
|
51
63
|
with patch.object(get_api_client(), "post") as mock_request:
|
|
52
64
|
mock_request.return_value = mock_response
|
|
53
|
-
|
|
65
|
+
token = create_test_token("test-session-id", "test-thread-id")
|
|
66
|
+
veris.parse_token(token)
|
|
54
67
|
result = await test_func()
|
|
55
|
-
# Check that the mock was called with
|
|
68
|
+
# Check that the mock was called with thread_id as session_id value
|
|
56
69
|
mock_request.assert_called_once()
|
|
57
70
|
call_args = mock_request.call_args
|
|
58
|
-
assert
|
|
71
|
+
assert (
|
|
72
|
+
call_args[0][1]["session_id"] == "test-thread-id"
|
|
73
|
+
) # thread_id used as session_id value
|
|
59
74
|
assert result == {"result": {"mocked": True}}
|
|
60
75
|
|
|
61
76
|
|
|
@@ -68,7 +83,7 @@ async def test_mock_without_context():
|
|
|
68
83
|
return {"result": "real"}
|
|
69
84
|
|
|
70
85
|
# Clear session_id to ensure production mode
|
|
71
|
-
veris.
|
|
86
|
+
veris.clear_context()
|
|
72
87
|
|
|
73
88
|
# In production mode, the original function should be called
|
|
74
89
|
result = await test_func()
|
|
@@ -100,7 +115,8 @@ async def test_mock_missing_endpoint():
|
|
|
100
115
|
return {"result": "real"}
|
|
101
116
|
|
|
102
117
|
# Set session_id to enable simulation mode
|
|
103
|
-
|
|
118
|
+
token = create_test_token("test-session")
|
|
119
|
+
veris.parse_token(token)
|
|
104
120
|
|
|
105
121
|
# Invalid domain will cause ConnectError
|
|
106
122
|
with pytest.raises(httpx.ConnectError):
|
|
@@ -109,6 +125,7 @@ async def test_mock_missing_endpoint():
|
|
|
109
125
|
|
|
110
126
|
@pytest.mark.asyncio
|
|
111
127
|
async def test_mock_invalid_endpoint(simulation_env):
|
|
128
|
+
"""Test that invalid endpoint URL raises ConnectError."""
|
|
112
129
|
# Use invalid URL that will trigger ConnectError
|
|
113
130
|
with (
|
|
114
131
|
patch.dict(os.environ, {"VERIS_API_URL": "http://invalid-nonexistent-domain-67890.local"}),
|
|
@@ -141,7 +158,8 @@ async def test_context_var_isolation():
|
|
|
141
158
|
"""Test that session_id context vars are isolated between concurrent calls."""
|
|
142
159
|
|
|
143
160
|
async def set_and_check_session(session_id: str):
|
|
144
|
-
|
|
161
|
+
token = create_test_token(session_id)
|
|
162
|
+
veris.parse_token(token)
|
|
145
163
|
# Verify the session_id is set correctly
|
|
146
164
|
assert veris.session_id == session_id
|
|
147
165
|
# Simulate some async work
|
|
@@ -181,7 +199,8 @@ async def test_context_var_persistence_in_call(simulation_env):
|
|
|
181
199
|
|
|
182
200
|
with patch.object(get_api_client(), "post", side_effect=mock_request_func) as mock_request:
|
|
183
201
|
# Set session_id and call function
|
|
184
|
-
|
|
202
|
+
token = create_test_token("test-session-123", "test-thread-123")
|
|
203
|
+
veris.parse_token(token)
|
|
185
204
|
await test_func()
|
|
186
205
|
|
|
187
206
|
# Verify session_id was captured
|
|
@@ -189,21 +208,22 @@ async def test_context_var_persistence_in_call(simulation_env):
|
|
|
189
208
|
assert len(captured_session_ids) >= 1
|
|
190
209
|
assert captured_session_ids[0] == "test-session-123"
|
|
191
210
|
|
|
192
|
-
# Verify the HTTP client was called with correct session_id
|
|
211
|
+
# Verify the HTTP client was called with correct session_id (should use thread_id)
|
|
193
212
|
mock_request.assert_called_once()
|
|
194
213
|
call_args = mock_request.call_args
|
|
195
|
-
assert call_args[0][1]["session_id"] == "test-
|
|
214
|
+
assert call_args[0][1]["session_id"] == "test-thread-123"
|
|
196
215
|
|
|
197
216
|
|
|
198
217
|
@pytest.mark.asyncio
|
|
199
218
|
async def test_context_var_cleanup():
|
|
200
219
|
"""Test that clearing session_id works correctly."""
|
|
201
220
|
# Set a session_id
|
|
202
|
-
|
|
221
|
+
token = create_test_token("test-session")
|
|
222
|
+
veris.parse_token(token)
|
|
203
223
|
assert veris.session_id == "test-session"
|
|
204
224
|
|
|
205
225
|
# Clear it
|
|
206
|
-
veris.
|
|
226
|
+
veris.clear_context()
|
|
207
227
|
assert veris.session_id is None
|
|
208
228
|
|
|
209
229
|
# Verify it stays None
|
|
@@ -217,7 +237,8 @@ async def test_context_var_no_interference():
|
|
|
217
237
|
results = []
|
|
218
238
|
|
|
219
239
|
async def task1():
|
|
220
|
-
|
|
240
|
+
token = create_test_token("task1-session")
|
|
241
|
+
veris.parse_token(token)
|
|
221
242
|
await asyncio.sleep(0.02)
|
|
222
243
|
results.append(("task1", veris.session_id))
|
|
223
244
|
|
|
@@ -225,7 +246,8 @@ async def test_context_var_no_interference():
|
|
|
225
246
|
await asyncio.sleep(0.01)
|
|
226
247
|
# Should be None since we didn't set it in this context
|
|
227
248
|
results.append(("task2", veris.session_id))
|
|
228
|
-
|
|
249
|
+
token = create_test_token("task2-session")
|
|
250
|
+
veris.parse_token(token)
|
|
229
251
|
results.append(("task2-after", veris.session_id))
|
|
230
252
|
|
|
231
253
|
await asyncio.gather(task1(), task2())
|
|
@@ -251,7 +273,8 @@ async def test_spy_decorator_with_session(simulation_env):
|
|
|
251
273
|
patch("veris_ai.tool_mock.log_tool_call") as mock_log_call,
|
|
252
274
|
patch("veris_ai.tool_mock.log_tool_response") as mock_log_response,
|
|
253
275
|
):
|
|
254
|
-
|
|
276
|
+
token = create_test_token("spy-session-123")
|
|
277
|
+
veris.parse_token(token)
|
|
255
278
|
result = await test_func(42, "hello")
|
|
256
279
|
|
|
257
280
|
# Verify the original function was executed
|
|
@@ -281,7 +304,7 @@ async def test_spy_decorator_without_session():
|
|
|
281
304
|
return x * 2
|
|
282
305
|
|
|
283
306
|
# Clear session to ensure no spying
|
|
284
|
-
veris.
|
|
307
|
+
veris.clear_context()
|
|
285
308
|
|
|
286
309
|
with (
|
|
287
310
|
patch("veris_ai.tool_mock.log_tool_call") as mock_log_call,
|
|
@@ -309,7 +332,8 @@ def test_spy_decorator_sync_function(simulation_env):
|
|
|
309
332
|
patch("veris_ai.tool_mock.log_tool_call") as mock_log_call,
|
|
310
333
|
patch("veris_ai.tool_mock.log_tool_response") as mock_log_response,
|
|
311
334
|
):
|
|
312
|
-
|
|
335
|
+
token = create_test_token("sync-spy-session")
|
|
336
|
+
veris.parse_token(token)
|
|
313
337
|
result = sync_func(10, 20)
|
|
314
338
|
|
|
315
339
|
# Verify the original function was executed
|
|
@@ -335,7 +359,7 @@ def test_spy_decorator_sync_without_session():
|
|
|
335
359
|
def sync_func(x: str) -> str:
|
|
336
360
|
return x.upper()
|
|
337
361
|
|
|
338
|
-
veris.
|
|
362
|
+
veris.clear_context()
|
|
339
363
|
|
|
340
364
|
with (
|
|
341
365
|
patch("veris_ai.tool_mock.log_tool_call") as mock_log_call,
|
|
@@ -365,7 +389,8 @@ async def test_spy_decorator_preserves_metadata():
|
|
|
365
389
|
assert documented_spy_function.__doc__ == "This is a documented spy function."
|
|
366
390
|
|
|
367
391
|
# Test that it works correctly
|
|
368
|
-
|
|
392
|
+
token = create_test_token("test-spy")
|
|
393
|
+
veris.parse_token(token)
|
|
369
394
|
result = await documented_spy_function(100)
|
|
370
395
|
assert result == "100: default"
|
|
371
396
|
|
|
@@ -385,7 +410,8 @@ async def test_spy_decorator_with_exception():
|
|
|
385
410
|
patch("veris_ai.tool_mock.log_tool_call") as mock_log_call,
|
|
386
411
|
patch("veris_ai.tool_mock.log_tool_response") as mock_log_response,
|
|
387
412
|
):
|
|
388
|
-
|
|
413
|
+
token = create_test_token("exception-spy-session")
|
|
414
|
+
veris.parse_token(token)
|
|
389
415
|
|
|
390
416
|
# Test normal execution
|
|
391
417
|
result = await failing_func(5)
|
|
@@ -16,6 +16,7 @@ if sys.version_info >= (3, 13):
|
|
|
16
16
|
from agents import Agent, FunctionTool
|
|
17
17
|
|
|
18
18
|
from veris_ai import veris, Runner, VerisConfig, ToolCallOptions, ResponseExpectation
|
|
19
|
+
from tests.test_helpers import create_test_token
|
|
19
20
|
|
|
20
21
|
# Skip tests if no API key is available
|
|
21
22
|
if not os.environ.get("OPENAI_API_KEY"):
|
|
@@ -131,8 +132,9 @@ def mock_api_client_with_options():
|
|
|
131
132
|
@pytest.mark.asyncio
|
|
132
133
|
async def test_tool_options_passed_to_mock_call(mock_api_client_with_options, multi_tool_agent):
|
|
133
134
|
"""Verify that ToolCallOptions are correctly passed to the mock_tool_call function."""
|
|
134
|
-
# Set up session for mocking
|
|
135
|
-
|
|
135
|
+
# Set up session for mocking using proper token format
|
|
136
|
+
token = create_test_token("test-session-tool-options")
|
|
137
|
+
veris.set_session_id(token)
|
|
136
138
|
|
|
137
139
|
try:
|
|
138
140
|
config = VerisConfig(
|
|
@@ -260,16 +260,16 @@ wheels = [
|
|
|
260
260
|
|
|
261
261
|
[[package]]
|
|
262
262
|
name = "fastapi"
|
|
263
|
-
version = "0.118.
|
|
263
|
+
version = "0.118.2"
|
|
264
264
|
source = { registry = "https://pypi.org/simple" }
|
|
265
265
|
dependencies = [
|
|
266
266
|
{ name = "pydantic" },
|
|
267
267
|
{ name = "starlette" },
|
|
268
268
|
{ name = "typing-extensions" },
|
|
269
269
|
]
|
|
270
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
270
|
+
sdist = { url = "https://files.pythonhosted.org/packages/2e/ad/31a59efecca3b584440cafac6f69634f4661295c858912c2b2905280a089/fastapi-0.118.2.tar.gz", hash = "sha256:d5388dbe76d97cb6ccd2c93b4dd981608062ebf6335280edfa9a11af82443e18", size = 311963, upload-time = "2025-10-08T14:52:17.796Z" }
|
|
271
271
|
wheels = [
|
|
272
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
272
|
+
{ url = "https://files.pythonhosted.org/packages/45/7c/97d033faf771c9fe960c7b51eb78ab266bfa64cbc917601978963f0c3c7b/fastapi-0.118.2-py3-none-any.whl", hash = "sha256:d1f842612e6a305f95abe784b7f8d3215477742e7c67a16fccd20bd79db68150", size = 97954, upload-time = "2025-10-08T14:52:16.166Z" },
|
|
273
273
|
]
|
|
274
274
|
|
|
275
275
|
[[package]]
|
|
@@ -295,11 +295,11 @@ wheels = [
|
|
|
295
295
|
|
|
296
296
|
[[package]]
|
|
297
297
|
name = "filelock"
|
|
298
|
-
version = "3.
|
|
298
|
+
version = "3.20.0"
|
|
299
299
|
source = { registry = "https://pypi.org/simple" }
|
|
300
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
300
|
+
sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" }
|
|
301
301
|
wheels = [
|
|
302
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
302
|
+
{ url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" },
|
|
303
303
|
]
|
|
304
304
|
|
|
305
305
|
[[package]]
|
|
@@ -936,11 +936,11 @@ wheels = [
|
|
|
936
936
|
|
|
937
937
|
[[package]]
|
|
938
938
|
name = "platformdirs"
|
|
939
|
-
version = "4.
|
|
939
|
+
version = "4.5.0"
|
|
940
940
|
source = { registry = "https://pypi.org/simple" }
|
|
941
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
941
|
+
sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" }
|
|
942
942
|
wheels = [
|
|
943
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
943
|
+
{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
|
|
944
944
|
]
|
|
945
945
|
|
|
946
946
|
[[package]]
|
|
@@ -1571,7 +1571,7 @@ wheels = [
|
|
|
1571
1571
|
|
|
1572
1572
|
[[package]]
|
|
1573
1573
|
name = "veris-ai"
|
|
1574
|
-
version = "1.
|
|
1574
|
+
version = "1.12.0"
|
|
1575
1575
|
source = { editable = "." }
|
|
1576
1576
|
dependencies = [
|
|
1577
1577
|
{ name = "httpx" },
|
|
@@ -1589,7 +1589,7 @@ dependencies = [
|
|
|
1589
1589
|
{ name = "tenacity" },
|
|
1590
1590
|
]
|
|
1591
1591
|
|
|
1592
|
-
[package.
|
|
1592
|
+
[package.optional-dependencies]
|
|
1593
1593
|
agents = [
|
|
1594
1594
|
{ name = "openai-agents" },
|
|
1595
1595
|
]
|
|
@@ -1613,8 +1613,12 @@ instrument = [
|
|
|
1613
1613
|
|
|
1614
1614
|
[package.metadata]
|
|
1615
1615
|
requires-dist = [
|
|
1616
|
+
{ name = "fastapi", marker = "extra == 'fastapi'", specifier = ">=0.116.1" },
|
|
1617
|
+
{ name = "fastapi-mcp", marker = "extra == 'fastapi'", specifier = ">=0.4.0" },
|
|
1616
1618
|
{ name = "httpx", specifier = ">=0.24.0" },
|
|
1617
1619
|
{ name = "logfire", specifier = ">=4.3.3" },
|
|
1620
|
+
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.17.0" },
|
|
1621
|
+
{ name = "openai-agents", marker = "extra == 'agents'", specifier = ">=0.2.5" },
|
|
1618
1622
|
{ name = "opentelemetry-api", specifier = ">=1.34.1" },
|
|
1619
1623
|
{ name = "opentelemetry-exporter-otlp", specifier = ">=1.34.1" },
|
|
1620
1624
|
{ name = "opentelemetry-instrumentation", specifier = ">=0.55b1" },
|
|
@@ -1623,28 +1627,19 @@ requires-dist = [
|
|
|
1623
1627
|
{ name = "opentelemetry-instrumentation-mcp", specifier = ">=0.44.1" },
|
|
1624
1628
|
{ name = "opentelemetry-instrumentation-requests", specifier = ">=0.55b1" },
|
|
1625
1629
|
{ name = "opentelemetry-sdk", specifier = ">=1.34.1" },
|
|
1630
|
+
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.3.3" },
|
|
1626
1631
|
{ name = "pydantic", specifier = ">=2.0.0" },
|
|
1632
|
+
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.4.1" },
|
|
1633
|
+
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=1.1.0" },
|
|
1634
|
+
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.2.1" },
|
|
1627
1635
|
{ name = "requests", specifier = ">=2.31.0" },
|
|
1636
|
+
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.12.4" },
|
|
1628
1637
|
{ name = "tenacity", specifier = ">=9.1.2" },
|
|
1638
|
+
{ name = "types-requests", marker = "extra == 'dev'", specifier = ">=2.32.4.20250611" },
|
|
1639
|
+
{ name = "uvicorn", marker = "extra == 'dev'", specifier = ">=0.30.0" },
|
|
1640
|
+
{ name = "wrapt", marker = "extra == 'instrument'", specifier = ">=1.14.0" },
|
|
1629
1641
|
]
|
|
1630
|
-
|
|
1631
|
-
[package.metadata.requires-dev]
|
|
1632
|
-
agents = [{ name = "openai-agents", specifier = ">=0.2.5" }]
|
|
1633
|
-
dev = [
|
|
1634
|
-
{ name = "mypy", specifier = ">=1.17.0" },
|
|
1635
|
-
{ name = "pre-commit", specifier = ">=3.3.3" },
|
|
1636
|
-
{ name = "pytest", specifier = ">=8.4.1" },
|
|
1637
|
-
{ name = "pytest-asyncio", specifier = ">=1.1.0" },
|
|
1638
|
-
{ name = "pytest-cov", specifier = ">=6.2.1" },
|
|
1639
|
-
{ name = "ruff", specifier = ">=0.12.4" },
|
|
1640
|
-
{ name = "types-requests", specifier = ">=2.32.4.20250611" },
|
|
1641
|
-
{ name = "uvicorn", specifier = ">=0.30.0" },
|
|
1642
|
-
]
|
|
1643
|
-
fastapi = [
|
|
1644
|
-
{ name = "fastapi", specifier = ">=0.116.1" },
|
|
1645
|
-
{ name = "fastapi-mcp", specifier = ">=0.4.0" },
|
|
1646
|
-
]
|
|
1647
|
-
instrument = [{ name = "wrapt", specifier = ">=1.14.0" }]
|
|
1642
|
+
provides-extras = ["dev", "fastapi", "agents", "instrument"]
|
|
1648
1643
|
|
|
1649
1644
|
[[package]]
|
|
1650
1645
|
name = "virtualenv"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|