jaf-py 2.5.10__py3-none-any.whl → 2.5.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. jaf/__init__.py +154 -57
  2. jaf/a2a/__init__.py +42 -21
  3. jaf/a2a/agent.py +79 -126
  4. jaf/a2a/agent_card.py +87 -78
  5. jaf/a2a/client.py +30 -66
  6. jaf/a2a/examples/client_example.py +12 -12
  7. jaf/a2a/examples/integration_example.py +38 -47
  8. jaf/a2a/examples/server_example.py +56 -53
  9. jaf/a2a/memory/__init__.py +0 -4
  10. jaf/a2a/memory/cleanup.py +28 -21
  11. jaf/a2a/memory/factory.py +155 -133
  12. jaf/a2a/memory/providers/composite.py +21 -26
  13. jaf/a2a/memory/providers/in_memory.py +89 -83
  14. jaf/a2a/memory/providers/postgres.py +117 -115
  15. jaf/a2a/memory/providers/redis.py +128 -121
  16. jaf/a2a/memory/serialization.py +77 -87
  17. jaf/a2a/memory/tests/run_comprehensive_tests.py +112 -83
  18. jaf/a2a/memory/tests/test_cleanup.py +211 -94
  19. jaf/a2a/memory/tests/test_serialization.py +73 -68
  20. jaf/a2a/memory/tests/test_stress_concurrency.py +186 -133
  21. jaf/a2a/memory/tests/test_task_lifecycle.py +138 -120
  22. jaf/a2a/memory/types.py +91 -53
  23. jaf/a2a/protocol.py +95 -125
  24. jaf/a2a/server.py +90 -118
  25. jaf/a2a/standalone_client.py +30 -43
  26. jaf/a2a/tests/__init__.py +16 -33
  27. jaf/a2a/tests/run_tests.py +17 -53
  28. jaf/a2a/tests/test_agent.py +40 -140
  29. jaf/a2a/tests/test_client.py +54 -117
  30. jaf/a2a/tests/test_integration.py +28 -82
  31. jaf/a2a/tests/test_protocol.py +54 -139
  32. jaf/a2a/tests/test_types.py +50 -136
  33. jaf/a2a/types.py +58 -34
  34. jaf/cli.py +21 -41
  35. jaf/core/__init__.py +7 -1
  36. jaf/core/agent_tool.py +93 -72
  37. jaf/core/analytics.py +257 -207
  38. jaf/core/checkpoint.py +223 -0
  39. jaf/core/composition.py +249 -235
  40. jaf/core/engine.py +817 -519
  41. jaf/core/errors.py +55 -42
  42. jaf/core/guardrails.py +276 -202
  43. jaf/core/handoff.py +47 -31
  44. jaf/core/parallel_agents.py +69 -75
  45. jaf/core/performance.py +75 -73
  46. jaf/core/proxy.py +43 -44
  47. jaf/core/proxy_helpers.py +24 -27
  48. jaf/core/regeneration.py +220 -129
  49. jaf/core/state.py +68 -66
  50. jaf/core/streaming.py +115 -108
  51. jaf/core/tool_results.py +111 -101
  52. jaf/core/tools.py +114 -116
  53. jaf/core/tracing.py +310 -210
  54. jaf/core/types.py +403 -151
  55. jaf/core/workflows.py +209 -168
  56. jaf/exceptions.py +46 -38
  57. jaf/memory/__init__.py +1 -6
  58. jaf/memory/approval_storage.py +54 -77
  59. jaf/memory/factory.py +4 -4
  60. jaf/memory/providers/in_memory.py +216 -180
  61. jaf/memory/providers/postgres.py +216 -146
  62. jaf/memory/providers/redis.py +173 -116
  63. jaf/memory/types.py +70 -51
  64. jaf/memory/utils.py +36 -34
  65. jaf/plugins/__init__.py +12 -12
  66. jaf/plugins/base.py +105 -96
  67. jaf/policies/__init__.py +0 -1
  68. jaf/policies/handoff.py +37 -46
  69. jaf/policies/validation.py +76 -52
  70. jaf/providers/__init__.py +6 -3
  71. jaf/providers/mcp.py +97 -51
  72. jaf/providers/model.py +475 -283
  73. jaf/server/__init__.py +1 -1
  74. jaf/server/main.py +7 -11
  75. jaf/server/server.py +514 -359
  76. jaf/server/types.py +208 -52
  77. jaf/utils/__init__.py +17 -18
  78. jaf/utils/attachments.py +111 -116
  79. jaf/utils/document_processor.py +175 -174
  80. jaf/visualization/__init__.py +1 -1
  81. jaf/visualization/example.py +111 -110
  82. jaf/visualization/functional_core.py +46 -71
  83. jaf/visualization/graphviz.py +154 -189
  84. jaf/visualization/imperative_shell.py +7 -16
  85. jaf/visualization/types.py +8 -4
  86. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/METADATA +2 -2
  87. jaf_py-2.5.12.dist-info/RECORD +97 -0
  88. jaf_py-2.5.10.dist-info/RECORD +0 -96
  89. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/WHEEL +0 -0
  90. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/entry_points.txt +0 -0
  91. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/licenses/LICENSE +0 -0
  92. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/top_level.txt +0 -0
jaf/a2a/agent_card.py CHANGED
@@ -10,9 +10,7 @@ from .types import A2AAgent
10
10
 
11
11
 
12
12
  def generate_agent_card(
13
- config: Dict[str, Any],
14
- agents: Dict[str, A2AAgent],
15
- base_url: str = "http://localhost:3000"
13
+ config: Dict[str, Any], agents: Dict[str, A2AAgent], base_url: str = "http://localhost:3000"
16
14
  ) -> Dict[str, Any]:
17
15
  """Pure function to generate Agent Card from A2A agents"""
18
16
  return {
@@ -26,13 +24,13 @@ def generate_agent_card(
26
24
  "capabilities": {
27
25
  "streaming": True,
28
26
  "pushNotifications": False,
29
- "stateTransitionHistory": True
27
+ "stateTransitionHistory": True,
30
28
  },
31
29
  "defaultInputModes": ["text/plain", "application/json"],
32
30
  "defaultOutputModes": ["text/plain", "application/json"],
33
31
  "skills": generate_skills_from_agents(agents),
34
32
  "securitySchemes": generate_security_schemes(),
35
- "security": generate_security_requirements()
33
+ "security": generate_security_requirements(),
36
34
  }
37
35
 
38
36
 
@@ -49,7 +47,7 @@ def generate_skills_from_agents(agents: Dict[str, A2AAgent]) -> List[Dict[str, A
49
47
  "tags": ["general"] + [tool.name for tool in agent.tools],
50
48
  "examples": generate_examples_for_agent(agent),
51
49
  "inputModes": agent.supported_content_types,
52
- "outputModes": agent.supported_content_types
50
+ "outputModes": agent.supported_content_types,
53
51
  }
54
52
 
55
53
  # Create individual skills for each tool
@@ -61,7 +59,7 @@ def generate_skills_from_agents(agents: Dict[str, A2AAgent]) -> List[Dict[str, A
61
59
  "tags": [tool.name, "tool", agent_name],
62
60
  "examples": generate_examples_for_tool(tool),
63
61
  "inputModes": ["text/plain", "application/json"],
64
- "outputModes": ["text/plain", "application/json"]
62
+ "outputModes": ["text/plain", "application/json"],
65
63
  }
66
64
  for tool in agent.tools
67
65
  ]
@@ -80,10 +78,7 @@ def generate_skills_from_agents(agents: Dict[str, A2AAgent]) -> List[Dict[str, A
80
78
 
81
79
  def generate_examples_for_agent(agent: A2AAgent) -> List[str]:
82
80
  """Pure function to generate examples for an agent"""
83
- base_examples = [
84
- f"Ask {agent.name} for help",
85
- f"What can {agent.name} do?"
86
- ]
81
+ base_examples = [f"Ask {agent.name} for help", f"What can {agent.name} do?"]
87
82
 
88
83
  # Add tool-specific examples using functional approach
89
84
  tool_examples = [
@@ -96,11 +91,7 @@ def generate_examples_for_agent(agent: A2AAgent) -> List[str]:
96
91
 
97
92
  def generate_examples_for_tool(tool: Any) -> List[str]:
98
93
  """Pure function to generate examples for a tool"""
99
- return [
100
- f"Use {tool.name}",
101
- tool.description,
102
- f"Help me with {tool.name.replace('_', ' ')}"
103
- ]
94
+ return [f"Use {tool.name}", tool.description, f"Help me with {tool.name.replace('_', ' ')}"]
104
95
 
105
96
 
106
97
  def generate_security_schemes() -> Dict[str, Any]:
@@ -109,14 +100,14 @@ def generate_security_schemes() -> Dict[str, Any]:
109
100
  "bearerAuth": {
110
101
  "type": "http",
111
102
  "scheme": "bearer",
112
- "description": "Bearer token authentication"
103
+ "description": "Bearer token authentication",
113
104
  },
114
105
  "apiKey": {
115
106
  "type": "apiKey",
116
107
  "in": "header",
117
108
  "name": "X-API-Key",
118
- "description": "API key authentication"
119
- }
109
+ "description": "API key authentication",
110
+ },
120
111
  }
121
112
 
122
113
 
@@ -128,7 +119,7 @@ def generate_security_requirements() -> List[Dict[str, List[str]]]:
128
119
  # Optional bearer auth
129
120
  {"bearerAuth": []},
130
121
  # Optional API key
131
- {"apiKey": []}
122
+ {"apiKey": []},
132
123
  ]
133
124
 
134
125
 
@@ -136,7 +127,7 @@ def generate_agent_card_for_agent(
136
127
  agent_name: str,
137
128
  agent: A2AAgent,
138
129
  config: Optional[Dict[str, Any]] = None,
139
- base_url: str = "http://localhost:3000"
130
+ base_url: str = "http://localhost:3000",
140
131
  ) -> Dict[str, Any]:
141
132
  """Pure function to generate Agent Card for a specific agent"""
142
133
  agent_map = {agent_name: agent}
@@ -145,10 +136,10 @@ def generate_agent_card_for_agent(
145
136
  "name": (config or {}).get("name", agent.name),
146
137
  "description": (config or {}).get("description", agent.description),
147
138
  "version": (config or {}).get("version", "1.0.0"),
148
- "provider": (config or {}).get("provider", {
149
- "organization": "JAF Agent",
150
- "url": "https://functional-agent-framework.com"
151
- })
139
+ "provider": (config or {}).get(
140
+ "provider",
141
+ {"organization": "JAF Agent", "url": "https://functional-agent-framework.com"},
142
+ ),
152
143
  }
153
144
 
154
145
  return generate_agent_card(card_config, agent_map, base_url)
@@ -159,55 +150,66 @@ def validate_agent_card(card: Dict[str, Any]) -> Dict[str, Any]:
159
150
 
160
151
  # Validate required fields functionally
161
152
  required_field_errors = [
162
- error for field, error_msg in [
153
+ error
154
+ for field, error_msg in [
163
155
  (card.get("name", "").strip(), "Agent card name is required"),
164
156
  (card.get("description", "").strip(), "Agent card description is required"),
165
157
  (card.get("url", "").strip(), "Agent card URL is required"),
166
158
  (card.get("version", "").strip(), "Agent card version is required"),
167
- (card.get("protocolVersion", "").strip(), "Protocol version is required")
168
- ] if not field
159
+ (card.get("protocolVersion", "").strip(), "Protocol version is required"),
160
+ ]
161
+ if not field
169
162
  for error in [error_msg]
170
163
  ]
171
164
 
172
165
  # Skills validation functionally
173
166
  skills = card.get("skills", [])
174
167
  skills_errors = (
175
- ["At least one skill is required"] if not skills else
176
- [
168
+ ["At least one skill is required"]
169
+ if not skills
170
+ else [
177
171
  error
178
172
  for index, skill in enumerate(skills)
179
173
  for error in [
180
174
  f"Skill {index}: ID is required" if not skill.get("id", "").strip() else None,
181
175
  f"Skill {index}: Name is required" if not skill.get("name", "").strip() else None,
182
- f"Skill {index}: Description is required" if not skill.get("description", "").strip() else None,
183
- f"Skill {index}: At least one tag is required" if not skill.get("tags") or len(skill.get("tags", [])) == 0 else None
184
- ] if error is not None
176
+ f"Skill {index}: Description is required"
177
+ if not skill.get("description", "").strip()
178
+ else None,
179
+ f"Skill {index}: At least one tag is required"
180
+ if not skill.get("tags") or len(skill.get("tags", [])) == 0
181
+ else None,
182
+ ]
183
+ if error is not None
185
184
  ]
186
185
  )
187
186
 
188
187
  # Input/Output modes validation functionally
189
188
  mode_errors = [
190
- error for condition, error in [
191
- (not card.get("defaultInputModes") or len(card.get("defaultInputModes", [])) == 0, "At least one default input mode is required"),
192
- (not card.get("defaultOutputModes") or len(card.get("defaultOutputModes", [])) == 0, "At least one default output mode is required")
193
- ] if condition
189
+ error
190
+ for condition, error in [
191
+ (
192
+ not card.get("defaultInputModes") or len(card.get("defaultInputModes", [])) == 0,
193
+ "At least one default input mode is required",
194
+ ),
195
+ (
196
+ not card.get("defaultOutputModes") or len(card.get("defaultOutputModes", [])) == 0,
197
+ "At least one default output mode is required",
198
+ ),
199
+ ]
200
+ if condition
194
201
  for error in [error]
195
202
  ]
196
203
 
197
204
  # URL validation functionally
198
205
  url_errors = [
199
- "Invalid URL format"
200
- for url in [card.get("url")]
201
- if url and not is_valid_url(url)
206
+ "Invalid URL format" for url in [card.get("url")] if url and not is_valid_url(url)
202
207
  ]
203
208
 
204
209
  # Combine all errors functionally
205
210
  errors = required_field_errors + skills_errors + mode_errors + url_errors
206
211
 
207
- return {
208
- "is_valid": len(errors) == 0,
209
- "errors": errors
210
- }
212
+ return {"is_valid": len(errors) == 0, "errors": errors}
211
213
 
212
214
 
213
215
  def is_valid_url(url: str) -> bool:
@@ -220,9 +222,7 @@ def is_valid_url(url: str) -> bool:
220
222
 
221
223
 
222
224
  def create_minimal_agent_card(
223
- name: str,
224
- description: str,
225
- url: str = "http://localhost:3000/a2a"
225
+ name: str, description: str, url: str = "http://localhost:3000/a2a"
226
226
  ) -> Dict[str, Any]:
227
227
  """Pure function to create minimal Agent Card"""
228
228
  return {
@@ -235,17 +235,19 @@ def create_minimal_agent_card(
235
235
  "capabilities": {
236
236
  "streaming": True,
237
237
  "pushNotifications": False,
238
- "stateTransitionHistory": False
238
+ "stateTransitionHistory": False,
239
239
  },
240
240
  "defaultInputModes": ["text/plain"],
241
241
  "defaultOutputModes": ["text/plain"],
242
- "skills": [{
243
- "id": "general",
244
- "name": "General Assistant",
245
- "description": "General purpose assistance",
246
- "tags": ["general", "assistant"],
247
- "examples": ["How can I help you?"]
248
- }]
242
+ "skills": [
243
+ {
244
+ "id": "general",
245
+ "name": "General Assistant",
246
+ "description": "General purpose assistance",
247
+ "tags": ["general", "assistant"],
248
+ "examples": ["How can I help you?"],
249
+ }
250
+ ],
249
251
  }
250
252
 
251
253
 
@@ -258,11 +260,7 @@ def merge_agent_cards(*cards: Dict[str, Any]) -> Dict[str, Any]:
258
260
  additional_cards = cards[1:]
259
261
 
260
262
  # Merge skills functionally
261
- all_skills = [
262
- skill
263
- for card in cards
264
- for skill in card.get("skills", [])
265
- ]
263
+ all_skills = [skill for card in cards for skill in card.get("skills", [])]
266
264
 
267
265
  # Remove duplicate skills by ID functionally
268
266
  seen_ids = set()
@@ -273,24 +271,35 @@ def merge_agent_cards(*cards: Dict[str, Any]) -> Dict[str, Any]:
273
271
  ]
274
272
 
275
273
  # Merge input/output modes
276
- merged_input_modes = list(set([
277
- *base_card.get("defaultInputModes", []),
278
- *[mode for card in additional_cards for mode in card.get("defaultInputModes", [])]
279
- ]))
274
+ merged_input_modes = list(
275
+ set(
276
+ [
277
+ *base_card.get("defaultInputModes", []),
278
+ *[mode for card in additional_cards for mode in card.get("defaultInputModes", [])],
279
+ ]
280
+ )
281
+ )
280
282
 
281
- merged_output_modes = list(set([
282
- *base_card.get("defaultOutputModes", []),
283
- *[mode for card in additional_cards for mode in card.get("defaultOutputModes", [])]
284
- ]))
283
+ merged_output_modes = list(
284
+ set(
285
+ [
286
+ *base_card.get("defaultOutputModes", []),
287
+ *[mode for card in additional_cards for mode in card.get("defaultOutputModes", [])],
288
+ ]
289
+ )
290
+ )
285
291
 
286
292
  # Merge capabilities
287
293
  capabilities = base_card.get("capabilities", {})
288
294
  for card in additional_cards:
289
295
  card_capabilities = card.get("capabilities", {})
290
296
  capabilities = {
291
- "streaming": capabilities.get("streaming", False) or card_capabilities.get("streaming", False),
292
- "pushNotifications": capabilities.get("pushNotifications", False) or card_capabilities.get("pushNotifications", False),
293
- "stateTransitionHistory": capabilities.get("stateTransitionHistory", False) or card_capabilities.get("stateTransitionHistory", False)
297
+ "streaming": capabilities.get("streaming", False)
298
+ or card_capabilities.get("streaming", False),
299
+ "pushNotifications": capabilities.get("pushNotifications", False)
300
+ or card_capabilities.get("pushNotifications", False),
301
+ "stateTransitionHistory": capabilities.get("stateTransitionHistory", False)
302
+ or card_capabilities.get("stateTransitionHistory", False),
294
303
  }
295
304
 
296
305
  return {
@@ -298,7 +307,7 @@ def merge_agent_cards(*cards: Dict[str, Any]) -> Dict[str, Any]:
298
307
  "skills": unique_skills,
299
308
  "defaultInputModes": merged_input_modes,
300
309
  "defaultOutputModes": merged_output_modes,
301
- "capabilities": capabilities
310
+ "capabilities": capabilities,
302
311
  }
303
312
 
304
313
 
@@ -313,17 +322,17 @@ def create_agent_card_from_config(config: Dict[str, Any]) -> Dict[str, Any]:
313
322
  "url": f"{base_url}/a2a",
314
323
  "preferredTransport": "JSONRPC",
315
324
  "version": config.get("version", "1.0.0"),
316
- "provider": config.get("provider", {
317
- "organization": "JAF Framework",
318
- "url": "https://functional-agent-framework.com"
319
- }),
325
+ "provider": config.get(
326
+ "provider",
327
+ {"organization": "JAF Framework", "url": "https://functional-agent-framework.com"},
328
+ ),
320
329
  "capabilities": {
321
330
  "streaming": True,
322
331
  "pushNotifications": False,
323
332
  "stateTransitionHistory": True,
324
- **config.get("capabilities", {})
333
+ **config.get("capabilities", {}),
325
334
  },
326
335
  "defaultInputModes": ["text/plain", "application/json"],
327
336
  "defaultOutputModes": ["text/plain", "application/json"],
328
- "skills": generate_skills_from_agents(config.get("agents", {}))
337
+ "skills": generate_skills_from_agents(config.get("agents", {})),
329
338
  }
jaf/a2a/client.py CHANGED
@@ -24,16 +24,14 @@ def create_a2a_client(base_url: str, config: Optional[Dict[str, Any]] = None) ->
24
24
  return A2AClientState(
25
25
  config=A2AClientConfig(
26
26
  baseUrl=base_url.rstrip("/"), # Remove trailing slash
27
- timeout=config.get("timeout", 30000)
27
+ timeout=config.get("timeout", 30000),
28
28
  ),
29
- sessionId=f"client_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}"
29
+ sessionId=f"client_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
30
30
  )
31
31
 
32
32
 
33
33
  def create_message_request(
34
- message: str,
35
- session_id: str,
36
- configuration: Optional[Dict[str, Any]] = None
34
+ message: str, session_id: str, configuration: Optional[Dict[str, Any]] = None
37
35
  ) -> Dict[str, Any]:
38
36
  """Pure function to create message request"""
39
37
  return {
@@ -46,17 +44,15 @@ def create_message_request(
46
44
  "parts": [{"kind": "text", "text": message}],
47
45
  "messageId": f"msg_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
48
46
  "contextId": session_id,
49
- "kind": "message"
47
+ "kind": "message",
50
48
  },
51
- "configuration": configuration
52
- }
49
+ "configuration": configuration,
50
+ },
53
51
  }
54
52
 
55
53
 
56
54
  def create_streaming_message_request(
57
- message: str,
58
- session_id: str,
59
- configuration: Optional[Dict[str, Any]] = None
55
+ message: str, session_id: str, configuration: Optional[Dict[str, Any]] = None
60
56
  ) -> Dict[str, Any]:
61
57
  """Pure function to create streaming message request"""
62
58
  return {
@@ -69,18 +65,14 @@ def create_streaming_message_request(
69
65
  "parts": [{"kind": "text", "text": message}],
70
66
  "messageId": f"msg_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
71
67
  "contextId": session_id,
72
- "kind": "message"
68
+ "kind": "message",
73
69
  },
74
- "configuration": configuration
75
- }
70
+ "configuration": configuration,
71
+ },
76
72
  }
77
73
 
78
74
 
79
- async def send_http_request(
80
- url: str,
81
- body: Dict[str, Any],
82
- timeout: int = 30000
83
- ) -> Dict[str, Any]:
75
+ async def send_http_request(url: str, body: Dict[str, Any], timeout: int = 30000) -> Dict[str, Any]:
84
76
  """Pure function to send HTTP request"""
85
77
  timeout_seconds = timeout / 1000.0
86
78
 
@@ -89,10 +81,7 @@ async def send_http_request(
89
81
  response = await client.post(
90
82
  url,
91
83
  json=body,
92
- headers={
93
- "Content-Type": "application/json",
94
- "Accept": "application/json"
95
- }
84
+ headers={"Content-Type": "application/json", "Accept": "application/json"},
96
85
  )
97
86
 
98
87
  if not response.is_success:
@@ -104,19 +93,14 @@ async def send_http_request(
104
93
  raise Exception(f"Request timeout after {timeout}ms")
105
94
 
106
95
 
107
- async def send_a2a_request(
108
- client: A2AClientState,
109
- request: Dict[str, Any]
110
- ) -> Dict[str, Any]:
96
+ async def send_a2a_request(client: A2AClientState, request: Dict[str, Any]) -> Dict[str, Any]:
111
97
  """Pure function to send A2A request"""
112
98
  url = f"{client.config.base_url}/a2a"
113
99
  return await send_http_request(url, request, client.config.timeout)
114
100
 
115
101
 
116
102
  async def send_message(
117
- client: A2AClientState,
118
- message: str,
119
- configuration: Optional[Dict[str, Any]] = None
103
+ client: A2AClientState, message: str, configuration: Optional[Dict[str, Any]] = None
120
104
  ) -> str:
121
105
  """Pure function to send message"""
122
106
  request = create_message_request(message, client.session_id, configuration)
@@ -130,9 +114,7 @@ async def send_message(
130
114
 
131
115
 
132
116
  async def stream_message(
133
- client: A2AClientState,
134
- message: str,
135
- configuration: Optional[Dict[str, Any]] = None
117
+ client: A2AClientState, message: str, configuration: Optional[Dict[str, Any]] = None
136
118
  ) -> AsyncGenerator[Dict[str, Any], None]:
137
119
  """Pure function to stream message"""
138
120
  request = create_streaming_message_request(message, client.session_id, configuration)
@@ -146,12 +128,8 @@ async def stream_message(
146
128
  "POST",
147
129
  url,
148
130
  json=request,
149
- headers={
150
- "Content-Type": "application/json",
151
- "Accept": "text/event-stream"
152
- }
131
+ headers={"Content-Type": "application/json", "Accept": "text/event-stream"},
153
132
  ) as response:
154
-
155
133
  if not response.is_success:
156
134
  raise Exception(f"HTTP {response.status_code}: {response.text}")
157
135
 
@@ -182,10 +160,7 @@ async def get_agent_card(client: A2AClientState) -> Dict[str, Any]:
182
160
  url = f"{client.config.base_url}/.well-known/agent-card"
183
161
 
184
162
  async with httpx.AsyncClient(timeout=client.config.timeout / 1000.0) as http_client:
185
- response = await http_client.get(
186
- url,
187
- headers={"Accept": "application/json"}
188
- )
163
+ response = await http_client.get(url, headers={"Accept": "application/json"})
189
164
 
190
165
  if not response.is_success:
191
166
  raise Exception(f"Failed to get agent card: HTTP {response.status_code}")
@@ -203,7 +178,7 @@ async def send_message_to_agent(
203
178
  client: A2AClientState,
204
179
  agent_name: str,
205
180
  message: str,
206
- configuration: Optional[Dict[str, Any]] = None
181
+ configuration: Optional[Dict[str, Any]] = None,
207
182
  ) -> str:
208
183
  """Pure function to send message to specific agent"""
209
184
  request = create_message_request(message, client.session_id, configuration)
@@ -222,7 +197,7 @@ async def stream_message_to_agent(
222
197
  client: A2AClientState,
223
198
  agent_name: str,
224
199
  message: str,
225
- configuration: Optional[Dict[str, Any]] = None
200
+ configuration: Optional[Dict[str, Any]] = None,
226
201
  ) -> AsyncGenerator[Dict[str, Any], None]:
227
202
  """Pure function to stream message to specific agent"""
228
203
  request = create_streaming_message_request(message, client.session_id, configuration)
@@ -236,12 +211,8 @@ async def stream_message_to_agent(
236
211
  "POST",
237
212
  url,
238
213
  json=request,
239
- headers={
240
- "Content-Type": "application/json",
241
- "Accept": "text/event-stream"
242
- }
214
+ headers={"Content-Type": "application/json", "Accept": "text/event-stream"},
243
215
  ) as response:
244
-
245
216
  if not response.is_success:
246
217
  raise Exception(f"HTTP {response.status_code}: {response.text}")
247
218
 
@@ -320,10 +291,7 @@ async def check_a2a_health(client: A2AClientState) -> Dict[str, Any]:
320
291
  url = f"{client.config.base_url}/a2a/health"
321
292
 
322
293
  async with httpx.AsyncClient(timeout=client.config.timeout / 1000.0) as http_client:
323
- response = await http_client.get(
324
- url,
325
- headers={"Accept": "application/json"}
326
- )
294
+ response = await http_client.get(url, headers={"Accept": "application/json"})
327
295
 
328
296
  if not response.is_success:
329
297
  raise Exception(f"Health check failed: HTTP {response.status_code}")
@@ -336,10 +304,7 @@ async def get_a2a_capabilities(client: A2AClientState) -> Dict[str, Any]:
336
304
  url = f"{client.config.base_url}/a2a/capabilities"
337
305
 
338
306
  async with httpx.AsyncClient(timeout=client.config.timeout / 1000.0) as http_client:
339
- response = await http_client.get(
340
- url,
341
- headers={"Accept": "application/json"}
342
- )
307
+ response = await http_client.get(url, headers={"Accept": "application/json"})
343
308
 
344
309
  if not response.is_success:
345
310
  raise Exception(f"Capabilities request failed: HTTP {response.status_code}")
@@ -371,16 +336,15 @@ async def connect_to_a2a_agent(base_url: str) -> Dict[str, Any]:
371
336
  "ask": ask,
372
337
  "stream": stream,
373
338
  "health": health,
374
- "capabilities": capabilities
339
+ "capabilities": capabilities,
375
340
  }
376
341
 
377
342
 
378
343
  # Utility functions
379
344
 
345
+
380
346
  def create_a2a_message_dict(
381
- text: str,
382
- role: str = "user",
383
- context_id: Optional[str] = None
347
+ text: str, role: str = "user", context_id: Optional[str] = None
384
348
  ) -> Dict[str, Any]:
385
349
  """Utility function to create A2A message dictionary"""
386
350
  return {
@@ -388,7 +352,7 @@ def create_a2a_message_dict(
388
352
  "parts": [{"kind": "text", "text": text}],
389
353
  "messageId": f"msg_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
390
354
  "contextId": context_id,
391
- "kind": "message"
355
+ "kind": "message",
392
356
  }
393
357
 
394
358
 
@@ -410,8 +374,8 @@ def parse_sse_event(line: str) -> Optional[Dict[str, Any]]:
410
374
  def validate_a2a_response(response: Dict[str, Any]) -> bool:
411
375
  """Pure function to validate A2A response"""
412
376
  return (
413
- isinstance(response, dict) and
414
- response.get("jsonrpc") == "2.0" and
415
- "id" in response and
416
- ("result" in response or "error" in response)
377
+ isinstance(response, dict)
378
+ and response.get("jsonrpc") == "2.0"
379
+ and "id" in response
380
+ and ("result" in response or "error" in response)
417
381
  )
@@ -7,13 +7,13 @@ using the JAF A2A client library.
7
7
  Usage:
8
8
  # Start the server first:
9
9
  python server_example.py
10
-
10
+
11
11
  # Then run this client:
12
12
  python client_example.py
13
13
 
14
14
  The client will connect to http://localhost:3000 and demonstrate:
15
15
  - Agent discovery via Agent Cards
16
- - Sending messages to specific agents
16
+ - Sending messages to specific agents
17
17
  - Streaming message responses
18
18
  - Health checks and capabilities
19
19
  - Error handling
@@ -46,11 +46,11 @@ async def discover_server_agents(base_url: str):
46
46
  print(f"🌐 URL: {agent_card['url']}")
47
47
  print(f"šŸ“” Protocol: {agent_card['protocolVersion']}")
48
48
 
49
- skills = agent_card.get('skills', [])
49
+ skills = agent_card.get("skills", [])
50
50
  print(f"\nšŸ› ļø Available skills ({len(skills)}):")
51
51
  for skill in skills:
52
52
  print(f" • {skill['name']}: {skill['description']}")
53
- if skill.get('tags'):
53
+ if skill.get("tags"):
54
54
  print(f" Tags: {', '.join(skill['tags'])}")
55
55
 
56
56
  return agent_card
@@ -70,18 +70,18 @@ async def check_server_health(client):
70
70
  print(f"šŸ“” Protocol: {health.get('protocol', 'unknown')}")
71
71
  print(f"šŸ”¢ Version: {health.get('version', 'unknown')}")
72
72
 
73
- agents = health.get('agents', [])
73
+ agents = health.get("agents", [])
74
74
  if agents:
75
75
  print(f"šŸ¤– Available agents: {', '.join(agents)}")
76
76
 
77
77
  print("\n⚔ Getting capabilities...")
78
78
  capabilities = await get_a2a_capabilities(client)
79
79
 
80
- methods = capabilities.get('supportedMethods', [])
80
+ methods = capabilities.get("supportedMethods", [])
81
81
  if methods:
82
82
  print(f"šŸ“‹ Supported methods: {', '.join(methods)}")
83
83
 
84
- transports = capabilities.get('supportedTransports', [])
84
+ transports = capabilities.get("supportedTransports", [])
85
85
  if transports:
86
86
  print(f"šŸš€ Supported transports: {', '.join(transports)}")
87
87
 
@@ -100,7 +100,7 @@ async def test_math_agent(client):
100
100
  "What is 25 + 17?",
101
101
  "Calculate 144 / 12",
102
102
  "What is 2^8?",
103
- "Can you solve (15 + 5) * 3?"
103
+ "Can you solve (15 + 5) * 3?",
104
104
  ]
105
105
 
106
106
  for question in test_cases:
@@ -121,7 +121,7 @@ async def test_weather_agent(client):
121
121
  "What's the weather in London?",
122
122
  "How's the weather in Tokyo?",
123
123
  "Tell me about the weather in New York",
124
- "Weather forecast for Sydney please"
124
+ "Weather forecast for Sydney please",
125
125
  ]
126
126
 
127
127
  for question in locations:
@@ -142,7 +142,7 @@ async def test_translator_agent(client):
142
142
  "Translate 'Hello, how are you?' to Spanish",
143
143
  "Can you translate 'Good morning' to French?",
144
144
  "Translate 'Thank you very much' to German",
145
- "Convert 'Beautiful day' to Italian"
145
+ "Convert 'Beautiful day' to Italian",
146
146
  ]
147
147
 
148
148
  for question in translations:
@@ -179,7 +179,7 @@ async def test_general_assistant(client):
179
179
  "Hello, what can you help me with?",
180
180
  "Tell me a joke",
181
181
  "What is the meaning of life?",
182
- "How can I be more productive?"
182
+ "How can I be more productive?",
183
183
  ]
184
184
 
185
185
  for question in questions:
@@ -212,7 +212,7 @@ async def test_convenience_connection():
212
212
 
213
213
  # Test capabilities
214
214
  capabilities = await connection["capabilities"]()
215
- methods = capabilities.get('supportedMethods', [])
215
+ methods = capabilities.get("supportedMethods", [])
216
216
  print(f"⚔ Methods via convenience: {len(methods)} methods")
217
217
 
218
218
  return True