waldiez 0.5.8__py3-none-any.whl → 0.5.10__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.
Potentially problematic release.
This version of waldiez might be problematic. Click here for more details.
- waldiez/_version.py +1 -1
- waldiez/cli.py +112 -24
- waldiez/exporting/agent/exporter.py +3 -0
- waldiez/exporting/agent/extras/captain_agent_extras.py +44 -7
- waldiez/exporting/agent/extras/handoffs/condition.py +3 -1
- waldiez/exporting/chats/utils/common.py +25 -23
- waldiez/exporting/core/__init__.py +0 -2
- waldiez/exporting/core/context.py +13 -13
- waldiez/exporting/core/protocols.py +0 -141
- waldiez/exporting/core/result.py +5 -5
- waldiez/exporting/flow/merger.py +2 -2
- waldiez/exporting/flow/orchestrator.py +1 -0
- waldiez/exporting/flow/utils/common.py +2 -2
- waldiez/exporting/flow/utils/importing.py +1 -0
- waldiez/exporting/flow/utils/logging.py +6 -7
- waldiez/exporting/tools/exporter.py +5 -0
- waldiez/exporting/tools/factory.py +4 -0
- waldiez/exporting/tools/processor.py +5 -1
- waldiez/io/_ws.py +13 -5
- waldiez/io/models/content/image.py +1 -0
- waldiez/io/models/user_input.py +4 -4
- waldiez/io/models/user_response.py +1 -0
- waldiez/io/mqtt.py +1 -1
- waldiez/io/structured.py +17 -17
- waldiez/io/utils.py +1 -1
- waldiez/io/ws.py +9 -11
- waldiez/logger.py +180 -63
- waldiez/models/agents/agent/update_system_message.py +0 -2
- waldiez/models/agents/doc_agent/doc_agent.py +8 -1
- waldiez/models/common/dict_utils.py +169 -40
- waldiez/models/flow/flow.py +6 -6
- waldiez/models/flow/info.py +5 -1
- waldiez/models/model/_llm.py +28 -14
- waldiez/models/model/model.py +4 -1
- waldiez/models/model/model_data.py +18 -5
- waldiez/models/tool/predefined/_config.py +5 -1
- waldiez/models/tool/predefined/_duckduckgo.py +4 -0
- waldiez/models/tool/predefined/_email.py +474 -0
- waldiez/models/tool/predefined/_google.py +8 -6
- waldiez/models/tool/predefined/_perplexity.py +3 -0
- waldiez/models/tool/predefined/_searxng.py +3 -0
- waldiez/models/tool/predefined/_tavily.py +4 -1
- waldiez/models/tool/predefined/_wikipedia.py +4 -1
- waldiez/models/tool/predefined/_youtube.py +4 -1
- waldiez/models/tool/predefined/protocol.py +3 -0
- waldiez/models/tool/tool.py +22 -4
- waldiez/models/waldiez.py +12 -0
- waldiez/runner.py +37 -54
- waldiez/running/__init__.py +6 -0
- waldiez/running/base_runner.py +310 -353
- waldiez/running/environment.py +1 -0
- waldiez/running/exceptions.py +9 -0
- waldiez/running/post_run.py +4 -4
- waldiez/running/pre_run.py +51 -40
- waldiez/running/protocol.py +21 -101
- waldiez/running/run_results.py +1 -1
- waldiez/running/standard_runner.py +84 -277
- waldiez/running/step_by_step/__init__.py +46 -0
- waldiez/running/step_by_step/breakpoints_mixin.py +188 -0
- waldiez/running/step_by_step/step_by_step_models.py +224 -0
- waldiez/running/step_by_step/step_by_step_runner.py +745 -0
- waldiez/running/subprocess_runner/__base__.py +282 -0
- waldiez/running/subprocess_runner/__init__.py +16 -0
- waldiez/running/subprocess_runner/_async_runner.py +362 -0
- waldiez/running/subprocess_runner/_sync_runner.py +455 -0
- waldiez/running/subprocess_runner/runner.py +561 -0
- waldiez/running/timeline_processor.py +1 -1
- waldiez/running/utils.py +376 -1
- waldiez/utils/version.py +2 -6
- waldiez/ws/__init__.py +70 -0
- waldiez/ws/__main__.py +15 -0
- waldiez/ws/_file_handler.py +201 -0
- waldiez/ws/cli.py +211 -0
- waldiez/ws/client_manager.py +835 -0
- waldiez/ws/errors.py +416 -0
- waldiez/ws/models.py +971 -0
- waldiez/ws/reloader.py +342 -0
- waldiez/ws/server.py +469 -0
- waldiez/ws/session_manager.py +393 -0
- waldiez/ws/session_stats.py +83 -0
- waldiez/ws/utils.py +385 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/METADATA +74 -74
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/RECORD +87 -65
- waldiez/running/patch_io_stream.py +0 -210
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/WHEEL +0 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/entry_points.txt +0 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
# pylint: disable=line-too-long
|
|
4
|
+
# flake8: noqa: E501
|
|
5
|
+
"""Predefined send email tool."""
|
|
6
|
+
|
|
7
|
+
from copy import deepcopy
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from ._config import PredefinedToolConfig
|
|
11
|
+
from .protocol import PredefinedTool
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SendEmailToolImpl(PredefinedTool):
|
|
15
|
+
"""Predefined tool for sending emails."""
|
|
16
|
+
|
|
17
|
+
required_secrets: list[str] = [
|
|
18
|
+
"SMTP_HOST",
|
|
19
|
+
"SMTP_PORT",
|
|
20
|
+
"SMTP_USERNAME",
|
|
21
|
+
"SMTP_PASSWORD",
|
|
22
|
+
]
|
|
23
|
+
required_kwargs: dict[str, type] = {}
|
|
24
|
+
|
|
25
|
+
DEFAULT_KWARGS: dict[str, Any] = {
|
|
26
|
+
"is_async": False, # controls whether the exported tool function is async or sync
|
|
27
|
+
"subject": "",
|
|
28
|
+
"body_text": "",
|
|
29
|
+
"body_html": "",
|
|
30
|
+
"to": [],
|
|
31
|
+
"attachments": [],
|
|
32
|
+
"cc": [],
|
|
33
|
+
"bcc": [],
|
|
34
|
+
"reply_to": [],
|
|
35
|
+
"sender": "",
|
|
36
|
+
"use_ssl": False,
|
|
37
|
+
"use_starttls": True,
|
|
38
|
+
"timeout": 30.0,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
kwarg_types: dict[str, type] = {
|
|
42
|
+
"is_async": bool,
|
|
43
|
+
"subject": str,
|
|
44
|
+
"body_text": str,
|
|
45
|
+
"body_html": str,
|
|
46
|
+
"to": list,
|
|
47
|
+
"attachments": list,
|
|
48
|
+
"cc": list,
|
|
49
|
+
"bcc": list,
|
|
50
|
+
"reply_to": list,
|
|
51
|
+
"sender": str,
|
|
52
|
+
"use_ssl": bool,
|
|
53
|
+
"use_starttls": bool,
|
|
54
|
+
"timeout": float,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def __init__(self) -> None:
|
|
58
|
+
self._kwargs: dict[str, Any] = deepcopy(self.DEFAULT_KWARGS)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def name(self) -> str:
|
|
62
|
+
return "send_email"
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def description(self) -> str:
|
|
66
|
+
return "A tool for sending emails with support for attachments, HTML/text content, and various SMTP configurations."
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def kwargs(self) -> dict[str, Any]:
|
|
70
|
+
"""
|
|
71
|
+
Get the keyword arguments for the tool.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
dict[str, Any]
|
|
76
|
+
The keyword arguments for the tool.
|
|
77
|
+
"""
|
|
78
|
+
return dict(self._kwargs)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def tags(self) -> list[str]:
|
|
82
|
+
return ["email", "communication", "smtp"]
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def requirements(self) -> list[str]:
|
|
86
|
+
return []
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def tool_imports(self) -> list[str]:
|
|
90
|
+
return [
|
|
91
|
+
"import asyncio",
|
|
92
|
+
"import smtplib",
|
|
93
|
+
"import ssl",
|
|
94
|
+
"from email.message import EmailMessage",
|
|
95
|
+
"from email.utils import formatdate, make_msgid",
|
|
96
|
+
"from pathlib import Path",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
def validate_secrets(self, secrets: dict[str, str]) -> list[str]:
|
|
100
|
+
"""
|
|
101
|
+
Validate the provided secrets against the tool's requirements.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
secrets: dict[str, str]
|
|
106
|
+
The secrets to validate.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
list[str]
|
|
111
|
+
A list of validation error messages, if any.
|
|
112
|
+
"""
|
|
113
|
+
problems: list[str] = []
|
|
114
|
+
for key in self.required_secrets:
|
|
115
|
+
val = secrets.get(key)
|
|
116
|
+
if val is None or str(val).strip() == "":
|
|
117
|
+
problems.append(key)
|
|
118
|
+
# pylint: disable=broad-exception-caught
|
|
119
|
+
try:
|
|
120
|
+
port = int(secrets.get("SMTP_PORT", ""))
|
|
121
|
+
if not 1 <= port <= 65535:
|
|
122
|
+
problems.append("SMTP_PORT (invalid port range)")
|
|
123
|
+
except Exception:
|
|
124
|
+
problems.append("SMTP_PORT (not a valid integer)")
|
|
125
|
+
return problems
|
|
126
|
+
|
|
127
|
+
def validate_kwargs(self, kwargs: dict[str, Any]) -> list[str]:
|
|
128
|
+
"""
|
|
129
|
+
Validate the provided keyword arguments against the tool's requirements.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
kwargs: dict[str, Any]
|
|
134
|
+
The keyword arguments to validate.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
list[str]
|
|
139
|
+
A list of validation error messages, if any.
|
|
140
|
+
"""
|
|
141
|
+
updated = dict(self._kwargs)
|
|
142
|
+
for key, value in kwargs.items():
|
|
143
|
+
if key in self.kwarg_types:
|
|
144
|
+
typ = self.kwarg_types[key]
|
|
145
|
+
# pylint: disable=broad-exception-caught
|
|
146
|
+
try:
|
|
147
|
+
if typ is list and isinstance(value, str):
|
|
148
|
+
updated[key] = [
|
|
149
|
+
s.strip() for s in value.split(",") if s.strip()
|
|
150
|
+
]
|
|
151
|
+
else:
|
|
152
|
+
updated[key] = typ(value)
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
self._kwargs = updated
|
|
156
|
+
return []
|
|
157
|
+
|
|
158
|
+
def _get_effective_kwargs(
|
|
159
|
+
self,
|
|
160
|
+
runtime_kwargs: dict[str, Any] | None = None,
|
|
161
|
+
) -> dict[str, Any]:
|
|
162
|
+
"""Get effective keyword arguments."""
|
|
163
|
+
effective = dict(self._kwargs)
|
|
164
|
+
if runtime_kwargs:
|
|
165
|
+
# cast only known keys, same rules as validate_kwargs
|
|
166
|
+
for k, v in runtime_kwargs.items():
|
|
167
|
+
if k in self.kwarg_types:
|
|
168
|
+
typ = self.kwarg_types[k]
|
|
169
|
+
# pylint: disable=broad-exception-caught
|
|
170
|
+
try:
|
|
171
|
+
if typ is list and isinstance(v, str):
|
|
172
|
+
effective[k] = [
|
|
173
|
+
s.strip() for s in v.split(",") if s.strip()
|
|
174
|
+
]
|
|
175
|
+
else:
|
|
176
|
+
effective[k] = typ(v)
|
|
177
|
+
except Exception:
|
|
178
|
+
pass
|
|
179
|
+
return effective
|
|
180
|
+
|
|
181
|
+
def get_content(
|
|
182
|
+
self,
|
|
183
|
+
secrets: dict[str, str],
|
|
184
|
+
runtime_kwargs: dict[str, Any] | None = None,
|
|
185
|
+
) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Generate the email content based on the provided secrets and tool kwargs.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
secrets: dict[str, str]
|
|
192
|
+
The secrets required for SMTP authentication and configuration.
|
|
193
|
+
runtime_kwargs: dict[str, Any] | None, optional
|
|
194
|
+
Runtime keyword arguments to customize the content generation.
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
str
|
|
199
|
+
The generated email content.
|
|
200
|
+
"""
|
|
201
|
+
smtp_host = secrets.get("SMTP_HOST", "smtp.gmail.com")
|
|
202
|
+
try:
|
|
203
|
+
smtp_port = int(secrets.get("SMTP_PORT", "587"))
|
|
204
|
+
except (ValueError, TypeError):
|
|
205
|
+
smtp_port = 587
|
|
206
|
+
smtp_user = secrets.get("SMTP_USERNAME", "")
|
|
207
|
+
smtp_pass = secrets.get("SMTP_PASSWORD", "")
|
|
208
|
+
|
|
209
|
+
effective_kwargs = self._get_effective_kwargs(runtime_kwargs)
|
|
210
|
+
|
|
211
|
+
is_async = effective_kwargs.get("is_async", False)
|
|
212
|
+
use_ssl = effective_kwargs.get("use_ssl", False)
|
|
213
|
+
use_starttls = effective_kwargs.get("use_starttls", True)
|
|
214
|
+
timeout = effective_kwargs.get("timeout", 30.0)
|
|
215
|
+
|
|
216
|
+
base = f"""
|
|
217
|
+
def _collect_rcpts(to, cc, bcc):
|
|
218
|
+
rcpts = []
|
|
219
|
+
for part in (to, cc, bcc):
|
|
220
|
+
if not part:
|
|
221
|
+
continue
|
|
222
|
+
if isinstance(part, str):
|
|
223
|
+
rcpts.extend([s.strip() for s in part.split(",") if s.strip()])
|
|
224
|
+
else:
|
|
225
|
+
rcpts.extend(part)
|
|
226
|
+
seen, out = set(), []
|
|
227
|
+
for r in rcpts:
|
|
228
|
+
if r not in seen:
|
|
229
|
+
seen.add(r)
|
|
230
|
+
out.append(r)
|
|
231
|
+
return out
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _split_emails(value) -> list[str]:
|
|
235
|
+
if not value:
|
|
236
|
+
return []
|
|
237
|
+
if isinstance(value, str):
|
|
238
|
+
return [s.strip() for s in value.split(",") if s.strip()]
|
|
239
|
+
return list(value)
|
|
240
|
+
|
|
241
|
+
def _build_message(
|
|
242
|
+
subject: str,
|
|
243
|
+
sender: str,
|
|
244
|
+
recipients: str | list[str],
|
|
245
|
+
body_text: str | None = None,
|
|
246
|
+
body_html: str | None = None,
|
|
247
|
+
attachments: list | None = None,
|
|
248
|
+
cc: list[str] | None = None,
|
|
249
|
+
reply_to: list[str] | None = None,
|
|
250
|
+
) -> EmailMessage:
|
|
251
|
+
recipients = _split_emails(recipients)
|
|
252
|
+
cc = _split_emails(cc)
|
|
253
|
+
reply_to = _split_emails(reply_to)
|
|
254
|
+
attachments = attachments or []
|
|
255
|
+
|
|
256
|
+
if not (body_text or body_html):
|
|
257
|
+
raise ValueError("Provide at least one of body_text or body_html.")
|
|
258
|
+
|
|
259
|
+
msg = EmailMessage()
|
|
260
|
+
msg["Subject"] = subject
|
|
261
|
+
msg["From"] = sender
|
|
262
|
+
msg["To"] = ", ".join(recipients)
|
|
263
|
+
msg["Date"] = formatdate(localtime=True)
|
|
264
|
+
msg["Message-ID"] = make_msgid()
|
|
265
|
+
if cc:
|
|
266
|
+
msg["Cc"] = ", ".join(cc)
|
|
267
|
+
if reply_to:
|
|
268
|
+
msg["Reply-To"] = ", ".join(reply_to)
|
|
269
|
+
|
|
270
|
+
if body_html and body_text:
|
|
271
|
+
msg.set_content(body_text)
|
|
272
|
+
msg.add_alternative(body_html, subtype="html")
|
|
273
|
+
elif body_html:
|
|
274
|
+
msg.add_alternative(body_html, subtype="html")
|
|
275
|
+
else:
|
|
276
|
+
msg.set_content(body_text or "")
|
|
277
|
+
|
|
278
|
+
for item in attachments:
|
|
279
|
+
if isinstance(item, Path):
|
|
280
|
+
data = item.read_bytes()
|
|
281
|
+
filename = item.name
|
|
282
|
+
mime = "application/octet-stream"
|
|
283
|
+
else:
|
|
284
|
+
filename, data, mime = item
|
|
285
|
+
maintype, _, subtype = mime.partition("/")
|
|
286
|
+
msg.add_attachment(data, maintype=maintype, subtype=subtype, filename=filename)
|
|
287
|
+
|
|
288
|
+
return msg
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _send_email_sync(
|
|
292
|
+
subject: str,
|
|
293
|
+
to: str | list[str],
|
|
294
|
+
body_text: str | None = None,
|
|
295
|
+
body_html: str | None = None,
|
|
296
|
+
attachments: list | None = None,
|
|
297
|
+
cc: list[str] | None = None,
|
|
298
|
+
bcc: list[str] | None = None,
|
|
299
|
+
reply_to: list[str] | None = None,
|
|
300
|
+
sender: str | None = None,
|
|
301
|
+
use_ssl: bool = {use_ssl},
|
|
302
|
+
use_starttls: bool = {use_starttls},
|
|
303
|
+
timeout: float = {timeout},
|
|
304
|
+
) -> dict:
|
|
305
|
+
smtp_host = "{smtp_host}"
|
|
306
|
+
smtp_port = {smtp_port}
|
|
307
|
+
username = "{smtp_user}"
|
|
308
|
+
password = "{smtp_pass}"
|
|
309
|
+
sender = sender or username
|
|
310
|
+
|
|
311
|
+
context = ssl.create_default_context()
|
|
312
|
+
msg = _build_message(
|
|
313
|
+
subject=subject,
|
|
314
|
+
sender=sender,
|
|
315
|
+
recipients=to,
|
|
316
|
+
body_text=body_text,
|
|
317
|
+
body_html=body_html,
|
|
318
|
+
attachments=attachments,
|
|
319
|
+
cc=cc,
|
|
320
|
+
reply_to=reply_to,
|
|
321
|
+
)
|
|
322
|
+
rcpts = _collect_rcpts(to, cc, bcc)
|
|
323
|
+
if not rcpts:
|
|
324
|
+
raise ValueError("No recipients provided (to/cc/bcc are all empty).")
|
|
325
|
+
if use_ssl:
|
|
326
|
+
with smtplib.SMTP_SSL(smtp_host, smtp_port, context=context, timeout=timeout) as server:
|
|
327
|
+
if username and password:
|
|
328
|
+
server.login(username, password)
|
|
329
|
+
return server.send_message(msg, to_addrs=rcpts)
|
|
330
|
+
else:
|
|
331
|
+
with smtplib.SMTP(smtp_host, smtp_port, timeout=timeout) as server:
|
|
332
|
+
server.ehlo()
|
|
333
|
+
if use_starttls:
|
|
334
|
+
server.starttls(context=context)
|
|
335
|
+
server.ehlo()
|
|
336
|
+
if username and password:
|
|
337
|
+
server.login(username, password)
|
|
338
|
+
return server.send_message(msg, to_addrs=rcpts)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
async def _send_email_async(
|
|
342
|
+
subject: str,
|
|
343
|
+
to: str | list[str],
|
|
344
|
+
body_text: str | None = None,
|
|
345
|
+
body_html: str | None = None,
|
|
346
|
+
attachments: list | None = None,
|
|
347
|
+
cc: list[str] | None = None,
|
|
348
|
+
bcc: list[str] | None = None,
|
|
349
|
+
reply_to: list[str] | None = None,
|
|
350
|
+
sender: str | None = None,
|
|
351
|
+
use_ssl: bool = {use_ssl},
|
|
352
|
+
use_starttls: bool = {use_starttls},
|
|
353
|
+
timeout: float = {timeout},
|
|
354
|
+
) -> dict:
|
|
355
|
+
return await asyncio.to_thread(
|
|
356
|
+
_send_email_sync,
|
|
357
|
+
subject=subject,
|
|
358
|
+
to=to,
|
|
359
|
+
body_text=body_text,
|
|
360
|
+
body_html=body_html,
|
|
361
|
+
attachments=attachments,
|
|
362
|
+
cc=cc,
|
|
363
|
+
bcc=bcc,
|
|
364
|
+
reply_to=reply_to,
|
|
365
|
+
sender=sender,
|
|
366
|
+
use_ssl=use_ssl,
|
|
367
|
+
use_starttls=use_starttls,
|
|
368
|
+
timeout=timeout,
|
|
369
|
+
)
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
# exported callable is chosen by config: async or sync
|
|
373
|
+
if is_async:
|
|
374
|
+
exported = f'''
|
|
375
|
+
async def {self.name}(
|
|
376
|
+
subject: str,
|
|
377
|
+
to: str | list[str],
|
|
378
|
+
body_text: str | None = None,
|
|
379
|
+
body_html: str | None = None,
|
|
380
|
+
attachments: list | None = None,
|
|
381
|
+
cc: list[str] | None = None,
|
|
382
|
+
bcc: list[str] | None = None,
|
|
383
|
+
reply_to: list[str] | None = None,
|
|
384
|
+
sender: str | None = None,
|
|
385
|
+
) -> dict:
|
|
386
|
+
"""Send email asynchronously.
|
|
387
|
+
|
|
388
|
+
At least one recipient must be specified.
|
|
389
|
+
At least one of `body_text` or `body_html` must be provided.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
subject: The email subject.
|
|
393
|
+
to: The recipient email address(es).
|
|
394
|
+
body_text: The plain text body of the email.
|
|
395
|
+
body_html: The HTML body of the email.
|
|
396
|
+
attachments: Any file attachments to include.
|
|
397
|
+
cc: The CC email address(es).
|
|
398
|
+
bcc: The BCC email address(es).
|
|
399
|
+
reply_to: The reply-to email address(es).
|
|
400
|
+
sender: The sender email address.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
A dictionary containing the result of the email sending operation.
|
|
404
|
+
"""
|
|
405
|
+
return await _send_email_async(
|
|
406
|
+
subject=subject,
|
|
407
|
+
to=to,
|
|
408
|
+
body_text=body_text,
|
|
409
|
+
body_html=body_html,
|
|
410
|
+
attachments=attachments,
|
|
411
|
+
cc=cc,
|
|
412
|
+
bcc=bcc,
|
|
413
|
+
reply_to=reply_to,
|
|
414
|
+
sender=sender,
|
|
415
|
+
)
|
|
416
|
+
'''
|
|
417
|
+
else:
|
|
418
|
+
exported = f'''
|
|
419
|
+
def {self.name}(
|
|
420
|
+
subject: str,
|
|
421
|
+
to: str | list[str],
|
|
422
|
+
body_text: str | None = None,
|
|
423
|
+
body_html: str | None = None,
|
|
424
|
+
attachments: list | None = None,
|
|
425
|
+
cc: list[str] | None = None,
|
|
426
|
+
bcc: list[str] | None = None,
|
|
427
|
+
reply_to: list[str] | None = None,
|
|
428
|
+
sender: str | None = None,
|
|
429
|
+
) -> dict:
|
|
430
|
+
"""Send email synchronously.
|
|
431
|
+
|
|
432
|
+
At least one recipient must be specified.
|
|
433
|
+
At least one of `body_text` or `body_html` must be provided.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
subject: The email subject.
|
|
437
|
+
to: The recipient email address(es).
|
|
438
|
+
body_text: The plain text body of the email.
|
|
439
|
+
body_html: The HTML body of the email.
|
|
440
|
+
attachments: Any file attachments to include.
|
|
441
|
+
cc: The CC email address(es).
|
|
442
|
+
bcc: The BCC email address(es).
|
|
443
|
+
reply_to: The reply-to email address(es).
|
|
444
|
+
sender: The sender email address.
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
A dictionary containing the result of the email sending operation.
|
|
448
|
+
"""
|
|
449
|
+
return _send_email_sync(
|
|
450
|
+
subject=subject,
|
|
451
|
+
to=to,
|
|
452
|
+
body_text=body_text,
|
|
453
|
+
body_html=body_html,
|
|
454
|
+
attachments=attachments,
|
|
455
|
+
cc=cc,
|
|
456
|
+
bcc=bcc,
|
|
457
|
+
reply_to=reply_to,
|
|
458
|
+
sender=sender,
|
|
459
|
+
)
|
|
460
|
+
'''
|
|
461
|
+
|
|
462
|
+
return base + exported
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
SendEmailTool = SendEmailToolImpl()
|
|
466
|
+
SendEmailConfig = PredefinedToolConfig(
|
|
467
|
+
name=SendEmailTool.name,
|
|
468
|
+
description=SendEmailTool.description,
|
|
469
|
+
tags=SendEmailTool.tags,
|
|
470
|
+
requirements=SendEmailTool.requirements,
|
|
471
|
+
required_secrets=SendEmailTool.required_secrets,
|
|
472
|
+
required_kwargs=SendEmailTool.required_kwargs,
|
|
473
|
+
implementation=SendEmailTool,
|
|
474
|
+
)
|
|
@@ -16,10 +16,9 @@ class GoogleSearchToolImpl(PredefinedTool):
|
|
|
16
16
|
|
|
17
17
|
required_secrets: list[str] = [
|
|
18
18
|
"GOOGLE_SEARCH_API_KEY",
|
|
19
|
+
"GOOGLE_SEARCH_ENGINE_ID",
|
|
19
20
|
]
|
|
20
|
-
required_kwargs: dict[str, type] = {
|
|
21
|
-
"google_search_engine_id": str,
|
|
22
|
-
}
|
|
21
|
+
required_kwargs: dict[str, type] = {}
|
|
23
22
|
_kwargs: dict[str, Any] = {}
|
|
24
23
|
|
|
25
24
|
@property
|
|
@@ -40,7 +39,7 @@ class GoogleSearchToolImpl(PredefinedTool):
|
|
|
40
39
|
@property
|
|
41
40
|
def requirements(self) -> list[str]:
|
|
42
41
|
"""Python requirements for the tool."""
|
|
43
|
-
return ["ag2[google-search,gemini
|
|
42
|
+
return ["ag2[google-search,gemini]"]
|
|
44
43
|
|
|
45
44
|
@property
|
|
46
45
|
def tags(self) -> list[str]:
|
|
@@ -125,6 +124,7 @@ class GoogleSearchToolImpl(PredefinedTool):
|
|
|
125
124
|
def get_content(
|
|
126
125
|
self,
|
|
127
126
|
secrets: dict[str, str],
|
|
127
|
+
runtime_kwargs: dict[str, Any] | None = None,
|
|
128
128
|
) -> str:
|
|
129
129
|
"""Get content for the tool.
|
|
130
130
|
|
|
@@ -132,6 +132,8 @@ class GoogleSearchToolImpl(PredefinedTool):
|
|
|
132
132
|
----------
|
|
133
133
|
secrets : dict[str, str]
|
|
134
134
|
Dictionary of secrets/environment variables.
|
|
135
|
+
runtime_kwargs : dict[str, Any] | None, optional
|
|
136
|
+
Runtime keyword arguments to customize the content generation.
|
|
135
137
|
|
|
136
138
|
Returns
|
|
137
139
|
-------
|
|
@@ -163,8 +165,8 @@ def {self.name}(
|
|
|
163
165
|
)
|
|
164
166
|
return {self.name}_tool(
|
|
165
167
|
query=query,
|
|
166
|
-
search_api_key=
|
|
167
|
-
search_engine_id=
|
|
168
|
+
search_api_key=google_search_api_key,
|
|
169
|
+
search_engine_id=google_search_engine_id,
|
|
168
170
|
num_results=num_results
|
|
169
171
|
)
|
|
170
172
|
'''
|
|
@@ -106,6 +106,7 @@ class PerplexitySearchToolImpl(PredefinedTool):
|
|
|
106
106
|
def get_content(
|
|
107
107
|
self,
|
|
108
108
|
secrets: dict[str, str],
|
|
109
|
+
runtime_kwargs: dict[str, Any] | None = None,
|
|
109
110
|
) -> str:
|
|
110
111
|
"""Get content for the tool.
|
|
111
112
|
|
|
@@ -113,6 +114,8 @@ class PerplexitySearchToolImpl(PredefinedTool):
|
|
|
113
114
|
----------
|
|
114
115
|
secrets : dict[str, str]
|
|
115
116
|
Dictionary of secrets/environment variables.
|
|
117
|
+
runtime_kwargs : dict[str, Any] | None, optional
|
|
118
|
+
Runtime keyword arguments to customize the content generation.
|
|
116
119
|
|
|
117
120
|
Returns
|
|
118
121
|
-------
|
|
@@ -98,6 +98,7 @@ class SearxNGSearchToolImpl(PredefinedTool):
|
|
|
98
98
|
def get_content(
|
|
99
99
|
self,
|
|
100
100
|
secrets: dict[str, str],
|
|
101
|
+
runtime_kwargs: dict[str, Any] | None = None,
|
|
101
102
|
) -> str:
|
|
102
103
|
"""Get content for the tool.
|
|
103
104
|
|
|
@@ -105,6 +106,8 @@ class SearxNGSearchToolImpl(PredefinedTool):
|
|
|
105
106
|
----------
|
|
106
107
|
secrets : dict[str, str]
|
|
107
108
|
Dictionary of secrets/environment variables.
|
|
109
|
+
runtime_kwargs : dict[str, Any] | None, optional
|
|
110
|
+
Runtime keyword arguments to customize the content generation.
|
|
108
111
|
|
|
109
112
|
Returns
|
|
110
113
|
-------
|
|
@@ -34,7 +34,7 @@ class TavilySearchToolImpl(PredefinedTool):
|
|
|
34
34
|
@property
|
|
35
35
|
def requirements(self) -> list[str]:
|
|
36
36
|
"""Python requirements for the tool."""
|
|
37
|
-
return ["ag2[tavily
|
|
37
|
+
return ["ag2[tavily]"]
|
|
38
38
|
|
|
39
39
|
@property
|
|
40
40
|
def tags(self) -> list[str]:
|
|
@@ -84,6 +84,7 @@ class TavilySearchToolImpl(PredefinedTool):
|
|
|
84
84
|
def get_content(
|
|
85
85
|
self,
|
|
86
86
|
secrets: dict[str, str],
|
|
87
|
+
runtime_kwargs: dict[str, Any] | None = None,
|
|
87
88
|
) -> str:
|
|
88
89
|
"""Get content for the tool.
|
|
89
90
|
|
|
@@ -91,6 +92,8 @@ class TavilySearchToolImpl(PredefinedTool):
|
|
|
91
92
|
----------
|
|
92
93
|
secrets : dict[str, str]
|
|
93
94
|
Dictionary of secrets/environment variables.
|
|
95
|
+
runtime_kwargs : dict[str, Any] | None, optional
|
|
96
|
+
Runtime keyword arguments to customize the content generation.
|
|
94
97
|
|
|
95
98
|
Returns
|
|
96
99
|
-------
|
|
@@ -43,7 +43,7 @@ class WikipediaSearchToolImpl(PredefinedTool):
|
|
|
43
43
|
@property
|
|
44
44
|
def requirements(self) -> list[str]:
|
|
45
45
|
"""Python requirements for the tool."""
|
|
46
|
-
return ["ag2[wikipedia
|
|
46
|
+
return ["ag2[wikipedia]"]
|
|
47
47
|
|
|
48
48
|
@property
|
|
49
49
|
def tags(self) -> list[str]:
|
|
@@ -104,6 +104,7 @@ class WikipediaSearchToolImpl(PredefinedTool):
|
|
|
104
104
|
def get_content(
|
|
105
105
|
self,
|
|
106
106
|
secrets: dict[str, str],
|
|
107
|
+
runtime_kwargs: dict[str, Any] | None = None,
|
|
107
108
|
) -> str:
|
|
108
109
|
"""Get the content of the tool.
|
|
109
110
|
|
|
@@ -111,6 +112,8 @@ class WikipediaSearchToolImpl(PredefinedTool):
|
|
|
111
112
|
----------
|
|
112
113
|
secrets : dict[str, str]
|
|
113
114
|
Dictionary of secrets/environment variables.
|
|
115
|
+
runtime_kwargs : dict[str, Any] | None, optional
|
|
116
|
+
Runtime keyword arguments to customize the content generation.
|
|
114
117
|
|
|
115
118
|
Returns
|
|
116
119
|
-------
|
|
@@ -36,7 +36,7 @@ class YouTubeSearchToolImpl(PredefinedTool):
|
|
|
36
36
|
@property
|
|
37
37
|
def requirements(self) -> list[str]:
|
|
38
38
|
"""Python requirements for the tool."""
|
|
39
|
-
return ["ag2[google-search
|
|
39
|
+
return ["ag2[google-search]"]
|
|
40
40
|
|
|
41
41
|
@property
|
|
42
42
|
def tags(self) -> list[str]:
|
|
@@ -87,6 +87,7 @@ class YouTubeSearchToolImpl(PredefinedTool):
|
|
|
87
87
|
def get_content(
|
|
88
88
|
self,
|
|
89
89
|
secrets: dict[str, str],
|
|
90
|
+
runtime_kwargs: dict[str, Any] | None = None,
|
|
90
91
|
) -> str:
|
|
91
92
|
"""Get content for the tool.
|
|
92
93
|
|
|
@@ -94,6 +95,8 @@ class YouTubeSearchToolImpl(PredefinedTool):
|
|
|
94
95
|
----------
|
|
95
96
|
secrets : dict[str, str]
|
|
96
97
|
Dictionary of secrets/environment variables.
|
|
98
|
+
runtime_kwargs : dict[str, Any] | None, optional
|
|
99
|
+
Runtime keyword arguments to customize the content generation.
|
|
97
100
|
|
|
98
101
|
Returns
|
|
99
102
|
-------
|
|
@@ -37,6 +37,7 @@ class PredefinedTool(Protocol):
|
|
|
37
37
|
def get_content(
|
|
38
38
|
self,
|
|
39
39
|
secrets: dict[str, str],
|
|
40
|
+
runtime_kwargs: dict[str, Any] | None = None,
|
|
40
41
|
) -> str:
|
|
41
42
|
"""Get the content of the tool.
|
|
42
43
|
|
|
@@ -44,6 +45,8 @@ class PredefinedTool(Protocol):
|
|
|
44
45
|
----------
|
|
45
46
|
secrets : dict[str, str]
|
|
46
47
|
Dictionary of secrets/environment variables.
|
|
48
|
+
runtime_kwargs : dict[str, Any] | None, optional
|
|
49
|
+
Runtime keyword arguments to customize the content generation.
|
|
47
50
|
|
|
48
51
|
Returns
|
|
49
52
|
-------
|