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/message.py ADDED
@@ -0,0 +1,366 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2025 Addison Kline, Jacob Hahn
3
+
4
+ import datetime
5
+ from typing import Any, Literal, TypedDict
6
+
7
+ from dict2xml import dict2xml
8
+
9
+ MAIL_MESSAGE_TYPES = [
10
+ "request",
11
+ "response",
12
+ "broadcast",
13
+ "interrupt",
14
+ "broadcast_complete",
15
+ ]
16
+
17
+
18
+ class MAILAddress(TypedDict):
19
+ """
20
+ An address representing the sender or recipient of a MAIL message.
21
+ """
22
+
23
+ address_type: Literal["admin", "agent", "system", "user"]
24
+ """The type of address."""
25
+ address: str
26
+ """The address of the sender or recipient."""
27
+
28
+
29
+ MAIL_ALL_LOCAL_AGENTS = MAILAddress(address_type="agent", address="all")
30
+
31
+
32
+ class MAILRequest(TypedDict):
33
+ """
34
+ A request to an agent using the MAIL protocol.
35
+ """
36
+
37
+ task_id: str
38
+ """The unique identifier for the task."""
39
+ request_id: str
40
+ """The unique identifier for the request."""
41
+ sender: MAILAddress
42
+ """The sender of the request."""
43
+ recipient: MAILAddress
44
+ """The recipient of the request."""
45
+ subject: str
46
+ """The subject of the request."""
47
+ body: str
48
+ """The body of the request."""
49
+ # Interswarm fields
50
+ sender_swarm: str | None
51
+ """The swarm name of the sender (for interswarm messages)."""
52
+ recipient_swarm: str | None
53
+ """The swarm name of the recipient (for interswarm messages)."""
54
+ routing_info: dict[str, Any] | None
55
+ """Additional routing information for interswarm messages."""
56
+
57
+
58
+ class MAILResponse(TypedDict):
59
+ """
60
+ A response from an agent using the MAIL protocol.
61
+ """
62
+
63
+ task_id: str
64
+ """The unique identifier for the task."""
65
+ request_id: str
66
+ """The unique identifier of the request being responded to."""
67
+ sender: MAILAddress
68
+ """The sender of the response."""
69
+ recipient: MAILAddress
70
+ """The recipient of the response."""
71
+ subject: str
72
+ """The status of the response."""
73
+ body: str
74
+ """The body of the response."""
75
+ # Interswarm fields
76
+ sender_swarm: str | None
77
+ """The swarm name of the sender (for interswarm messages)."""
78
+ recipient_swarm: str | None
79
+ """The swarm name of the recipient (for interswarm messages)."""
80
+ routing_info: dict[str, Any] | None
81
+ """Additional routing information for interswarm messages."""
82
+
83
+
84
+ class MAILBroadcast(TypedDict):
85
+ """
86
+ A broadcast message using the MAIL protocol.
87
+ """
88
+
89
+ task_id: str
90
+ """The unique identifier for the task."""
91
+ broadcast_id: str
92
+ """The unique identifier for the broadcast."""
93
+ sender: MAILAddress
94
+ """The sender of the broadcast."""
95
+ recipients: list[MAILAddress]
96
+ """The recipients of the broadcast."""
97
+ subject: str
98
+ """The subject of the broadcast."""
99
+ body: str
100
+ """The full details of the broadcast."""
101
+ # Interswarm fields
102
+ sender_swarm: str | None
103
+ """The swarm name of the sender (for interswarm messages)."""
104
+ recipient_swarms: list[str] | None
105
+ """The swarm names of the recipients (for interswarm messages)."""
106
+ routing_info: dict[str, Any] | None
107
+ """Additional routing information for interswarm messages."""
108
+
109
+
110
+ class MAILInterrupt(TypedDict):
111
+ """
112
+ An interrupt using the MAIL protocol.
113
+ """
114
+
115
+ task_id: str
116
+ """The unique identifier for the task."""
117
+ interrupt_id: str
118
+ """The unique identifier for the interrupt."""
119
+ sender: MAILAddress
120
+ """The sender of the interrupt."""
121
+ recipients: list[MAILAddress]
122
+ """The recipients of the interrupt."""
123
+ subject: str
124
+ """The description of the interrupt."""
125
+ body: str
126
+ """The full details of the interrupt, including what tasks to halt, conditions for resuming, and if interrupted tasks should be discarded."""
127
+ # Interswarm fields
128
+ sender_swarm: str | None
129
+ """The swarm name of the sender (for interswarm messages)."""
130
+ recipient_swarms: list[str] | None
131
+ """The swarm names of the recipients (for interswarm messages)."""
132
+ routing_info: dict[str, Any] | None
133
+ """Additional routing information for interswarm messages."""
134
+
135
+
136
+ class MAILInterswarmMessage(TypedDict):
137
+ """
138
+ An interswarm message wrapper for HTTP transport.
139
+ """
140
+
141
+ message_id: str
142
+ """The unique identifier for the interswarm message."""
143
+ source_swarm: str
144
+ """The source swarm name."""
145
+ target_swarm: str
146
+ """The target swarm name."""
147
+ timestamp: str
148
+ """The timestamp of the message."""
149
+ payload: MAILRequest | MAILResponse | MAILBroadcast | MAILInterrupt
150
+ """The wrapped MAIL message."""
151
+ msg_type: Literal["request", "response", "broadcast", "interrupt"]
152
+ """The type of the message."""
153
+ auth_token: str | None
154
+ """Authentication token for interswarm communication."""
155
+ task_owner: str
156
+ """The owner of the task (role:id@swarm)."""
157
+ task_contributors: list[str]
158
+ """The contributors to the task (role:id@swarm)."""
159
+ metadata: dict[str, Any] | None
160
+ """Additional metadata for routing and processing."""
161
+
162
+
163
+ def parse_task_contributors(contributors: list[str]) -> list[tuple[str, str, str]]:
164
+ """
165
+ Parse a list of task contributors in the format `role:id@swarm`.
166
+ """
167
+ return [parse_task_contributor(contributor) for contributor in contributors]
168
+
169
+
170
+ def parse_task_contributor(contributor: str) -> tuple[str, str, str]:
171
+ """
172
+ Parse an individual task contributor in the format `role:id@swarm`.
173
+ """
174
+ if ":" not in contributor:
175
+ raise ValueError("task contributor must be in the format 'role:id@swarm'")
176
+ if "@" not in contributor:
177
+ raise ValueError("task contributor must be in the format 'role:id@swarm'")
178
+
179
+ role = contributor.split(":")[0]
180
+ id = contributor.split(":")[1].split("@")[0]
181
+ swarm = contributor.split("@")[1]
182
+
183
+ return role, id, swarm
184
+
185
+
186
+ def parse_agent_address(address: str) -> tuple[str, str | None]:
187
+ """
188
+ Parse an agent address in the format 'agent-name' or 'agent-name@swarm-name'.
189
+
190
+ Returns:
191
+ tuple: (agent_name, swarm_name or None)
192
+ """
193
+ if "@" in address:
194
+ agent_name, swarm_name = address.split("@", 1)
195
+ return agent_name.strip(), swarm_name.strip()
196
+ else:
197
+ return address.strip(), None
198
+
199
+
200
+ def format_agent_address(agent_name: str, swarm_name: str | None = None) -> MAILAddress:
201
+ """
202
+ Format an agent address from agent name and optional swarm name.
203
+
204
+ Returns:
205
+ MAILAddress: Formatted address
206
+ """
207
+ if swarm_name:
208
+ return MAILAddress(address_type="agent", address=f"{agent_name}@{swarm_name}")
209
+ else:
210
+ return MAILAddress(address_type="agent", address=agent_name)
211
+
212
+
213
+ def create_address(
214
+ address: str, address_type: Literal["admin", "agent", "system", "user"]
215
+ ) -> MAILAddress:
216
+ """
217
+ Create a MAILAddress object with the specified type.
218
+
219
+ Args:
220
+ address: The address string
221
+ address_type: The type of address ("admin", "agent", "system", or "user")
222
+
223
+ Returns:
224
+ MAILAddress: A properly formatted address object
225
+ """
226
+ return MAILAddress(address_type=address_type, address=address)
227
+
228
+
229
+ def create_admin_address(address: str) -> MAILAddress:
230
+ """
231
+ Create a MAILAddress for an admin.
232
+ """
233
+ return create_address(address, "admin")
234
+
235
+
236
+ def create_agent_address(address: str) -> MAILAddress:
237
+ """
238
+ Create a MAILAddress for an AI agent.
239
+ """
240
+ return create_address(address, "agent")
241
+
242
+
243
+ def create_system_address(address: str) -> MAILAddress:
244
+ """
245
+ Create a MAILAddress for the system.
246
+ """
247
+ return create_address(address, "system")
248
+
249
+
250
+ def create_user_address(address: str) -> MAILAddress:
251
+ """
252
+ Create a MAILAddress for a human user.
253
+ """
254
+ return create_address(address, "user")
255
+
256
+
257
+ def get_address_string(address: MAILAddress) -> str:
258
+ """
259
+ Extract the address string from a MAILAddress.
260
+ """
261
+ return address["address"]
262
+
263
+
264
+ def get_address_type(
265
+ address: MAILAddress,
266
+ ) -> Literal["admin", "agent", "system", "user"]:
267
+ """
268
+ Extract the address type from a MAILAddress.
269
+ """
270
+ return address["address_type"]
271
+
272
+
273
+ def build_body_xml(content: dict[str, Any]) -> str:
274
+ """
275
+ Build the XML representation a MAIL body section.
276
+ """
277
+ return str(dict2xml(content, wrap="body", indent=""))
278
+
279
+
280
+ def build_mail_xml(message: "MAILMessage", is_manual: bool = False) -> dict[str, str]:
281
+ """
282
+ Build the XML representation of a MAIL message.
283
+ """
284
+ if is_manual:
285
+ return {
286
+ "role": "user",
287
+ "content": message["message"]["body"],
288
+ }
289
+ to = (
290
+ message["message"]["recipient"] # type: ignore
291
+ if "recipient" in message["message"]
292
+ else message["message"]["recipients"]
293
+ )
294
+ to = [to] if isinstance(to, dict) else to
295
+
296
+ # Extract sender and recipient information with type metadata
297
+ sender = message["message"]["sender"]
298
+ sender_str = get_address_string(sender)
299
+ sender_type = get_address_type(sender)
300
+ return {
301
+ "role": "user",
302
+ "content": f"""
303
+ <incoming_message>
304
+ <timestamp>{datetime.datetime.fromisoformat(message["timestamp"]).astimezone(datetime.UTC).isoformat()}</timestamp>
305
+ <from type="{sender_type}">{sender_str}</from>
306
+ <to>
307
+ {[f'<address type="{get_address_type(recipient)}">{get_address_string(recipient)}</address>' for recipient in to]}
308
+ </to>
309
+ <subject>{message["message"]["subject"]}</subject>
310
+ <body>{message["message"]["body"]}</body>
311
+ </incoming_message>
312
+ """,
313
+ }
314
+
315
+
316
+ def build_interswarm_mail_xml(message: MAILInterswarmMessage) -> dict[str, str]:
317
+ """
318
+ Build the XML representation of an interswarm MAIL message.
319
+ """
320
+ return {
321
+ "role": "user",
322
+ "content": f"""
323
+ <incoming_message>
324
+ <timestamp>{
325
+ datetime.datetime.fromisoformat(message["timestamp"])
326
+ .astimezone(datetime.UTC)
327
+ .isoformat()
328
+ }</timestamp>
329
+ <from type="agent">{message["payload"]["sender"]["address"]}</from>
330
+ <to>
331
+ {
332
+ [
333
+ f'<address type="agent">{recipient["address"]}</address>'
334
+ for recipient in message["payload"]["recipients"] # type: ignore
335
+ ]
336
+ if "recipients" in message["payload"] # type: ignore
337
+ else f'<address type="agent">{message["payload"]["recipient"]["address"]}</address>'
338
+ }
339
+ </to>
340
+ <subject>{message["payload"]["subject"]}</subject>
341
+ <body>{message["payload"]["body"]}</body>
342
+ </incoming_message>
343
+ """,
344
+ }
345
+
346
+
347
+ class MAILMessage(TypedDict):
348
+ """
349
+ A message using the MAIL protocol.
350
+ """
351
+
352
+ id: str
353
+ """The unique identifier for the message."""
354
+ timestamp: str
355
+ """The timestamp of the message."""
356
+ message: MAILRequest | MAILResponse | MAILBroadcast | MAILInterrupt
357
+ """The message content."""
358
+ msg_type: Literal[
359
+ "request",
360
+ "response",
361
+ "broadcast",
362
+ "interrupt",
363
+ "broadcast_complete",
364
+ "buffered",
365
+ ]
366
+ """The type of the message."""