jaf-py 2.2.1__tar.gz → 2.2.3__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.
- {jaf_py-2.2.1/jaf_py.egg-info → jaf_py-2.2.3}/PKG-INFO +2 -1
- {jaf_py-2.2.1 → jaf_py-2.2.3}/README.md +1 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/__init__.py +5 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/engine.py +1 -1
- jaf_py-2.2.3/jaf/core/proxy.py +141 -0
- jaf_py-2.2.3/jaf/core/proxy_helpers.py +126 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/providers/model.py +24 -6
- {jaf_py-2.2.1 → jaf_py-2.2.3/jaf_py.egg-info}/PKG-INFO +2 -1
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf_py.egg-info/SOURCES.txt +3 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/pyproject.toml +1 -1
- jaf_py-2.2.3/tests/test_proxy_simple.py +117 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/LICENSE +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/agent.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/agent_card.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/client.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/examples/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/examples/client_example.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/examples/integration_example.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/examples/rag_demo/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/examples/server_demo/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/examples/server_example.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/cleanup.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/factory.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/providers/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/providers/composite.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/providers/in_memory.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/providers/postgres.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/providers/redis.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/serialization.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/tests/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/tests/run_comprehensive_tests.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/tests/test_cleanup.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/tests/test_serialization.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/tests/test_stress_concurrency.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/tests/test_task_lifecycle.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/memory/types.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/protocol.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/server.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/standalone_client.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/tests/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/tests/run_tests.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/tests/test_agent.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/tests/test_client.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/tests/test_integration.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/tests/test_protocol.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/tests/test_types.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/a2a/types.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/cli.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/agent_tool.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/analytics.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/composition.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/errors.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/performance.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/streaming.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/tool_results.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/tools.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/tracing.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/types.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/core/workflows.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/exceptions.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/memory/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/memory/factory.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/memory/providers/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/memory/providers/in_memory.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/memory/providers/postgres.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/memory/providers/redis.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/memory/types.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/memory/utils.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/plugins/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/plugins/base.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/policies/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/policies/handoff.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/policies/validation.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/providers/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/providers/mcp.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/server/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/server/main.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/server/server.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/server/types.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/visualization/__init__.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/visualization/example.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/visualization/functional_core.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/visualization/graphviz.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/visualization/imperative_shell.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf/visualization/types.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf_py.egg-info/dependency_links.txt +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf_py.egg-info/entry_points.txt +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf_py.egg-info/requires.txt +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/jaf_py.egg-info/top_level.txt +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/setup.cfg +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/setup.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_a2a_deep.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_a2a_examples.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_api_reference_examples.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_callback_system_examples.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_coffee_tool.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_conversation_id_fix.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_deployment_examples.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_docs_code_examples.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_engine.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_engine_manual.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_error_handling_examples.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_getting_started_examples.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_math_tool.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_mcp_comprehensive.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_mcp_docs.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_mcp_real_functionality.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_mcp_transports.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_memory_system_examples.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_model_providers_examples.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_property_based.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_redis_fixes.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_redis_memory.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_server_api_examples.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_session_continuity.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_streamable_http_mcp_example.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_timeout_functionality.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_tool_integration.py +0 -0
- {jaf_py-2.2.1 → jaf_py-2.2.3}/tests/test_validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jaf-py
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.3
|
|
4
4
|
Summary: A purely functional agent framework with immutable state and composable tools - Python implementation
|
|
5
5
|
Author: JAF Contributors
|
|
6
6
|
Maintainer: JAF Contributors
|
|
@@ -110,6 +110,7 @@ A purely functional agent framework with immutable state and composable tools, p
|
|
|
110
110
|
- ✅ **Output Guardrails**: Response sanitization
|
|
111
111
|
- ✅ **Permission System**: Role-based access control
|
|
112
112
|
- ✅ **Audit Logging**: Complete interaction tracing
|
|
113
|
+
- ✅ **Proxy Support**: Corporate proxy integration with authentication
|
|
113
114
|
|
|
114
115
|
### 📊 **Observability & Monitoring**
|
|
115
116
|
- ✅ **Real-time Tracing**: Event-driven observability
|
|
@@ -39,6 +39,7 @@ A purely functional agent framework with immutable state and composable tools, p
|
|
|
39
39
|
- ✅ **Output Guardrails**: Response sanitization
|
|
40
40
|
- ✅ **Permission System**: Role-based access control
|
|
41
41
|
- ✅ **Audit Logging**: Complete interaction tracing
|
|
42
|
+
- ✅ **Proxy Support**: Corporate proxy integration with authentication
|
|
42
43
|
|
|
43
44
|
### 📊 **Observability & Monitoring**
|
|
44
45
|
- ✅ **Real-time Tracing**: Event-driven observability
|
|
@@ -13,6 +13,7 @@ from .agent_tool import (
|
|
|
13
13
|
get_current_run_config,
|
|
14
14
|
set_current_run_config,
|
|
15
15
|
)
|
|
16
|
+
from .proxy import ProxyConfig, ProxyAuth, create_proxy_config, get_default_proxy_config
|
|
16
17
|
|
|
17
18
|
__all__ = [
|
|
18
19
|
"Agent",
|
|
@@ -22,6 +23,8 @@ __all__ = [
|
|
|
22
23
|
"Message",
|
|
23
24
|
"ModelConfig",
|
|
24
25
|
"ModelProvider",
|
|
26
|
+
"ProxyAuth",
|
|
27
|
+
"ProxyConfig",
|
|
25
28
|
"RunConfig",
|
|
26
29
|
"RunId",
|
|
27
30
|
"RunResult",
|
|
@@ -39,9 +42,11 @@ __all__ = [
|
|
|
39
42
|
"create_conditional_enabler",
|
|
40
43
|
"create_default_output_extractor",
|
|
41
44
|
"create_json_output_extractor",
|
|
45
|
+
"create_proxy_config",
|
|
42
46
|
"create_run_id",
|
|
43
47
|
"create_trace_id",
|
|
44
48
|
"get_current_run_config",
|
|
49
|
+
"get_default_proxy_config",
|
|
45
50
|
"require_permissions",
|
|
46
51
|
"run",
|
|
47
52
|
"set_current_run_config",
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Proxy configuration for JAF agents and HTTP clients.
|
|
3
|
+
|
|
4
|
+
This module provides unified proxy configuration that can be used across
|
|
5
|
+
different HTTP clients (httpx, OpenAI, etc.) in the JAF framework.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Optional, Dict, Any, Union
|
|
11
|
+
from urllib.parse import urlparse
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ProxyAuth:
|
|
16
|
+
"""Proxy authentication configuration."""
|
|
17
|
+
username: str
|
|
18
|
+
password: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ProxyConfig:
|
|
23
|
+
"""Proxy configuration for HTTP clients."""
|
|
24
|
+
http_proxy: Optional[str] = None
|
|
25
|
+
https_proxy: Optional[str] = None
|
|
26
|
+
no_proxy: Optional[str] = None
|
|
27
|
+
auth: Optional[ProxyAuth] = None
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_environment(cls) -> 'ProxyConfig':
|
|
31
|
+
"""Create proxy configuration from environment variables."""
|
|
32
|
+
return cls(
|
|
33
|
+
http_proxy=os.getenv('HTTP_PROXY') or os.getenv('http_proxy'),
|
|
34
|
+
https_proxy=os.getenv('HTTPS_PROXY') or os.getenv('https_proxy'),
|
|
35
|
+
no_proxy=os.getenv('NO_PROXY') or os.getenv('no_proxy'),
|
|
36
|
+
auth=ProxyAuth(
|
|
37
|
+
username=os.getenv('PROXY_USERNAME', ''),
|
|
38
|
+
password=os.getenv('PROXY_PASSWORD', '')
|
|
39
|
+
) if os.getenv('PROXY_USERNAME') else None
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_url(cls, proxy_url: str, auth: Optional[ProxyAuth] = None) -> 'ProxyConfig':
|
|
44
|
+
"""Create proxy configuration from a single URL."""
|
|
45
|
+
return cls(
|
|
46
|
+
http_proxy=proxy_url,
|
|
47
|
+
https_proxy=proxy_url,
|
|
48
|
+
auth=auth
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def to_httpx_proxies(self) -> Optional[Dict[str, str]]:
|
|
52
|
+
"""Convert to httpx proxies format."""
|
|
53
|
+
if not self.http_proxy and not self.https_proxy:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
proxies = {}
|
|
57
|
+
|
|
58
|
+
if self.http_proxy:
|
|
59
|
+
proxies['http://'] = self._add_auth_to_url(self.http_proxy)
|
|
60
|
+
|
|
61
|
+
if self.https_proxy:
|
|
62
|
+
proxies['https://'] = self._add_auth_to_url(self.https_proxy)
|
|
63
|
+
|
|
64
|
+
return proxies if proxies else None
|
|
65
|
+
|
|
66
|
+
def to_openai_proxies(self) -> Optional[Dict[str, str]]:
|
|
67
|
+
"""Convert to OpenAI client proxies format."""
|
|
68
|
+
# OpenAI client supports httpx-style proxy configuration
|
|
69
|
+
return self.to_httpx_proxies()
|
|
70
|
+
|
|
71
|
+
def _add_auth_to_url(self, url: str) -> str:
|
|
72
|
+
"""Add authentication to proxy URL if configured."""
|
|
73
|
+
if not self.auth or not self.auth.username:
|
|
74
|
+
return url
|
|
75
|
+
|
|
76
|
+
parsed = urlparse(url)
|
|
77
|
+
|
|
78
|
+
# If URL already has auth, don't override
|
|
79
|
+
if '@' in parsed.netloc:
|
|
80
|
+
return url
|
|
81
|
+
|
|
82
|
+
auth_string = f"{self.auth.username}:{self.auth.password}"
|
|
83
|
+
|
|
84
|
+
# Reconstruct URL with auth
|
|
85
|
+
if parsed.port:
|
|
86
|
+
netloc = f"{auth_string}@{parsed.hostname}:{parsed.port}"
|
|
87
|
+
else:
|
|
88
|
+
netloc = f"{auth_string}@{parsed.hostname}"
|
|
89
|
+
|
|
90
|
+
return f"{parsed.scheme}://{netloc}{parsed.path}"
|
|
91
|
+
|
|
92
|
+
def should_bypass_proxy(self, host: str) -> bool:
|
|
93
|
+
"""Check if a host should bypass the proxy based on no_proxy settings."""
|
|
94
|
+
if not self.no_proxy:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
no_proxy_hosts = [h.strip() for h in self.no_proxy.split(',')]
|
|
98
|
+
|
|
99
|
+
for no_proxy_host in no_proxy_hosts:
|
|
100
|
+
if not no_proxy_host:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# Exact match
|
|
104
|
+
if host == no_proxy_host:
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
# Wildcard match (e.g., *.example.com)
|
|
108
|
+
if no_proxy_host.startswith('*'):
|
|
109
|
+
suffix = no_proxy_host[1:]
|
|
110
|
+
if host.endswith(suffix):
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
# Domain suffix match
|
|
114
|
+
if no_proxy_host.startswith('.'):
|
|
115
|
+
if host.endswith(no_proxy_host) or host == no_proxy_host[1:]:
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def create_proxy_config(
|
|
122
|
+
proxy_url: Optional[str] = None,
|
|
123
|
+
username: Optional[str] = None,
|
|
124
|
+
password: Optional[str] = None,
|
|
125
|
+
no_proxy: Optional[str] = None
|
|
126
|
+
) -> ProxyConfig:
|
|
127
|
+
"""Create a proxy configuration with optional parameters."""
|
|
128
|
+
auth = ProxyAuth(username, password) if username else None
|
|
129
|
+
|
|
130
|
+
if proxy_url:
|
|
131
|
+
config = ProxyConfig.from_url(proxy_url, auth)
|
|
132
|
+
if no_proxy:
|
|
133
|
+
config.no_proxy = no_proxy
|
|
134
|
+
return config
|
|
135
|
+
|
|
136
|
+
return ProxyConfig.from_environment()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_default_proxy_config() -> ProxyConfig:
|
|
140
|
+
"""Get the default proxy configuration from environment variables."""
|
|
141
|
+
return ProxyConfig.from_environment()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper functions for creating JAF core components with proxy support.
|
|
3
|
+
|
|
4
|
+
This module provides convenience functions to easily create model providers
|
|
5
|
+
with proxy configuration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional, Dict, Any
|
|
9
|
+
from .proxy import ProxyConfig, get_default_proxy_config
|
|
10
|
+
from ..providers.model import make_litellm_provider
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def make_proxy_enabled_litellm_provider(
|
|
14
|
+
base_url: str,
|
|
15
|
+
api_key: str = "anything",
|
|
16
|
+
default_timeout: Optional[float] = None,
|
|
17
|
+
proxy_url: Optional[str] = None,
|
|
18
|
+
proxy_username: Optional[str] = None,
|
|
19
|
+
proxy_password: Optional[str] = None,
|
|
20
|
+
use_env_proxy: bool = True
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Create a LiteLLM provider with proxy support.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
base_url: Base URL for the LiteLLM server
|
|
27
|
+
api_key: API key (defaults to "anything" for local servers)
|
|
28
|
+
default_timeout: Default timeout for model API calls in seconds
|
|
29
|
+
proxy_url: Proxy URL (if not using environment variables)
|
|
30
|
+
proxy_username: Proxy username for authentication
|
|
31
|
+
proxy_password: Proxy password for authentication
|
|
32
|
+
use_env_proxy: Whether to use proxy settings from environment variables
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
ModelProvider with proxy configuration
|
|
36
|
+
"""
|
|
37
|
+
proxy_config = None
|
|
38
|
+
|
|
39
|
+
if proxy_url:
|
|
40
|
+
# Use explicitly provided proxy settings
|
|
41
|
+
from .proxy import ProxyAuth, create_proxy_config
|
|
42
|
+
proxy_config = create_proxy_config(
|
|
43
|
+
proxy_url=proxy_url,
|
|
44
|
+
username=proxy_username,
|
|
45
|
+
password=proxy_password
|
|
46
|
+
)
|
|
47
|
+
elif use_env_proxy:
|
|
48
|
+
# Use environment-based proxy settings
|
|
49
|
+
proxy_config = get_default_proxy_config()
|
|
50
|
+
# Only use proxy if actually configured in environment
|
|
51
|
+
if not proxy_config.http_proxy and not proxy_config.https_proxy:
|
|
52
|
+
proxy_config = None
|
|
53
|
+
|
|
54
|
+
return make_litellm_provider(
|
|
55
|
+
base_url=base_url,
|
|
56
|
+
api_key=api_key,
|
|
57
|
+
default_timeout=default_timeout,
|
|
58
|
+
proxy_config=proxy_config
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_proxy_info() -> Dict[str, Any]:
|
|
65
|
+
"""
|
|
66
|
+
Get information about current proxy configuration from environment.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Dictionary with proxy configuration details
|
|
70
|
+
"""
|
|
71
|
+
proxy_config = get_default_proxy_config()
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"http_proxy": proxy_config.http_proxy,
|
|
75
|
+
"https_proxy": proxy_config.https_proxy,
|
|
76
|
+
"no_proxy": proxy_config.no_proxy,
|
|
77
|
+
"has_auth": proxy_config.auth is not None,
|
|
78
|
+
"auth_username": proxy_config.auth.username if proxy_config.auth else None,
|
|
79
|
+
"is_configured": bool(proxy_config.http_proxy or proxy_config.https_proxy)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def validate_proxy_config(proxy_config: ProxyConfig) -> Dict[str, Any]:
|
|
84
|
+
"""
|
|
85
|
+
Validate proxy configuration and return validation results.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
proxy_config: Proxy configuration to validate
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Dictionary with validation results
|
|
92
|
+
"""
|
|
93
|
+
results = {
|
|
94
|
+
"valid": True,
|
|
95
|
+
"warnings": [],
|
|
96
|
+
"errors": []
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Check if at least one proxy is configured
|
|
100
|
+
if not proxy_config.http_proxy and not proxy_config.https_proxy:
|
|
101
|
+
results["warnings"].append("No proxy URLs configured")
|
|
102
|
+
|
|
103
|
+
# Validate proxy URLs if configured
|
|
104
|
+
for proxy_type, proxy_url in [("HTTP", proxy_config.http_proxy), ("HTTPS", proxy_config.https_proxy)]:
|
|
105
|
+
if proxy_url:
|
|
106
|
+
try:
|
|
107
|
+
from urllib.parse import urlparse
|
|
108
|
+
parsed = urlparse(proxy_url)
|
|
109
|
+
if not parsed.scheme:
|
|
110
|
+
results["errors"].append(f"{proxy_type} proxy URL missing scheme: {proxy_url}")
|
|
111
|
+
results["valid"] = False
|
|
112
|
+
if not parsed.netloc:
|
|
113
|
+
results["errors"].append(f"{proxy_type} proxy URL missing host: {proxy_url}")
|
|
114
|
+
results["valid"] = False
|
|
115
|
+
except Exception as e:
|
|
116
|
+
results["errors"].append(f"Invalid {proxy_type} proxy URL: {e}")
|
|
117
|
+
results["valid"] = False
|
|
118
|
+
|
|
119
|
+
# Check authentication consistency
|
|
120
|
+
if proxy_config.auth:
|
|
121
|
+
if not proxy_config.auth.username:
|
|
122
|
+
results["warnings"].append("Proxy authentication configured but username is empty")
|
|
123
|
+
if not proxy_config.auth.password:
|
|
124
|
+
results["warnings"].append("Proxy authentication configured but password is empty")
|
|
125
|
+
|
|
126
|
+
return results
|
|
@@ -6,18 +6,21 @@ starting with LiteLLM for multi-provider support.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from typing import Any, Dict, Optional, TypeVar
|
|
9
|
+
import httpx
|
|
9
10
|
|
|
10
11
|
from openai import OpenAI
|
|
11
12
|
from pydantic import BaseModel
|
|
12
13
|
|
|
13
14
|
from ..core.types import Agent, ContentRole, Message, ModelProvider, RunConfig, RunState
|
|
15
|
+
from ..core.proxy import ProxyConfig
|
|
14
16
|
|
|
15
17
|
Ctx = TypeVar('Ctx')
|
|
16
18
|
|
|
17
19
|
def make_litellm_provider(
|
|
18
20
|
base_url: str,
|
|
19
21
|
api_key: str = "anything",
|
|
20
|
-
default_timeout: Optional[float] = None
|
|
22
|
+
default_timeout: Optional[float] = None,
|
|
23
|
+
proxy_config: Optional[ProxyConfig] = None
|
|
21
24
|
) -> ModelProvider[Ctx]:
|
|
22
25
|
"""
|
|
23
26
|
Create a LiteLLM-compatible model provider.
|
|
@@ -26,6 +29,7 @@ def make_litellm_provider(
|
|
|
26
29
|
base_url: Base URL for the LiteLLM server
|
|
27
30
|
api_key: API key (defaults to "anything" for local servers)
|
|
28
31
|
default_timeout: Default timeout for model API calls in seconds
|
|
32
|
+
proxy_config: Optional proxy configuration
|
|
29
33
|
|
|
30
34
|
Returns:
|
|
31
35
|
ModelProvider instance
|
|
@@ -35,11 +39,25 @@ def make_litellm_provider(
|
|
|
35
39
|
def __init__(self):
|
|
36
40
|
# Default to "anything" if api_key is not provided, for local servers
|
|
37
41
|
effective_api_key = api_key if api_key is not None else "anything"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
# Configure HTTP client with proxy support
|
|
44
|
+
client_kwargs = {
|
|
45
|
+
"base_url": base_url,
|
|
46
|
+
"api_key": effective_api_key,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if proxy_config:
|
|
50
|
+
proxies = proxy_config.to_httpx_proxies()
|
|
51
|
+
if proxies:
|
|
52
|
+
# Create httpx client with proxy configuration
|
|
53
|
+
try:
|
|
54
|
+
http_client = httpx.Client(proxies=proxies)
|
|
55
|
+
client_kwargs["http_client"] = http_client
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"Warning: Could not configure proxy: {e}")
|
|
58
|
+
# Fall back to environment variables for proxy
|
|
59
|
+
|
|
60
|
+
self.client = OpenAI(**client_kwargs)
|
|
43
61
|
self.default_timeout = default_timeout
|
|
44
62
|
|
|
45
63
|
async def get_completion(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jaf-py
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.3
|
|
4
4
|
Summary: A purely functional agent framework with immutable state and composable tools - Python implementation
|
|
5
5
|
Author: JAF Contributors
|
|
6
6
|
Maintainer: JAF Contributors
|
|
@@ -110,6 +110,7 @@ A purely functional agent framework with immutable state and composable tools, p
|
|
|
110
110
|
- ✅ **Output Guardrails**: Response sanitization
|
|
111
111
|
- ✅ **Permission System**: Role-based access control
|
|
112
112
|
- ✅ **Audit Logging**: Complete interaction tracing
|
|
113
|
+
- ✅ **Proxy Support**: Corporate proxy integration with authentication
|
|
113
114
|
|
|
114
115
|
### 📊 **Observability & Monitoring**
|
|
115
116
|
- ✅ **Real-time Tracing**: Event-driven observability
|
|
@@ -49,6 +49,8 @@ jaf/core/composition.py
|
|
|
49
49
|
jaf/core/engine.py
|
|
50
50
|
jaf/core/errors.py
|
|
51
51
|
jaf/core/performance.py
|
|
52
|
+
jaf/core/proxy.py
|
|
53
|
+
jaf/core/proxy_helpers.py
|
|
52
54
|
jaf/core/streaming.py
|
|
53
55
|
jaf/core/tool_results.py
|
|
54
56
|
jaf/core/tools.py
|
|
@@ -107,6 +109,7 @@ tests/test_mcp_transports.py
|
|
|
107
109
|
tests/test_memory_system_examples.py
|
|
108
110
|
tests/test_model_providers_examples.py
|
|
109
111
|
tests/test_property_based.py
|
|
112
|
+
tests/test_proxy_simple.py
|
|
110
113
|
tests/test_redis_fixes.py
|
|
111
114
|
tests/test_redis_memory.py
|
|
112
115
|
tests/test_server_api_examples.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jaf-py"
|
|
7
|
-
version = "2.2.
|
|
7
|
+
version = "2.2.3"
|
|
8
8
|
description = "A purely functional agent framework with immutable state and composable tools - Python implementation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simple proxy configuration test for JAF.
|
|
3
|
+
|
|
4
|
+
This script tests basic proxy configuration without making actual LLM calls.
|
|
5
|
+
Use this to verify proxy settings are loaded correctly.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from dotenv import load_dotenv
|
|
11
|
+
|
|
12
|
+
# Add the jaf package to the path
|
|
13
|
+
sys.path.insert(0, '/Users/yash.gupta/check/jaf-py')
|
|
14
|
+
|
|
15
|
+
from jaf.core import ProxyConfig, ProxyAuth, get_default_proxy_config
|
|
16
|
+
from jaf.core.proxy_helpers import get_proxy_info, validate_proxy_config
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
"""Test proxy configuration loading and validation."""
|
|
21
|
+
print("=== JAF Proxy Configuration Test ===\n")
|
|
22
|
+
|
|
23
|
+
# Load .env if it exists
|
|
24
|
+
if os.path.exists('.env'):
|
|
25
|
+
load_dotenv('.env')
|
|
26
|
+
print("✓ Loaded .env file\n")
|
|
27
|
+
else:
|
|
28
|
+
print("ℹ No .env file found, using system environment\n")
|
|
29
|
+
|
|
30
|
+
# Test 1: Get proxy info
|
|
31
|
+
print("1. Current proxy configuration:")
|
|
32
|
+
proxy_info = get_proxy_info()
|
|
33
|
+
for key, value in proxy_info.items():
|
|
34
|
+
print(f" {key}: {value}")
|
|
35
|
+
print()
|
|
36
|
+
|
|
37
|
+
# Test 2: Get default proxy config
|
|
38
|
+
print("2. Default proxy config from environment:")
|
|
39
|
+
proxy_config = get_default_proxy_config()
|
|
40
|
+
print(f" HTTP Proxy: {proxy_config.http_proxy}")
|
|
41
|
+
print(f" HTTPS Proxy: {proxy_config.https_proxy}")
|
|
42
|
+
print(f" No Proxy: {proxy_config.no_proxy}")
|
|
43
|
+
print(f" Has Auth: {proxy_config.auth is not None}")
|
|
44
|
+
if proxy_config.auth:
|
|
45
|
+
print(f" Auth Username: {proxy_config.auth.username}")
|
|
46
|
+
print(f" Auth Password: {'*' * len(proxy_config.auth.password) if proxy_config.auth.password else 'None'}")
|
|
47
|
+
print()
|
|
48
|
+
|
|
49
|
+
# Test 3: Validate proxy config
|
|
50
|
+
print("3. Proxy configuration validation:")
|
|
51
|
+
validation = validate_proxy_config(proxy_config)
|
|
52
|
+
print(f" Valid: {validation['valid']}")
|
|
53
|
+
if validation['warnings']:
|
|
54
|
+
print(" Warnings:")
|
|
55
|
+
for warning in validation['warnings']:
|
|
56
|
+
print(f" - {warning}")
|
|
57
|
+
if validation['errors']:
|
|
58
|
+
print(" Errors:")
|
|
59
|
+
for error in validation['errors']:
|
|
60
|
+
print(f" - {error}")
|
|
61
|
+
print()
|
|
62
|
+
|
|
63
|
+
# Test 4: HTTP client format conversion
|
|
64
|
+
print("4. HTTP client format conversion:")
|
|
65
|
+
if proxy_config.http_proxy or proxy_config.https_proxy:
|
|
66
|
+
httpx_proxies = proxy_config.to_httpx_proxies()
|
|
67
|
+
openai_proxies = proxy_config.to_openai_proxies()
|
|
68
|
+
print(f" HTTPX format: {httpx_proxies}")
|
|
69
|
+
print(f" OpenAI format: {openai_proxies}")
|
|
70
|
+
else:
|
|
71
|
+
print(" No proxy configured for conversion")
|
|
72
|
+
print()
|
|
73
|
+
|
|
74
|
+
# Test 5: Proxy bypass logic
|
|
75
|
+
print("5. Proxy bypass test:")
|
|
76
|
+
test_hosts = ["localhost", "127.0.0.1", "api.openai.com", "internal.local"]
|
|
77
|
+
for host in test_hosts:
|
|
78
|
+
bypass = proxy_config.should_bypass_proxy(host)
|
|
79
|
+
status = "BYPASS" if bypass else "USE PROXY"
|
|
80
|
+
print(f" {host}: {status}")
|
|
81
|
+
print()
|
|
82
|
+
|
|
83
|
+
# Test 6: Manual proxy creation
|
|
84
|
+
print("6. Manual proxy configuration test:")
|
|
85
|
+
try:
|
|
86
|
+
manual_proxy = ProxyConfig(
|
|
87
|
+
http_proxy="http://test-proxy.example.com:8080",
|
|
88
|
+
https_proxy="http://test-proxy.example.com:8080",
|
|
89
|
+
no_proxy="localhost,*.local",
|
|
90
|
+
auth=ProxyAuth(username="testuser", password="testpass")
|
|
91
|
+
)
|
|
92
|
+
manual_validation = validate_proxy_config(manual_proxy)
|
|
93
|
+
print(f" Manual config valid: {manual_validation['valid']}")
|
|
94
|
+
print(f" Manual config HTTPX: {manual_proxy.to_httpx_proxies()}")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print(f" Error creating manual config: {e}")
|
|
97
|
+
print()
|
|
98
|
+
|
|
99
|
+
print("=== Configuration Summary ===")
|
|
100
|
+
if proxy_info['is_configured']:
|
|
101
|
+
print("✓ Proxy is configured and ready to use")
|
|
102
|
+
print(f" HTTP: {proxy_info['http_proxy']}")
|
|
103
|
+
print(f" HTTPS: {proxy_info['https_proxy']}")
|
|
104
|
+
print(f" Auth: {'Yes' if proxy_info['has_auth'] else 'No'}")
|
|
105
|
+
else:
|
|
106
|
+
print("ℹ No proxy configured (will use direct connections)")
|
|
107
|
+
|
|
108
|
+
print("\nTo configure proxy, set environment variables or create .env file:")
|
|
109
|
+
print(" HTTP_PROXY=http://proxy.example.com:8080")
|
|
110
|
+
print(" HTTPS_PROXY=http://proxy.example.com:8080")
|
|
111
|
+
print(" PROXY_USERNAME=username (optional)")
|
|
112
|
+
print(" PROXY_PASSWORD=password (optional)")
|
|
113
|
+
print(" NO_PROXY=localhost,127.0.0.1,*.local (optional)")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
main()
|
|
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
|
|
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
|
|
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
|
|
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
|