mail-swarms 1.3.2__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 (137) hide show
  1. mail/__init__.py +35 -0
  2. mail/api.py +1964 -0
  3. mail/cli.py +432 -0
  4. mail/client.py +1657 -0
  5. mail/config/__init__.py +8 -0
  6. mail/config/client.py +87 -0
  7. mail/config/server.py +165 -0
  8. mail/core/__init__.py +72 -0
  9. mail/core/actions.py +69 -0
  10. mail/core/agents.py +73 -0
  11. mail/core/message.py +366 -0
  12. mail/core/runtime.py +3537 -0
  13. mail/core/tasks.py +311 -0
  14. mail/core/tools.py +1206 -0
  15. mail/db/__init__.py +0 -0
  16. mail/db/init.py +182 -0
  17. mail/db/types.py +65 -0
  18. mail/db/utils.py +523 -0
  19. mail/examples/__init__.py +27 -0
  20. mail/examples/analyst_dummy/__init__.py +15 -0
  21. mail/examples/analyst_dummy/agent.py +136 -0
  22. mail/examples/analyst_dummy/prompts.py +44 -0
  23. mail/examples/consultant_dummy/__init__.py +15 -0
  24. mail/examples/consultant_dummy/agent.py +136 -0
  25. mail/examples/consultant_dummy/prompts.py +42 -0
  26. mail/examples/data_analysis/__init__.py +40 -0
  27. mail/examples/data_analysis/analyst/__init__.py +9 -0
  28. mail/examples/data_analysis/analyst/agent.py +67 -0
  29. mail/examples/data_analysis/analyst/prompts.py +53 -0
  30. mail/examples/data_analysis/processor/__init__.py +13 -0
  31. mail/examples/data_analysis/processor/actions.py +293 -0
  32. mail/examples/data_analysis/processor/agent.py +67 -0
  33. mail/examples/data_analysis/processor/prompts.py +48 -0
  34. mail/examples/data_analysis/reporter/__init__.py +10 -0
  35. mail/examples/data_analysis/reporter/actions.py +187 -0
  36. mail/examples/data_analysis/reporter/agent.py +67 -0
  37. mail/examples/data_analysis/reporter/prompts.py +49 -0
  38. mail/examples/data_analysis/statistics/__init__.py +18 -0
  39. mail/examples/data_analysis/statistics/actions.py +343 -0
  40. mail/examples/data_analysis/statistics/agent.py +67 -0
  41. mail/examples/data_analysis/statistics/prompts.py +60 -0
  42. mail/examples/mafia/__init__.py +0 -0
  43. mail/examples/mafia/game.py +1537 -0
  44. mail/examples/mafia/narrator_tools.py +396 -0
  45. mail/examples/mafia/personas.py +240 -0
  46. mail/examples/mafia/prompts.py +489 -0
  47. mail/examples/mafia/roles.py +147 -0
  48. mail/examples/mafia/spec.md +350 -0
  49. mail/examples/math_dummy/__init__.py +23 -0
  50. mail/examples/math_dummy/actions.py +252 -0
  51. mail/examples/math_dummy/agent.py +136 -0
  52. mail/examples/math_dummy/prompts.py +46 -0
  53. mail/examples/math_dummy/types.py +5 -0
  54. mail/examples/research/__init__.py +39 -0
  55. mail/examples/research/researcher/__init__.py +9 -0
  56. mail/examples/research/researcher/agent.py +67 -0
  57. mail/examples/research/researcher/prompts.py +54 -0
  58. mail/examples/research/searcher/__init__.py +10 -0
  59. mail/examples/research/searcher/actions.py +324 -0
  60. mail/examples/research/searcher/agent.py +67 -0
  61. mail/examples/research/searcher/prompts.py +53 -0
  62. mail/examples/research/summarizer/__init__.py +18 -0
  63. mail/examples/research/summarizer/actions.py +255 -0
  64. mail/examples/research/summarizer/agent.py +67 -0
  65. mail/examples/research/summarizer/prompts.py +55 -0
  66. mail/examples/research/verifier/__init__.py +10 -0
  67. mail/examples/research/verifier/actions.py +337 -0
  68. mail/examples/research/verifier/agent.py +67 -0
  69. mail/examples/research/verifier/prompts.py +52 -0
  70. mail/examples/supervisor/__init__.py +11 -0
  71. mail/examples/supervisor/agent.py +4 -0
  72. mail/examples/supervisor/prompts.py +93 -0
  73. mail/examples/support/__init__.py +33 -0
  74. mail/examples/support/classifier/__init__.py +10 -0
  75. mail/examples/support/classifier/actions.py +307 -0
  76. mail/examples/support/classifier/agent.py +68 -0
  77. mail/examples/support/classifier/prompts.py +56 -0
  78. mail/examples/support/coordinator/__init__.py +9 -0
  79. mail/examples/support/coordinator/agent.py +67 -0
  80. mail/examples/support/coordinator/prompts.py +48 -0
  81. mail/examples/support/faq/__init__.py +10 -0
  82. mail/examples/support/faq/actions.py +182 -0
  83. mail/examples/support/faq/agent.py +67 -0
  84. mail/examples/support/faq/prompts.py +42 -0
  85. mail/examples/support/sentiment/__init__.py +15 -0
  86. mail/examples/support/sentiment/actions.py +341 -0
  87. mail/examples/support/sentiment/agent.py +67 -0
  88. mail/examples/support/sentiment/prompts.py +54 -0
  89. mail/examples/weather_dummy/__init__.py +23 -0
  90. mail/examples/weather_dummy/actions.py +75 -0
  91. mail/examples/weather_dummy/agent.py +136 -0
  92. mail/examples/weather_dummy/prompts.py +35 -0
  93. mail/examples/weather_dummy/types.py +5 -0
  94. mail/factories/__init__.py +27 -0
  95. mail/factories/action.py +223 -0
  96. mail/factories/base.py +1531 -0
  97. mail/factories/supervisor.py +241 -0
  98. mail/net/__init__.py +7 -0
  99. mail/net/registry.py +712 -0
  100. mail/net/router.py +728 -0
  101. mail/net/server_utils.py +114 -0
  102. mail/net/types.py +247 -0
  103. mail/server.py +1605 -0
  104. mail/stdlib/__init__.py +0 -0
  105. mail/stdlib/anthropic/__init__.py +0 -0
  106. mail/stdlib/fs/__init__.py +15 -0
  107. mail/stdlib/fs/actions.py +209 -0
  108. mail/stdlib/http/__init__.py +19 -0
  109. mail/stdlib/http/actions.py +333 -0
  110. mail/stdlib/interswarm/__init__.py +11 -0
  111. mail/stdlib/interswarm/actions.py +208 -0
  112. mail/stdlib/mcp/__init__.py +19 -0
  113. mail/stdlib/mcp/actions.py +294 -0
  114. mail/stdlib/openai/__init__.py +13 -0
  115. mail/stdlib/openai/agents.py +451 -0
  116. mail/summarizer.py +234 -0
  117. mail/swarms_json/__init__.py +27 -0
  118. mail/swarms_json/types.py +87 -0
  119. mail/swarms_json/utils.py +255 -0
  120. mail/url_scheme.py +51 -0
  121. mail/utils/__init__.py +53 -0
  122. mail/utils/auth.py +194 -0
  123. mail/utils/context.py +17 -0
  124. mail/utils/logger.py +73 -0
  125. mail/utils/openai.py +212 -0
  126. mail/utils/parsing.py +89 -0
  127. mail/utils/serialize.py +292 -0
  128. mail/utils/store.py +49 -0
  129. mail/utils/string_builder.py +119 -0
  130. mail/utils/version.py +20 -0
  131. mail_swarms-1.3.2.dist-info/METADATA +237 -0
  132. mail_swarms-1.3.2.dist-info/RECORD +137 -0
  133. mail_swarms-1.3.2.dist-info/WHEEL +4 -0
  134. mail_swarms-1.3.2.dist-info/entry_points.txt +2 -0
  135. mail_swarms-1.3.2.dist-info/licenses/LICENSE +202 -0
  136. mail_swarms-1.3.2.dist-info/licenses/NOTICE +10 -0
  137. mail_swarms-1.3.2.dist-info/licenses/THIRD_PARTY_NOTICES.md +12334 -0
mail/core/tools.py ADDED
@@ -0,0 +1,1206 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2025 Addison Kline, Ryan Heaton
3
+
4
+ import datetime
5
+ import logging
6
+ from typing import Any, Literal, Optional, cast
7
+ from uuid import uuid4
8
+
9
+ import ujson
10
+ from openai import pydantic_function_tool
11
+ from openai.resources.responses.responses import _make_tools
12
+ from pydantic import BaseModel, Field, model_validator
13
+
14
+ from .message import (
15
+ MAILBroadcast,
16
+ MAILInterrupt,
17
+ MAILMessage,
18
+ MAILRequest,
19
+ MAILResponse,
20
+ create_agent_address,
21
+ )
22
+
23
+ logger = logging.getLogger("mail.tools")
24
+
25
+ MAIL_TOOL_NAMES = [
26
+ "send_request",
27
+ "send_response",
28
+ "send_interrupt",
29
+ "send_broadcast",
30
+ "task_complete",
31
+ "acknowledge_broadcast",
32
+ "ignore_broadcast",
33
+ "await_message",
34
+ "help",
35
+ ]
36
+
37
+
38
+ def get_tool_spec_name(tool: dict[str, Any]) -> str | None:
39
+ """
40
+ Extract the logical tool name from either responses or completions tool specs.
41
+ """
42
+ name = tool.get("name")
43
+ if isinstance(name, str):
44
+ return name
45
+ maybe_function = tool.get("function")
46
+ if isinstance(maybe_function, dict):
47
+ function_name = maybe_function.get("name")
48
+ if isinstance(function_name, str):
49
+ return function_name
50
+ return None
51
+
52
+
53
+ def pydantic_model_to_tool(
54
+ model_cls: type[BaseModel],
55
+ name: str | None = None,
56
+ description: str | None = None,
57
+ style: Literal["completions", "responses"] = "completions",
58
+ ) -> dict[str, Any]:
59
+ """
60
+ Convert a Pydantic model class into an OpenAI function tool spec.
61
+
62
+ Returns a dict in the shape expected by Chat Completions and is compatible
63
+ with the Responses API (we later mirror parameters → input_schema when needed).
64
+ """
65
+ completions_tool = pydantic_function_tool(
66
+ model_cls, name=name, description=description
67
+ )
68
+ if style == "completions":
69
+ return cast(dict[str, Any], completions_tool)
70
+ elif style == "responses":
71
+ return _make_tools([completions_tool])[0] # type: ignore
72
+
73
+
74
+ class AgentToolCall(BaseModel):
75
+ """
76
+ A tool call from an agent.
77
+
78
+ Args:
79
+ tool_name: The name of the tool called.
80
+ tool_args: The arguments passed to the tool.
81
+ tool_call_id: The ID of the tool call.
82
+ completion: The full completion of the tool call, if using completions api.
83
+ responses: The full responses list of the tool call, if using responses api.
84
+ reasoning: List of reasoning/thinking text blocks preceding this tool call.
85
+ preamble: Text/message content that appeared before this tool call.
86
+ """
87
+
88
+ tool_name: str
89
+ tool_args: dict[str, Any]
90
+ tool_call_id: str
91
+ completion: dict[str, Any] = Field(default_factory=dict)
92
+ responses: list[dict[str, Any]] = Field(default_factory=list)
93
+ reasoning: list[str] | None = None
94
+ preamble: str | None = None
95
+
96
+ @model_validator(mode="after")
97
+ def check_completion_or_responses(self):
98
+ if not self.completion and not self.responses:
99
+ raise ValueError(
100
+ "Either 'completion' or 'responses' must be defined (non-empty)."
101
+ )
102
+ return self
103
+
104
+ def create_response_msg(self, content: str) -> dict[str, str]:
105
+ if self.completion:
106
+ return {
107
+ "role": "tool",
108
+ "name": self.tool_name,
109
+ "content": content,
110
+ "tool_call_id": self.tool_call_id,
111
+ }
112
+ return {
113
+ "type": "function_call_output",
114
+ "call_id": self.tool_call_id,
115
+ "output": content,
116
+ }
117
+
118
+
119
+ def convert_call_to_mail_message(
120
+ call: AgentToolCall, sender: str, task_id: str
121
+ ) -> MAILMessage:
122
+ """
123
+ Convert a MAIL tool call to a MAIL message.
124
+ """
125
+ # Convert sender string to MAILAddress (assuming it's an agent)
126
+ sender_address = create_agent_address(sender)
127
+
128
+ match call.tool_name:
129
+ case "send_request":
130
+ return MAILMessage(
131
+ id=str(uuid4()),
132
+ timestamp=datetime.datetime.now(datetime.UTC).isoformat(),
133
+ message=MAILRequest(
134
+ task_id=task_id,
135
+ request_id=str(uuid4()),
136
+ sender=sender_address,
137
+ recipient=create_agent_address(call.tool_args["target"]),
138
+ subject=call.tool_args["subject"],
139
+ body=call.tool_args["body"],
140
+ sender_swarm=None,
141
+ recipient_swarm=None,
142
+ routing_info={},
143
+ ),
144
+ msg_type="request",
145
+ )
146
+ case "send_response" | "text_output":
147
+ return MAILMessage(
148
+ id=str(uuid4()),
149
+ timestamp=datetime.datetime.now(datetime.UTC).isoformat(),
150
+ message=MAILResponse(
151
+ task_id=task_id,
152
+ request_id=str(uuid4()),
153
+ sender=sender_address,
154
+ recipient=create_agent_address(call.tool_args["target"]),
155
+ subject=call.tool_args.get("subject", ""),
156
+ body=call.tool_args.get("body", None)
157
+ or call.tool_args.get("content", ""),
158
+ sender_swarm=None,
159
+ recipient_swarm=None,
160
+ routing_info={},
161
+ ),
162
+ msg_type="response",
163
+ )
164
+ case "send_interrupt":
165
+ return MAILMessage(
166
+ id=str(uuid4()),
167
+ timestamp=datetime.datetime.now(datetime.UTC).isoformat(),
168
+ message=MAILInterrupt(
169
+ task_id=task_id,
170
+ interrupt_id=str(uuid4()),
171
+ sender=sender_address,
172
+ recipients=[create_agent_address(call.tool_args["target"])],
173
+ subject=call.tool_args["subject"],
174
+ body=call.tool_args["body"],
175
+ sender_swarm=None,
176
+ recipient_swarms=None,
177
+ routing_info={},
178
+ ),
179
+ msg_type="interrupt",
180
+ )
181
+ case "send_broadcast":
182
+ return MAILMessage(
183
+ id=str(uuid4()),
184
+ timestamp=datetime.datetime.now(datetime.UTC).isoformat(),
185
+ message=MAILBroadcast(
186
+ task_id=task_id,
187
+ broadcast_id=str(uuid4()),
188
+ sender=sender_address,
189
+ recipients=[create_agent_address("all")],
190
+ subject=call.tool_args["subject"],
191
+ body=call.tool_args["body"],
192
+ sender_swarm=None,
193
+ recipient_swarms=None,
194
+ routing_info={},
195
+ ),
196
+ msg_type="broadcast",
197
+ )
198
+ case "task_complete":
199
+ return MAILMessage(
200
+ id=str(uuid4()),
201
+ timestamp=datetime.datetime.now(datetime.UTC).isoformat(),
202
+ message=MAILBroadcast(
203
+ task_id=task_id,
204
+ broadcast_id=str(uuid4()),
205
+ sender=sender_address,
206
+ recipients=[create_agent_address("all")],
207
+ subject="Task complete",
208
+ body=call.tool_args["finish_message"],
209
+ sender_swarm=None,
210
+ recipient_swarms=None,
211
+ routing_info={},
212
+ ),
213
+ msg_type="broadcast_complete",
214
+ )
215
+ case _:
216
+ raise ValueError(f"Unknown tool name: {call.tool_name}")
217
+
218
+
219
+ def normalize_breakpoint_tool_call(
220
+ call: AgentToolCall, raw: dict[str, Any] | None = None
221
+ ) -> dict[str, Any]:
222
+ """
223
+ Normalize a breakpoint tool call to Responses-style function_call shape.
224
+ """
225
+ call_id: str | None = None
226
+ name: str | None = None
227
+ arguments: Any = None
228
+ status: str = "completed"
229
+ fc_id: str | None = None
230
+
231
+ if isinstance(raw, dict):
232
+ raw_type = raw.get("type")
233
+ if raw_type == "function_call":
234
+ call_id = raw.get("call_id")
235
+ name = raw.get("name")
236
+ arguments = raw.get("arguments")
237
+ status = raw.get("status") or status
238
+ fc_id = raw.get("id")
239
+ elif raw_type == "tool_use":
240
+ call_id = raw.get("id")
241
+ name = raw.get("name")
242
+ arguments = raw.get("input")
243
+
244
+ if name is None:
245
+ name = call.tool_name
246
+ if not call_id:
247
+ call_id = call.tool_call_id
248
+ if arguments is None:
249
+ arguments = call.tool_args
250
+ if not isinstance(arguments, str):
251
+ arguments = ujson.dumps(arguments)
252
+ if not fc_id:
253
+ fc_id = f"fc_{call_id}"
254
+
255
+ return {
256
+ "arguments": arguments,
257
+ "call_id": call_id,
258
+ "name": name,
259
+ "type": "function_call",
260
+ "id": fc_id,
261
+ "status": status,
262
+ }
263
+
264
+
265
+ def convert_manual_step_call_to_mail_message(
266
+ call: AgentToolCall,
267
+ sender: str,
268
+ task_id: str,
269
+ response_targets: list[str],
270
+ response_type: Literal["broadcast", "response", "request"],
271
+ ) -> MAILMessage:
272
+ """
273
+ Convert a MAIL tool call to a MAIL message.
274
+ """
275
+ # Convert sender string to MAILAddress (assuming it's an agent)
276
+ sender_address = create_agent_address(sender)
277
+ targets = []
278
+ for target in response_targets:
279
+ if target == "all":
280
+ targets.append(create_agent_address("all"))
281
+ else:
282
+ targets.append(create_agent_address(target))
283
+
284
+ match response_type:
285
+ case "request":
286
+ return MAILMessage(
287
+ id=str(uuid4()),
288
+ timestamp=datetime.datetime.now(datetime.UTC).isoformat(),
289
+ message=MAILRequest(
290
+ task_id=task_id,
291
+ request_id=str(uuid4()),
292
+ sender=sender_address,
293
+ recipient=targets[0],
294
+ subject=call.tool_args.get("subject", ""),
295
+ body=call.tool_args.get("body", None)
296
+ or call.tool_args.get("content", ""),
297
+ sender_swarm=None,
298
+ recipient_swarm=None,
299
+ routing_info={},
300
+ ),
301
+ msg_type="request",
302
+ )
303
+ case "response":
304
+ return MAILMessage(
305
+ id=str(uuid4()),
306
+ timestamp=datetime.datetime.now(datetime.UTC).isoformat(),
307
+ message=MAILResponse(
308
+ task_id=task_id,
309
+ request_id=str(uuid4()),
310
+ sender=sender_address,
311
+ recipient=targets[0],
312
+ subject=call.tool_args.get("subject", ""),
313
+ body=call.tool_args.get("body", None)
314
+ or call.tool_args.get("content", ""),
315
+ sender_swarm=None,
316
+ recipient_swarm=None,
317
+ routing_info={},
318
+ ),
319
+ msg_type="response",
320
+ )
321
+ case "broadcast":
322
+ return MAILMessage(
323
+ id=str(uuid4()),
324
+ timestamp=datetime.datetime.now(datetime.UTC).isoformat(),
325
+ message=MAILBroadcast(
326
+ task_id=task_id,
327
+ broadcast_id=str(uuid4()),
328
+ sender=sender_address,
329
+ recipients=targets,
330
+ subject=call.tool_args.get("subject", ""),
331
+ body=call.tool_args.get("body", None)
332
+ or call.tool_args.get("content", ""),
333
+ sender_swarm=None,
334
+ recipient_swarms=None,
335
+ routing_info={},
336
+ ),
337
+ msg_type="broadcast",
338
+ )
339
+
340
+
341
+ def create_request_tool(
342
+ targets: list[str],
343
+ enable_interswarm: bool = False,
344
+ style: Literal["completions", "responses"] = "completions",
345
+ ) -> dict[str, Any]:
346
+ """
347
+ Create a MAIL message tool to send messages to specific agents.
348
+ """
349
+
350
+ class send_request(BaseModel):
351
+ """Send a message to a specific target recipient agent."""
352
+
353
+ target: str = Field(
354
+ description=f"The target recipient agent for the message. Must be one of: {', '.join(targets)}"
355
+ + (
356
+ ", or use 'agent-name@swarm-name' format for interswarm messaging"
357
+ if enable_interswarm
358
+ else ""
359
+ )
360
+ )
361
+ subject: str = Field(description="The subject of the message.")
362
+ body: str = Field(description="The message content to send.")
363
+
364
+ tool_dict = pydantic_model_to_tool(send_request, name="send_request", style=style)
365
+
366
+ target_param = (
367
+ tool_dict["function"]["parameters"]["properties"]["target"]
368
+ if style == "completions"
369
+ else tool_dict["parameters"]["properties"]["target"]
370
+ )
371
+ if enable_interswarm:
372
+ # For interswarm messaging, we don't restrict to enum values
373
+ # The validation will happen at runtime
374
+ target_param["description"] = (
375
+ target_param["description"]
376
+ + " (supports interswarm format: agent-name@swarm-name)"
377
+ )
378
+ else:
379
+ target_param["enum"] = targets # This provides the allowed values to the LLM
380
+
381
+ return tool_dict
382
+
383
+
384
+ SEND_REQUEST_HELP_STRING = """
385
+ Send a message to a specific recipient agent (the `target`).
386
+ This is useful for initiating a conversation with another agent.
387
+
388
+ # Example
389
+ A MAIL swarm `example` has agents `supervisor` and `weather`.
390
+ `supervisor` can interact with the user and complete the task, while `weather` can call the `get_weather_forecast` action.
391
+ Say a user creates a task for getting the weather forecast in Tokyo:
392
+
393
+ ```xml
394
+ <message>
395
+ <sender type="user">example_user</sender>
396
+ <recipient type="agent">supervisor</recipient>
397
+ <subject>New Message</subject>
398
+ <body>What will the weather be like in Tokyo tomorrow?</body>
399
+ <msg_type>request</msg_type>
400
+ </message>
401
+ ```
402
+
403
+ `supervisor` can then delegate the task to `weather`:
404
+
405
+ ```xml
406
+ <message>
407
+ <sender type="agent">supervisor</sender>
408
+ <recipient type="agent">weather</recipient>
409
+ <subject>Weather forecast in Tokyo</subject>
410
+ <body>User asked: 'What will the weather be like in Tokyo tomorrow?' Please get the forecast and respond to me with the result.</body>
411
+ <msg_type>request</msg_type>
412
+ </message>
413
+ ```
414
+
415
+ From there, `weather` can call the `get_weather_forecast` action, and send the result back to `supervisor` in the form of a response.
416
+ Finally, `supervisor` can call the `task_complete` tool with the final answer to the user's task (in this case, the weather forecast for Tokyo tomorrow).
417
+ """
418
+
419
+
420
+ def create_response_tool(
421
+ targets: list[str],
422
+ enable_interswarm: bool = False,
423
+ style: Literal["completions", "responses"] = "completions",
424
+ ) -> dict[str, Any]:
425
+ """
426
+ Create a MAIL message tool to send messages to specific agents.
427
+ """
428
+
429
+ class send_response(BaseModel):
430
+ """Send a message to a specific target recipient agent."""
431
+
432
+ target: str = Field(
433
+ description=f"The target recipient agent for the message. Must be one of: {', '.join(targets)}"
434
+ + (
435
+ ", or use 'agent-name@swarm-name' format for interswarm messaging"
436
+ if enable_interswarm
437
+ else ""
438
+ )
439
+ )
440
+ subject: str = Field(description="The subject of the message.")
441
+ body: str = Field(description="The message content to send.")
442
+
443
+ tool_dict = pydantic_model_to_tool(send_response, name="send_response", style=style)
444
+
445
+ target_param = (
446
+ tool_dict["function"]["parameters"]["properties"]["target"]
447
+ if style == "completions"
448
+ else tool_dict["parameters"]["properties"]["target"]
449
+ )
450
+ if enable_interswarm:
451
+ # For interswarm messaging, we don't restrict to enum values
452
+ # The validation will happen at runtime
453
+ target_param["description"] = (
454
+ target_param["description"]
455
+ + " (supports interswarm format: agent-name@swarm-name)"
456
+ )
457
+ else:
458
+ target_param["enum"] = targets # This provides the allowed values to the LLM
459
+
460
+ return tool_dict
461
+
462
+
463
+ SEND_RESPONSE_HELP_STRING = """
464
+ Send a message to a specific target recipient agent (the `target`).
465
+ This is useful for responding to a message from another agent.
466
+
467
+ # Example
468
+ A MAIL swarm `example` has agents `supervisor` and `math`.
469
+ `supervisor` can interact with the user and complete the task, while `math` can call the `calculate_expression` action.
470
+ Say a user creates a task for solving the expression `2 * (3 + 4)`:
471
+
472
+ ```xml
473
+ <message>
474
+ <sender type="user">example_user</sender>
475
+ <recipient type="agent">supervisor</recipient>
476
+ <subject>New Message</subject>
477
+ <body>What is 2 * (3 + 4)?</body>
478
+ <msg_type>request</msg_type>
479
+ </message>
480
+ ```
481
+
482
+ `supervisor` can then delegate the task to `math`:
483
+
484
+ ```xml
485
+ <message>
486
+ <sender type="agent">supervisor</sender>
487
+ <recipient type="agent">math</recipient>
488
+ <subject>Math problem</subject>
489
+ <body>User asked: 'What is 2 * (3 + 4)?' Please solve the expression and respond to me with the result.</body>
490
+ <msg_type>request</msg_type>
491
+ </message>
492
+ ```
493
+
494
+ `math` can then call the `calculate_expression` action, and send the result back to `supervisor` in the form of a response:
495
+
496
+ ```xml
497
+ <message>
498
+ <sender type="agent">math</sender>
499
+ <recipient type="agent">supervisor</recipient>
500
+ <subject>Re: Math problem</subject>
501
+ <body>The result is 14.</body>
502
+ <msg_type>response</msg_type>
503
+ </message>
504
+ ```
505
+
506
+ Finally, `supervisor` can call the `task_complete` tool with the final answer to the user's task (in this case, the result of the expression `2 * (3 + 4)`).
507
+ """
508
+
509
+
510
+ def create_interrupt_tool(
511
+ targets: list[str],
512
+ enable_interswarm: bool = False,
513
+ style: Literal["completions", "responses"] = "completions",
514
+ ) -> dict[str, Any]:
515
+ """
516
+ Create a MAIL interrupt tool to interrupt specific agents.
517
+ """
518
+
519
+ class send_interrupt(BaseModel):
520
+ """Interrupt a specific target recipient agent."""
521
+
522
+ target: str = Field(
523
+ description=f"The target recipient agent for the interrupt. Must be one of: {', '.join(targets)}"
524
+ + (
525
+ ", or use 'agent-name@swarm-name' format for interswarm messaging"
526
+ if enable_interswarm
527
+ else ""
528
+ )
529
+ )
530
+ subject: str = Field(description="The subject of the interrupt.")
531
+ body: str = Field(description="The message content to send.")
532
+
533
+ tool_dict = pydantic_model_to_tool(
534
+ send_interrupt, name="send_interrupt", style=style
535
+ )
536
+
537
+ target_param = (
538
+ tool_dict["function"]["parameters"]["properties"]["target"]
539
+ if style == "completions"
540
+ else tool_dict["parameters"]["properties"]["target"]
541
+ )
542
+ if enable_interswarm:
543
+ target_param["description"] = (
544
+ target_param["description"]
545
+ + " (supports interswarm format: agent-name@swarm-name)"
546
+ )
547
+ else:
548
+ target_param["enum"] = targets # This provides the allowed values to the LLM
549
+
550
+ return tool_dict
551
+
552
+
553
+ SEND_INTERRUPT_HELP_STRING = """
554
+ Interrupt a specific target recipient agent (the `target`).
555
+ This is useful for interrupting a specific agent's execution.
556
+
557
+ # Example
558
+ A MAIL swarm `example` has agents `supervisor` and `wra`.
559
+ `supervisor` can interact with the user and complete the task, while `wra` can call the actions `start_web_research` and `fetch_web_research`.
560
+ Say a user creates a task to start web research on the topic of "effect of climate change by 2050":
561
+
562
+ ```xml
563
+ <message>
564
+ <sender type="user">example_user</sender>
565
+ <recipient type="agent">supervisor</recipient>
566
+ <subject>New Message</subject>
567
+ <body>Start web research on the topic of "effect of climate change by 2050".</body>
568
+ <msg_type>request</msg_type>
569
+ </message>
570
+ ```
571
+
572
+ `supervisor` can then delegate the task to `wra`:
573
+
574
+ ```xml
575
+ <message>
576
+ <sender type="agent">supervisor</sender>
577
+ <recipient type="agent">wra</recipient>
578
+ <subject>Web research</subject>
579
+ <body>User asked: 'Start web research on the topic of "effect of climate change by 2050".' Please start the research and respond to me with the result.</body>
580
+ <msg_type>request</msg_type>
581
+ </message>
582
+ ```
583
+
584
+ From there, `wra` can call `start_web_research` to begin the research process for the user's query.
585
+ However, this task is taking too long to complete--perhaps due to API issues.
586
+ The user sends an interrupt to `supervisor` to check on the status of the research:
587
+
588
+ ```xml
589
+ <message>
590
+ <sender type="user">example_user</sender>
591
+ <recipient type="agent">supervisor</recipient>
592
+ <subject>New Message</subject>
593
+ <body>The web research process is taking too long to complete. Can you check on the status?</body>
594
+ <msg_type>interrupt</msg_type>
595
+ </message>
596
+ ```
597
+
598
+ `supervisor` can then call the `send_interrupt` tool to interrupt `wra`:
599
+
600
+ ```xml
601
+ <message>
602
+ <sender type="agent">supervisor</sender>
603
+ <recipient type="agent">wra</recipient>
604
+ <subject>Check on web research status</subject>
605
+ <body>The web research process for the query "effect of climate change by 2050" is taking too long to complete. Can you check on the status?</body>
606
+ <msg_type>interrupt</msg_type>
607
+ </message>
608
+ ```
609
+
610
+ From there, `wra` can call `fetch_web_research` to get the status of the research process.
611
+ The agent can then notify `supervisor` of the status of the research process:
612
+
613
+ ```xml
614
+ <message>
615
+ <sender type="agent">wra</sender>
616
+ <recipient type="agent">supervisor</recipient>
617
+ <subject>Web research status</subject>
618
+ <body>I checked the status on the web research process. It has not yet completed.</body>
619
+ <msg_type>response</msg_type>
620
+ </message>
621
+ ```
622
+
623
+ Finally, `supervisor` can call `task_complete` to inform the user of the research process status (in this case, not yet completed).
624
+ """
625
+
626
+
627
+ def create_interswarm_broadcast_tool(
628
+ style: Literal["completions", "responses"] = "completions",
629
+ ) -> dict[str, Any]:
630
+ """
631
+ Create a MAIL broadcast tool for interswarm communication.
632
+ """
633
+
634
+ class send_interswarm_broadcast(BaseModel):
635
+ """Broadcast a message to all known swarms."""
636
+
637
+ subject: str = Field(description="The subject of the broadcast.")
638
+ body: str = Field(description="The message content to send.")
639
+ target_swarms: list[str] = Field(
640
+ description="List of target swarm names. If empty, broadcasts to all known swarms.",
641
+ default=[],
642
+ )
643
+
644
+ return pydantic_model_to_tool(
645
+ send_interswarm_broadcast, name="send_interswarm_broadcast", style=style
646
+ )
647
+
648
+
649
+ def create_swarm_discovery_tool(
650
+ style: Literal["completions", "responses"] = "completions",
651
+ ) -> dict[str, Any]:
652
+ """
653
+ Create a tool for discovering and registering swarms.
654
+ """
655
+
656
+ class discover_swarms(BaseModel):
657
+ """Discover and register new swarms from discovery endpoints."""
658
+
659
+ discovery_urls: list[str] = Field(
660
+ description="List of URLs to discover swarms from."
661
+ )
662
+
663
+ return pydantic_model_to_tool(discover_swarms, name="discover_swarms", style=style)
664
+
665
+
666
+ def create_broadcast_tool(
667
+ style: Literal["completions", "responses"] = "completions",
668
+ ) -> dict[str, Any]:
669
+ """
670
+ Create a MAIL broadcast tool to broadcast messages to a specified list of agents, or all agents in the swarm.
671
+ """
672
+
673
+ class send_broadcast(BaseModel):
674
+ """Broadcast a message to a specified list of agents, or all agents in the swarm."""
675
+
676
+ subject: str = Field(description="The subject of the broadcast.")
677
+ body: str = Field(description="The message content to send.")
678
+ targets: list[str] = Field(
679
+ description="The list of agents to broadcast to. Use ['all'] to broadcast to all agents in the swarm."
680
+ )
681
+
682
+ return pydantic_model_to_tool(send_broadcast, name="send_broadcast", style=style)
683
+
684
+
685
+ SEND_BROADCAST_HELP_STRING = """
686
+ Broadcast a message to a specified list of agents, or all agents in the swarm.
687
+ This is useful for disseminating information relevant to the recipients.
688
+
689
+ # Example
690
+ A MAIL swarm `example` has agents `supervisor`, `weather`, and `wra`.
691
+ The supervisor can interact with the user and complete the task, while `weather` and `wra` have their own actions.
692
+ Say a user creates a task that utilizes both `weather` and `wra`:
693
+
694
+ ```xml
695
+ <message>
696
+ <sender type="user">example_user</sender>
697
+ <recipient type="agent">supervisor</recipient>
698
+ <subject>New Message</subject>
699
+ <body>Create a report on the impact of climate change by 2050.</body>
700
+ <msg_type>request</msg_type>
701
+ </message>
702
+ ```
703
+
704
+ `supervisor` can then delegate specific tasks to `weather` and `wra` before preparing a final response.
705
+ `weather` and `wra` may communicate with each other directly, not just through `supervisor`.
706
+ However, `weather` does not seem to be working properly--it's unable to process `supervisor`'s messages.
707
+ Upon seeing this, `supervisor` sends a broadcast to `wra` to inform it of the issue:
708
+
709
+ ```xml
710
+ <message>
711
+ <sender type="agent">supervisor</sender>
712
+ <recipients>
713
+ <recipient type="agent">wra</recipient>
714
+ </recipients>
715
+ <subject>Weather issue</subject>
716
+ <body>The weather agent is not responding to messages. Keep this in mind when preparing the report. I will let you know if there are any updates.</body>
717
+ <msg_type>broadcast</msg_type>
718
+ </message>
719
+ ```
720
+
721
+ Alternatively, `supervisor` can send a broadcast to all agents in the swarm (including `weather`):
722
+
723
+ ```xml
724
+ <message>
725
+ <sender type="agent">supervisor</sender>
726
+ <recipients>
727
+ <recipient type="agent">all</recipient>
728
+ </recipients>
729
+ <subject>Weather issue</subject>
730
+ <body>The weather agent is not responding to messages. Keep this in mind for this task. I will let you know if there are any updates.</body>
731
+ <msg_type>broadcast</msg_type>
732
+ </message>
733
+ ```
734
+
735
+ Broadcast recipients can then acknowledge the broadcast and continue with their work.
736
+ When the user's task is done, `task_complete` is called by `supervisor`.
737
+ """
738
+
739
+
740
+ def create_acknowledge_broadcast_tool(
741
+ style: Literal["completions", "responses"] = "completions",
742
+ ) -> dict[str, Any]:
743
+ """
744
+ Create a tool for agents to acknowledge a broadcast without replying.
745
+ When invoked, the runtime will store the incoming broadcast in the agent's
746
+ memory and will not emit any outgoing MAIL message.
747
+ """
748
+
749
+ class acknowledge_broadcast(BaseModel):
750
+ """
751
+ Store the received broadcast in memory, do not respond.
752
+ This MUST NOT be used in response to a message of type other than 'broadcast'.
753
+ Not all broadcasts warrant acknowledgement--only those that do not warrant a response.
754
+ """
755
+
756
+ # Use Optional to avoid PEP 604 UnionType issues in some converters
757
+ note: Optional[str] = Field( # noqa: UP045
758
+ default=None,
759
+ description="Optional note to include in internal memory only.",
760
+ )
761
+
762
+ return pydantic_model_to_tool(
763
+ acknowledge_broadcast, name="acknowledge_broadcast", style=style
764
+ )
765
+
766
+
767
+ ACKNOWLEDGE_BROADCAST_HELP_STRING = """
768
+ Acknowledge a broadcast and store in memory without replying.
769
+ This is useful for cases where a broadcast does not warrant a response message.
770
+
771
+ # Example
772
+ A MAIL swarm `example` has agents `supervisor`, `weather`, and `wra`.
773
+ The supervisor can interact with the user and complete the task, while `weather` and `wra` have their own actions.
774
+ Say a user creates a task that utilizes both `weather` and `wra`:
775
+
776
+ ```xml
777
+ <message>
778
+ <sender type="user">example_user</sender>
779
+ <recipient type="agent">supervisor</recipient>
780
+ <subject>New Message</subject>
781
+ <body>Create a report on the impact of climate change by 2050.</body>
782
+ <msg_type>request</msg_type>
783
+ </message>
784
+ ```
785
+
786
+ `supervisor` can then delegate specific tasks to `weather` and `wra` before preparing a final response.
787
+ `weather` and `wra` may communicate with each other directly, not just through `supervisor`.
788
+ However, `weather` does not seem to be working properly--it's unable to process `supervisor`'s messages.
789
+ Upon seeing this, `supervisor` sends a broadcast to `wra` to inform it of the issue:
790
+
791
+ ```xml
792
+ <message>
793
+ <sender type="agent">supervisor</sender>
794
+ <recipients>
795
+ <recipient type="agent">wra</recipient>
796
+ </recipients>
797
+ <subject>Weather issue</subject>
798
+ <body>The weather agent is not responding to messages. Keep this in mind when preparing the report. I will let you know if there are any updates.</body>
799
+ <msg_type>broadcast</msg_type>
800
+ </message>
801
+ ```
802
+
803
+ Upon receiving the broadcast, `wra` can acknowledge the broadcast without replying:
804
+
805
+ ```python
806
+ acknowledge_broadcast(note="Acknowledged that `weather` is not responding to messages.")
807
+ ```
808
+
809
+ No response message is sent to `supervisor`, but the broadcast is stored in `wra`'s memory.
810
+ From there, the swarm can continue with their work until `task_complete` is called by `supervisor`.
811
+ """
812
+
813
+
814
+ def create_ignore_broadcast_tool(
815
+ style: Literal["completions", "responses"] = "completions",
816
+ ) -> dict[str, Any]:
817
+ """
818
+ Create a tool for agents to ignore a broadcast entirely.
819
+ When invoked, the runtime will neither store nor respond to the broadcast.
820
+ """
821
+
822
+ class ignore_broadcast(BaseModel):
823
+ """Ignore the received broadcast. No memory, no response."""
824
+
825
+ # Use Optional to avoid PEP 604 UnionType issues in some converters
826
+ reason: Optional[str] = Field( # noqa: UP045
827
+ default=None,
828
+ description="Optional internal reason for ignoring (not sent).",
829
+ )
830
+
831
+ return pydantic_model_to_tool(
832
+ ignore_broadcast, name="ignore_broadcast", style=style
833
+ )
834
+
835
+
836
+ IGNORE_BROADCAST_HELP_STRING = """
837
+ Ignore a broadcast by not storing or responding to it.
838
+ This is useful for cases where a broadcast does not warrant a response message.
839
+ Note that the broadcast itself is not saved, but the tool call is.
840
+
841
+ # Example
842
+ A MAIL swarm `example` has agents `supervisor`, `weather`, and `wra`.
843
+ The supervisor can interact with the user and complete the task, while `weather` and `wra` have their own actions.
844
+ `supervisor` can communicate with both `weather` and `wra` directly, but `weather` and `wra` cannot communicate with each other.
845
+ Say a user creates a task that utilizes both `weather` and `wra`:
846
+
847
+ ```xml
848
+ <message>
849
+ <sender type="user">example_user</sender>
850
+ <recipient type="agent">supervisor</recipient>
851
+ <subject>New Message</subject>
852
+ <body>Create a report on the impact of climate change by 2050.</body>
853
+ <msg_type>request</msg_type>
854
+ </message>
855
+ ```
856
+
857
+ `supervisor` can then delegate specific tasks to `weather` and `wra` before preparing a final response.
858
+ `weather` and `wra` may communicate with each other directly, not just through `supervisor`.
859
+ However, `weather` does not seem to be working properly--it's unable to process `supervisor`'s messages.
860
+ Upon seeing this, `supervisor` sends a broadcast to `wra` to inform it of the issue:
861
+
862
+ ```xml
863
+ <message>
864
+ <sender type="agent">supervisor</sender>
865
+ <recipients>
866
+ <recipient type="agent">wra</recipient>
867
+ </recipients>
868
+ <subject>Weather issue</subject>
869
+ <body>The weather agent is not responding to messages. Keep this in mind when preparing the report. I will let you know if there are any updates.</body>
870
+ <msg_type>broadcast</msg_type>
871
+ </message>
872
+ ```
873
+
874
+ However, since `weather` and `wra` cannot communicate with each other, `wra` can ignore the broadcast without replying:
875
+
876
+ ```python
877
+ ignore_broadcast(reason="`weather` is not responding to messages.")
878
+ ```
879
+
880
+ No response message is sent to `supervisor`, but the broadcast is ignored.
881
+ No broadcast is saved in `wra`'s memory, but the tool call is.
882
+ From there, the swarm can continue with their work until `task_complete` is called by `supervisor`.
883
+ """
884
+
885
+
886
+ def create_task_complete_tool(
887
+ style: Literal["completions", "responses"] = "completions",
888
+ ) -> dict[str, Any]:
889
+ """
890
+ Create a MAIL task complete tool to indicate that a task has been completed.
891
+ """
892
+
893
+ class task_complete(BaseModel):
894
+ """Indicate that a task has been completed. This will end the current loop, and should always be the last tool called."""
895
+
896
+ finish_message: str = Field(
897
+ description="""The final response to the user's task.
898
+ Since the user cannot see the swarm's communication, you MUST include the full answer to the user's task.
899
+ Furthermore, this broadcast will be sent to all agents in the swarm to notify them that the task has been completed.
900
+ """
901
+ )
902
+
903
+ return pydantic_model_to_tool(task_complete, name="task_complete", style=style)
904
+
905
+
906
+ TASK_COMPLETE_HELP_STRING = """
907
+ Indicate that the current task has been completed, and provide the user a final response.
908
+ This will end the current loop, and should always be the last tool called.
909
+
910
+ # Example
911
+ A MAIL swarm `example` has agents `supervisor` and `weather`.
912
+ The supervisor can interact with the user and complete the task, while `weather` can call the `get_weather_forecast` action.
913
+ Say a user creates a task for getting the weather forecast in Tokyo:
914
+
915
+ ```xml
916
+ <message>
917
+ <sender type="user">example_user</sender>
918
+ <recipient type="agent">supervisor</recipient>
919
+ <subject>New Message</subject>
920
+ <body>What will the weather be like in Tokyo tomorrow?</body>
921
+ <msg_type>request</msg_type>
922
+ </message>
923
+ ```
924
+
925
+ `supervisor` can then delegate the task to `weather`:
926
+
927
+ ```xml
928
+ <message>
929
+ <sender type="agent">supervisor</sender>
930
+ <recipient type="agent">weather</recipient>
931
+ <subject>Weather forecast in Tokyo</subject>
932
+ <body>User asked: 'What will the weather be like in Tokyo tomorrow?' Please get the forecast and respond to me with the result.</body>
933
+ <msg_type>request</msg_type>
934
+ </message>
935
+ ```
936
+
937
+ From there, `weather` can call the `get_weather_forecast` action and send the result back to `supervisor` in the form of a response:
938
+
939
+ ```xml
940
+ <message>
941
+ <sender type="agent">weather</sender>
942
+ <recipient type="agent">supervisor</recipient>
943
+ <subject>Re: Weather forecast in Tokyo</subject>
944
+ <body>The weather forecast for Tokyo tomorrow is sunny with a temperature of 20 degrees Celsius.</body>
945
+ <msg_type>response</msg_type>
946
+ </message>
947
+ ```
948
+
949
+ Finally, `supervisor` can call the `task_complete` tool with the final answer to the user's task (in this case, the weather forecast for Tokyo tomorrow).
950
+
951
+ ```python
952
+ task_complete(finish_message="The weather forecast for Tokyo tomorrow is sunny with a temperature of 20 degrees Celsius.")
953
+ ```
954
+
955
+ The finish message will be sent to the user.
956
+ Furthermore, the finish message will be sent to all agents in the swarm to notify them that the task has been completed:
957
+
958
+ ```xml
959
+ <message>
960
+ <sender type="agent">supervisor</sender>
961
+ <recipients>
962
+ <recipient type="agent">all</recipient>
963
+ </recipients>
964
+ <subject>Re: Weather forecast in Tokyo</subject>
965
+ <body>The weather forecast for Tokyo tomorrow is sunny with a temperature of 20 degrees Celsius.</body>
966
+ <msg_type>broadcast_complete</msg_type>
967
+ </message>
968
+ ```
969
+
970
+ Note that the broadcast_complete saved in agent memory for all recipients, but no recipients are reprompted.
971
+ The `task_complete` call shuts down the task runtime.
972
+ """
973
+
974
+
975
+ def create_await_message_tool(
976
+ style: Literal["completions", "responses"] = "completions",
977
+ ) -> dict[str, Any]:
978
+ """
979
+ Create a MAIL await message tool to wait for a message.
980
+ """
981
+
982
+ class await_message(BaseModel):
983
+ """Wait until another message is received."""
984
+
985
+ reason: Optional[str] = Field( # noqa: UP045
986
+ default=None,
987
+ description="Optional reason for waiting.",
988
+ )
989
+
990
+ return pydantic_model_to_tool(await_message, name="await_message", style=style)
991
+
992
+
993
+ AWAIT_MESSAGE_HELP_STRING = """
994
+ Wait until another message is received.
995
+ This is useful for cases where an agent needs to wait for a message before continuing.
996
+
997
+ # Example
998
+ A MAIL swarm `example` has agents `supervisor` and `wra`.
999
+ The supervisor can interact with the user and complete the task, while `wra` can call the `perform_web_research` action.
1000
+ Say a user creates a task for performing web research on the topic of "economic impact of AI by 2030":
1001
+
1002
+ ```xml
1003
+ <message>
1004
+ <sender type="user">example_user</sender>
1005
+ <recipient type="agent">supervisor</recipient>
1006
+ <subject>New Message</subject>
1007
+ <body>Perform web research on the topic of "economic impact of AI by 2030".</body>
1008
+ <msg_type>request</msg_type>
1009
+ </message>
1010
+ ```
1011
+
1012
+ `supervisor` can then delegate the task to `wra`:
1013
+
1014
+ ```xml
1015
+ <message>
1016
+ <sender type="agent">supervisor</sender>
1017
+ <recipient type="agent">wra</recipient>
1018
+ <subject>Web research</subject>
1019
+ <body>User asked: 'Perform web research on the topic of "economic impact of AI by 2030".' Please perform the research and respond to me with the result.</body>
1020
+ <msg_type>request</msg_type>
1021
+ </message>
1022
+ ```
1023
+
1024
+ From there, `wra` can call the `perform_web_research` action, and respond to `supervisor` to inform it that the research has started:
1025
+
1026
+ ```xml
1027
+ <message>
1028
+ <sender type="agent">wra</sender>
1029
+ <recipient type="agent">supervisor</recipient>
1030
+ <subject>Web research</subject>
1031
+ <body>I have started the web research. When the process is complete, I will respond to you with the result.</body>
1032
+ <msg_type>response</msg_type>
1033
+ </message>
1034
+ ```
1035
+
1036
+ In this case, `supervisor` has nothing else to do before research is complete.
1037
+ Since `wra` will receive an '::action_complete_broadcast::' message when the research is complete, `supervisor` can call the `await_message` tool to wait for `wra`'s response:
1038
+
1039
+ ```python
1040
+ await_message(reason="Waiting for wra's response.")
1041
+ ```
1042
+
1043
+ When `perform_web_research` is complete, `wra` will respond to `supervisor` with the result of the research:
1044
+
1045
+ ```xml
1046
+ <message>
1047
+ <sender type="agent">wra</sender>
1048
+ <recipient type="agent">supervisor</recipient>
1049
+ <subject>Web research</subject>
1050
+ <body>The result of the web research is as follows. ...</body>
1051
+ <msg_type>response</msg_type>
1052
+ </message>
1053
+ ```
1054
+
1055
+ This reprompts `supervisor`, who can proceed with the task until completion.
1056
+ """
1057
+
1058
+
1059
+ def create_help_tool(
1060
+ style: Literal["completions", "responses"] = "completions",
1061
+ ) -> dict[str, Any]:
1062
+ """
1063
+ Create a MAIL help tool to get help with MAIL.
1064
+ """
1065
+
1066
+ class help(BaseModel):
1067
+ """Get help with MAIL."""
1068
+
1069
+ get_summary: bool = Field(
1070
+ default=True, description="Whether to get a short summary of MAIL."
1071
+ )
1072
+ get_identity: bool = Field(
1073
+ default=False,
1074
+ description="Whether to get your identity (agent name, swarm, etc.).",
1075
+ )
1076
+ get_tool_help: list[
1077
+ Literal[
1078
+ "send_request",
1079
+ "send_response",
1080
+ "send_broadcast",
1081
+ "send_interrupt",
1082
+ "acknowledge_broadcast",
1083
+ "ignore_broadcast",
1084
+ "await_message",
1085
+ "task_complete",
1086
+ ]
1087
+ ] = Field(default=[], description="The tools to get help for.")
1088
+ get_full_protocol: bool = Field(
1089
+ default=False,
1090
+ description="Whether to get the full MAIL protocol specification.",
1091
+ )
1092
+
1093
+ return pydantic_model_to_tool(help, name="help", style=style)
1094
+
1095
+
1096
+ def create_mail_tools(
1097
+ targets: list[str],
1098
+ enable_interswarm: bool = False,
1099
+ style: Literal["completions", "responses"] = "completions",
1100
+ exclude_tools: list[str] | None = None,
1101
+ ) -> list[dict[str, Any]]:
1102
+ """
1103
+ Create MAIL tools. These should be used for all agents.
1104
+
1105
+ Args:
1106
+ targets: The agents that the agent can send messages to.
1107
+ enable_interswarm: Whether the agent can send interswarm messages.
1108
+ style: The style of the tools to create.
1109
+ exclude_tools: The names of MAIL tools that should not be available.
1110
+ """
1111
+ exclude_tools = exclude_tools or []
1112
+ all_tools = [
1113
+ create_request_tool(targets, enable_interswarm, style),
1114
+ create_response_tool(targets, enable_interswarm, style),
1115
+ create_acknowledge_broadcast_tool(style),
1116
+ create_ignore_broadcast_tool(style),
1117
+ create_await_message_tool(style),
1118
+ create_help_tool(style),
1119
+ ]
1120
+
1121
+ # filter out the excluded tools
1122
+ final_tools: list[dict[str, Any]] = []
1123
+ for tool in all_tools:
1124
+ tool_name = get_tool_spec_name(tool)
1125
+ if tool_name is None or tool_name not in exclude_tools:
1126
+ final_tools.append(tool)
1127
+
1128
+ return final_tools
1129
+
1130
+
1131
+ def create_supervisor_tools(
1132
+ targets: list[str],
1133
+ can_complete_tasks: bool = True,
1134
+ enable_interswarm: bool = False,
1135
+ exclude_tools: list[str] | None = None,
1136
+ style: Literal["completions", "responses"] = "completions",
1137
+ _debug_include_intraswarm: bool = True,
1138
+ ) -> list[dict[str, Any]]:
1139
+ """
1140
+ Create MAIL supervisor-exclusive tools.
1141
+
1142
+ Args:
1143
+ targets: The agents that the supervisor can send messages to.
1144
+ can_complete_tasks: Whether the supervisor can complete tasks.
1145
+ enable_interswarm: Whether the supervisor can send interswarm messages.
1146
+ exclude_tools: The names of MAIL tools that should not be available.
1147
+ style: The style of the tools to create.
1148
+ """
1149
+ exclude_tools = exclude_tools or []
1150
+ tools: list[dict[str, Any]] = []
1151
+ if _debug_include_intraswarm:
1152
+ tools += [
1153
+ create_interrupt_tool(targets, enable_interswarm, style),
1154
+ create_broadcast_tool(style),
1155
+ ]
1156
+
1157
+ if enable_interswarm:
1158
+ tools += [
1159
+ create_interswarm_broadcast_tool(style),
1160
+ create_swarm_discovery_tool(style),
1161
+ ]
1162
+
1163
+ if can_complete_tasks:
1164
+ tools.append(create_task_complete_tool(style))
1165
+
1166
+ # filter out the excluded tools
1167
+ final_tools: list[dict[str, Any]] = []
1168
+ for tool in tools:
1169
+ tool_name = get_tool_spec_name(tool)
1170
+ if tool_name is None or tool_name not in exclude_tools:
1171
+ final_tools.append(tool)
1172
+
1173
+ return final_tools
1174
+
1175
+
1176
+ def get_tool_help(
1177
+ tools: list[str],
1178
+ ) -> str:
1179
+ """
1180
+ Create a MAIL tools help string for the given list of tool names.
1181
+ """
1182
+ result = ""
1183
+ for tool in tools:
1184
+ match tool:
1185
+ case "send_request":
1186
+ tool_help = SEND_REQUEST_HELP_STRING
1187
+ case "send_response":
1188
+ tool_help = SEND_RESPONSE_HELP_STRING
1189
+ case "send_broadcast":
1190
+ tool_help = SEND_BROADCAST_HELP_STRING
1191
+ case "send_interrupt":
1192
+ tool_help = SEND_INTERRUPT_HELP_STRING
1193
+ case "acknowledge_broadcast":
1194
+ tool_help = ACKNOWLEDGE_BROADCAST_HELP_STRING
1195
+ case "ignore_broadcast":
1196
+ tool_help = IGNORE_BROADCAST_HELP_STRING
1197
+ case "await_message":
1198
+ tool_help = AWAIT_MESSAGE_HELP_STRING
1199
+ case "task_complete":
1200
+ tool_help = TASK_COMPLETE_HELP_STRING
1201
+ case _:
1202
+ raise ValueError(f"unknown tool name: {tool}")
1203
+
1204
+ result += f"===== Tool `{tool}` =====\n{tool_help}\n\n"
1205
+
1206
+ return result