jaf-py 2.2.2__py3-none-any.whl → 2.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
jaf/core/__init__.py CHANGED
@@ -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",
jaf/core/engine.py CHANGED
@@ -631,7 +631,7 @@ async def _execute_tool_calls(
631
631
  result=result_string,
632
632
  trace_id=state.trace_id,
633
633
  run_id=state.run_id,
634
- tool_result=tool_result_obj,
634
+ tool_result=tool_result,
635
635
  status='success'
636
636
  ))))
637
637
 
jaf/core/proxy.py ADDED
@@ -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
jaf/providers/model.py CHANGED
@@ -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
- self.client = OpenAI(
39
- base_url=base_url,
40
- api_key=effective_api_key,
41
- # Note: dangerouslyAllowBrowser is JavaScript-specific
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.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
@@ -38,13 +38,15 @@ jaf/a2a/tests/test_client.py,sha256=L5h7DtQRVlULiRhRLtrmaCoYdvmbXsgLTy3QQ6KgmNM,
38
38
  jaf/a2a/tests/test_integration.py,sha256=I7LdgwN99mAOljM9kYtK7dGMMntTSWKMw_oLOcJjinU,18454
39
39
  jaf/a2a/tests/test_protocol.py,sha256=He3vGlBfIazpppAnuSybutrvjIN3VGxEleAohrVd9hc,23287
40
40
  jaf/a2a/tests/test_types.py,sha256=PgRjDVJrHSXuu05z0B5lsSUUY5qEdQLFJbLBIExyVgI,18384
41
- jaf/core/__init__.py,sha256=jpeow6G3SpDtYaY4hdEoTQ2ZgPUSUZTDR_AFwrs2cRE,1155
41
+ jaf/core/__init__.py,sha256=rBvP_7TGbJICDJnA7a3qyX8yQErCDWaGAn5WzpyH4gU,1339
42
42
  jaf/core/agent_tool.py,sha256=8TcBuSxGmDTW5F_GhBU_m5S43nYqkjO4qTrNERraAig,11656
43
43
  jaf/core/analytics.py,sha256=NrUfOLLTDIhOzdfc65ZqS9AJ4ZAP9BtNtga69q0YdYw,23265
44
44
  jaf/core/composition.py,sha256=IVxRO1Q9nK7JRH32qQ4p8WMIUu66BhqPNrlTNMGFVwE,26317
45
- jaf/core/engine.py,sha256=KqY87WFpmfU-mPzTzLXnlncyq2xKDSYONJiz0tYJZfI,26861
45
+ jaf/core/engine.py,sha256=wakHo9DcNSVH7N5FC_6vi4Nc0yO15YJpNvNTLyyGM_E,26857
46
46
  jaf/core/errors.py,sha256=5fwTNhkojKRQ4wZj3lZlgDnAsrYyjYOwXJkIr5EGNUc,5539
47
47
  jaf/core/performance.py,sha256=jedQmTEkrKMD6_Aw1h8PdG-5TsdYSFFT7Or6k5dmN2g,9974
48
+ jaf/core/proxy.py,sha256=_WM3cpRlSQLYpgSBrnY30UPMe2iZtlqDQ65kppE-WY0,4609
49
+ jaf/core/proxy_helpers.py,sha256=i7a5fAX9rLmO4FMBX51-yRkTFwfWedzQNgnLmeLUd_A,4370
48
50
  jaf/core/streaming.py,sha256=c5o9iqpjoYV2LrUpG6qLWCYrWcP-DCcZsvMbyqKunp8,16089
49
51
  jaf/core/tool_results.py,sha256=-bTOqOX02lMyslp5Z4Dmuhx0cLd5o7kgR88qK2HO_sw,11323
50
52
  jaf/core/tools.py,sha256=SbJRRr4y_xxNYNTulZg6OiyNaHBlo_qXWYY510jxQEs,16489
@@ -66,7 +68,7 @@ jaf/policies/handoff.py,sha256=KJYYuL9T6v6DECRhnsS2Je6q4Aj9_zC5d_KBnvEnZNE,8318
66
68
  jaf/policies/validation.py,sha256=wn-7ynH10E5nk-_r1_kHIYHrBGmLX0EFr-FUTHrsxvc,10903
67
69
  jaf/providers/__init__.py,sha256=j_o-Rubr8d9tNYlFWb6fvzkxIBl3JKK_iabj9wTFia0,2114
68
70
  jaf/providers/mcp.py,sha256=WxcC8gUFpDBBYyhorMcc1jHq3xMDMBtnwyRPthfL0S0,13074
69
- jaf/providers/model.py,sha256=hKDLLE4sMqCiQT03VcNSgsvMp3phMvFQewRDDsP-qWw,6781
71
+ jaf/providers/model.py,sha256=9kCisPgppBeVKXS7JtXeOyA5791Tdh6z0vcJbENAQNs,7535
70
72
  jaf/server/__init__.py,sha256=K0vSgTfzn3oM54UX9BeAROpaksYY43mFtfjSXoQrhXA,339
71
73
  jaf/server/main.py,sha256=CTb0ywbPIq9ELfay5MKChVR7BpIQOoEbPjPfpzo2aBQ,2152
72
74
  jaf/server/server.py,sha256=o-n6dPWmlu4_v5ozVW3BTOm1b5kHBQhqRYlHh5sXL-k,8048
@@ -77,9 +79,9 @@ jaf/visualization/functional_core.py,sha256=zedMDZbvjuOugWwnh6SJ2stvRNQX1Hlkb9Ab
77
79
  jaf/visualization/graphviz.py,sha256=WTOM6UP72-lVKwI4_SAr5-GCC3ouckxHv88ypCDQWJ0,12056
78
80
  jaf/visualization/imperative_shell.py,sha256=GpMrAlMnLo2IQgyB2nardCz09vMvAzaYI46MyrvJ0i4,2593
79
81
  jaf/visualization/types.py,sha256=QQcbVeQJLuAOXk8ynd08DXIS-PVCnv3R-XVE9iAcglw,1389
80
- jaf_py-2.2.2.dist-info/licenses/LICENSE,sha256=LXUQBJxdyr-7C4bk9cQBwvsF_xwA-UVstDTKabpcjlI,1063
81
- jaf_py-2.2.2.dist-info/METADATA,sha256=08sGQ4y4S6KFtf5_ay-VnraZ14z5lR4qIqqBkT_xMNw,23077
82
- jaf_py-2.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
83
- jaf_py-2.2.2.dist-info/entry_points.txt,sha256=OtIJeNJpb24kgGrqRx9szGgDx1vL9ayq8uHErmu7U5w,41
84
- jaf_py-2.2.2.dist-info/top_level.txt,sha256=Xu1RZbGaM4_yQX7bpalo881hg7N_dybaOW282F15ruE,4
85
- jaf_py-2.2.2.dist-info/RECORD,,
82
+ jaf_py-2.2.3.dist-info/licenses/LICENSE,sha256=LXUQBJxdyr-7C4bk9cQBwvsF_xwA-UVstDTKabpcjlI,1063
83
+ jaf_py-2.2.3.dist-info/METADATA,sha256=IqS2qTIVxsXrYE0GLCKRRVv5H4MjwBCbg0UgWuqsZHE,23150
84
+ jaf_py-2.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
85
+ jaf_py-2.2.3.dist-info/entry_points.txt,sha256=OtIJeNJpb24kgGrqRx9szGgDx1vL9ayq8uHErmu7U5w,41
86
+ jaf_py-2.2.3.dist-info/top_level.txt,sha256=Xu1RZbGaM4_yQX7bpalo881hg7N_dybaOW282F15ruE,4
87
+ jaf_py-2.2.3.dist-info/RECORD,,
File without changes